<?php
/**
 * Checkout handling for VAT.
 *
 * @package     EDD\Pro\Taxes\VAT
 * @copyright   Copyright (c) 2025, Sandhills Development, LLC
 * @license     https://opensource.org/licenses/gpl-2.0.php GNU Public License
 * @since       3.5.0
 */

namespace EDD\Pro\Taxes\VAT;

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore

use EDD\EventManagement\SubscriberInterface;

/**
 * Checkout handling for VAT.
 *
 * @since 3.5.0
 */
class Checkout implements SubscriberInterface {

	/**
	 * The current VAT state of the cart.
	 *
	 * @var Cart
	 */
	private $cart;

	/**
	 * Constructor.
	 */
	public function __construct( Cart $cart ) {
		$this->cart = $cart;
	}

	/**
	 * Returns the list of events to subscribe to.
	 *
	 * @return array
	 */
	public static function get_subscribed_events() {
		return array(
			'init'                                 => 'register_vat_field_action',
			'wp_enqueue_scripts'                   => 'enqueue_scripts',
			'wp_ajax_edd_vat_check'                => 'ajax_vat_check',
			'wp_ajax_nopriv_edd_vat_check'         => 'ajax_vat_check',
			'wp_ajax_edd_recalculate_taxes'        => array( 'ajax_edd_recalculate_taxes', 5 ),
			'wp_ajax_nopriv_edd_recalculate_taxes' => array( 'ajax_edd_recalculate_taxes', 5 ),
			'edd_tax_rate'                         => array( 'get_tax_rate', 500, 3 ),
			'edd_get_cart_tax'                     => 'get_tax_rate',
			'edd_cart_tax'                         => 'cart_tax',
			'edd_cart_item_price'                  => array( 'maybe_adjust_cart_item_price', 10, 3 ),
			'edd_checkout_error_checks'            => array( 'checkout_error_checks', 10, 2 ),
			'edd_built_order'                      => array( 'add_vat_to_order', 10, 2 ),
			'edd_global_checkout_script_vars'      => 'update_checkout_variables',
		);
	}

	/**
	 * Registers the action which output the VAT field.
	 *
	 * @since 3.5.0
	 */
	public function register_vat_field_action() {
		add_action( apply_filters( 'edd_vat_checkout_vat_field_location', 'edd_cc_billing_bottom' ), array( $this, 'display_vat_field' ) );
	}

	/**
	 * Enqueue the VAT script.
	 *
	 * @since 3.5.0
	 */
	public function enqueue_scripts() {
		if ( edd_is_checkout() && ! empty( edd_get_cart_contents() ) ) {
			wp_enqueue_script( 'edd-pro-vat', EDD_PLUGIN_URL . 'assets/pro/js/vat.js', array( 'edd-checkout-global' ), EDD_VERSION, true );
		}
	}

	/**
	 * Display VAT field on checkout (default location: below the billing address).
	 *
	 * @since 3.5.0
	 */
	public function display_vat_field() {
		if ( ! $this->should_display_vat_field() ) {
			return;
		}

		$vat_details = $this->cart->get_vat_details();

		ob_start();
		$field = new \EDD\Pro\Forms\Checkout\VAT( $vat_details );
		$field->render();
		echo wp_kses_post( $this->get_vat_check_result() );

		echo apply_filters( 'edd_vat_checkout_vat_field_html', ob_get_clean(), $vat_details, $this->cart->is_reverse_charged() );
	}

	/**
	 * AJAX action for VAT number check.
	 *
	 * @since 3.5.0
	 */
	public function ajax_vat_check() {
		if ( ! $this->verify_checkout_address_nonce() ) {
			return;
		}

		if ( ! edd_get_cart_contents() ) {
			return;
		}

		$this->check_vat();

		if ( \EDD\Checkout\Validator::has_block() ) {
			EDD()->cart->set_tax_rate( null );
		}

		ob_start();
		edd_checkout_cart();

		wp_send_json(
			array(
				'html'             => ob_get_clean(),
				'tax_raw'          => edd_get_cart_tax(),
				'tax'              => html_entity_decode( edd_cart_tax(), ENT_COMPAT, 'UTF-8' ),
				'tax_rate_raw'     => edd_get_tax_rate(),
				'tax_rate'         => html_entity_decode( edd_get_formatted_tax_rate(), ENT_COMPAT, 'UTF-8' ),
				'total'            => html_entity_decode( edd_cart_total( false ), ENT_COMPAT, 'UTF-8' ),
				'total_raw'        => edd_get_cart_total(),
				'vat_check_result' => $this->get_vat_check_result(),
			)
		);
	}

	/**
	 * Maybe clear the VAT state of the cart if the country has changed.
	 *
	 * @since 3.5.0
	 */
	public function ajax_edd_recalculate_taxes() {
		if ( ! $this->verify_checkout_address_nonce() ) {
			return;
		}

		$selected_country = isset( $_POST['billing_country'] ) ? sanitize_text_field( $_POST['billing_country'] ) : edd_get_shop_country();

		// Clear the VAT state if the country has changed.
		if ( ! empty( $this->cart->get_vat_details()->country_code ) && $selected_country !== $this->cart->get_vat_details()->country_code ) {
			edd_debug_log( 'EUVAT: ajax_edd_recalculate_taxes cleared VAT state of country has changed' );
			$this->cart->clear();
		}
	}

	/**
	 * Gets the tax rate for reverse charged orders.
	 *
	 * @param float  $rate    The tax rate.
	 * @param string $country The country code. Not used by `edd_get_cart_tax`.
	 * @param string $state   The state code. Not used by `edd_get_cart_tax`.
	 * @return float
	 */
	public function get_tax_rate( $rate, $country = null, $state = null ) {
		if ( ! $this->cart->is_reverse_charged() ) {
			return $rate;
		}

		/**
		 * Allow filtering the VAT rate for reverse charged orders.
		 *
		 * @param float  $rate    The VAT rate.
		 * @param string $country The country code.
		 * @param string $state   The state code.
		 * @return float
		 */
		return apply_filters( 'edd_vat_reverse_charge_vat_rate', 0.00, $country, $state );
	}

	/**
	 * Gets the cart tax label.
	 *
	 * @since 3.5.0
	 * @param string $cart_tax The cart tax label.
	 * @return string
	 */
	public function cart_tax( $cart_tax ) {

		// Check if the cart tax already has any bracketed format applied and return early to avoid duplication.
		if ( preg_match( '/^\[.*?\]/', $cart_tax ) ) {
			return $cart_tax;
		}

		if ( $this->cart->is_reverse_charged() ) {
			return apply_filters(
				'edd_vat_cart_label_reverse_charged',
				/* translators: %s is the formatted tax amount */
				sprintf( __( '[VAT reverse charged] %s', 'easy-digital-downloads' ), $cart_tax ),
				$cart_tax
			);
		}

		$tax_rate = edd_get_tax_rate();
		if ( $tax_rate ) {
			$tax_percent    = $tax_rate * 100;
			$formatted_rate = round( $tax_percent ) === $tax_percent ? sprintf( '%.0f', $tax_percent ) : sprintf( '%.1f', $tax_percent );

			return apply_filters(
				'edd_vat_cart_label_tax_rate',
				sprintf( '[%1$s%%] %2$s', $formatted_rate, $cart_tax ),
				$cart_tax,
				$formatted_rate
			);
		}

		edd_debug_log(
			'EUVAT: cart_tax called with arguments: ' . print_r(
				array(
					'$cart_tax'       => $cart_tax,
					'reverse_charged' => $this->cart->is_reverse_charged(),
				),
				true
			)
		);

		return $cart_tax;
	}

	/**
	 * Subtract VAT from cart item total if price includes tax.
	 *
	 * @param mixed $total       The total price.
	 * @param mixed $download_id The download ID.
	 * @param mixed $options     The options.
	 * @return mixed
	 */
	public function maybe_adjust_cart_item_price( $total, $download_id, $options ) {
		if ( ! edd_prices_include_tax() ) {
			return $total;
		}

		if ( ! $this->cart->is_reverse_charged() ) {
			return $total;
		}

		$country_code = $this->cart->get_vat_details()->country_code;
		if ( empty( $country_code ) ) {
			return $total;
		}

		$rate = edd_get_tax_rate_by_location(
			array(
				'country' => $country_code,
			)
		);
		if ( ! $rate ) {
			return $total;
		}

		$tax_rate = (float) $rate->amount / 100;

		return $total / ( 1 + $tax_rate );
	}

	/**
	 * Handle VAT checks on checkout if necessary.
	 *
	 * @param bool|array $valid_data The validated data.
	 * @param array      $post_data  The submitted data.
	 */
	public function checkout_error_checks( $valid_data, $post_data ) {

		$check_vat   = false;
		$vat_details = $this->cart->get_vat_details();

		if ( empty( $post_data['vat_number'] ) && ! empty( $vat_details->vat_number ) ) {
			$check_vat = true;
		} elseif ( ! empty( $post_data['vat_number'] ) ) {
			$vat_number = sanitize_text_field( $post_data['vat_number'] );
			if ( $vat_number !== $vat_details->vat_number ) {
				$check_vat = true;
			}
		}

		if ( $check_vat ) {
			$this->check_vat();
		}
	}

	/**
	 * Add VAT details to the EDD order meta.
	 *
	 * @param int   $order_id     The order ID.
	 * @param array $payment_data The payment data.
	 */
	public function add_vat_to_order( $order_id, $payment_data ) {
		if ( $this->cart->is_reverse_charged() ) {
			edd_update_order_meta( $order_id, '_edd_payment_vat_reverse_charged', true );
		}

		$country_code = $this->get_order_country_code( $order_id, $payment_data );
		$details      = $this->cart->get_vat_details();
		if ( ! $details ) {
			return;
		}

		if ( ! empty( $details->vat_number ) ) {
			edd_update_order_meta( $order_id, '_edd_payment_vat_number', $details->vat_number );
		}

		if ( ! empty( $details->consultation_number ) ) {
			edd_update_order_meta( $order_id, '_edd_payment_vat_consultation_number', $details->consultation_number );
		}

		if ( $details->is_valid() ) {
			edd_update_order_meta( $order_id, '_edd_payment_vat_number_valid', true );
		}

		if ( ! empty( $details->name ) ) {
			edd_update_order_meta( $order_id, '_edd_payment_vat_company_name', $details->name );
		}

		if ( ! empty( $details->address ) ) {
			edd_update_order_meta( $order_id, '_edd_payment_vat_company_address', $details->address );
		}

		if ( ! empty( $details->consultation_number ) ) {
			edd_update_order_meta( $order_id, '_edd_payment_vat_consultation_number', $details->consultation_number );
		}

		if ( ! edd_is_debug_mode() ) {
			return;
		}

		edd_insert_payment_note(
			$order_id,
			print_r(
				array(
					'details'         => $details,
					'reverse_charged' => $this->cart->is_reverse_charged(),
					'payment_id'      => $order_id,
					'country_code'    => $country_code,
				),
				true
			)
		);
	}

	/**
	 * Handle VAT input on the checkout.
	 *
	 * @since 3.5.0
	 */
	private function check_vat() {
		$vat_number   = filter_input( INPUT_POST, 'vat_number', FILTER_SANITIZE_SPECIAL_CHARS );
		$country_code = filter_input( INPUT_POST, 'billing_country', FILTER_SANITIZE_SPECIAL_CHARS );

		if ( empty( $vat_number ) || empty( $country_code ) ) {
			return;
		}

		// Check the submitted VAT number.
		$this->cart->check_vat_number( $vat_number, $country_code );
	}

	/**
	 * Gets the VAT check result html.
	 *
	 * @since 3.5.0
	 * @return string
	 */
	private function get_vat_check_result() {
		$vat_details = $this->cart->get_vat_details();

		if ( ! $vat_details ) {
			return '';
		}

		ob_start();
		edd_get_template_part(
			'checkout/vat-result',
			null,
			true,
			array(
				'vat_details'  => $vat_details,
				'result_text'  => $this->get_vat_result_text( $vat_details ),
				'result_class' => $this->get_vat_result_class( $vat_details ),
			)
		);

		return apply_filters( 'edd_vat_checkout_vat_result_html', ob_get_clean(), $vat_details, $this->cart->is_reverse_charged() );
	}

	/**
	 * Determines whether the VAT field should be displayed.
	 *
	 * @since 3.5.0
	 * @return bool
	 */
	private function should_display_vat_field(): bool {
		$show_vat_field = ( did_action( 'edd_purchase_form_top' ) && ! did_action( 'edd_purchase_form_bottom' ) ) || edd_is_checkout();

		return apply_filters( 'edd_vat_checkout_display_vat_field', $show_vat_field );
	}

	/**
	 * Update the checkout localization variables.
	 *
	 * @since 3.5.0
	 * @param array $variables The array of localization variables.
	 * @return array
	 */
	public function update_checkout_variables( $variables ): array {
		$variables['edd_eu_countries']               = \EDD\Utils\Countries::get_eu_countries();
		$variables['checkout_updated_cart_selector'] = apply_filters( 'edd_vat_checkout_updated_cart_selector', '#edd_checkout_cart_form' );
		$variables['checkout_form_selector']         = apply_filters( 'edd_vat_checkout_form_selector', '#edd_checkout_cart_form' );
		$variables['messages']                       = array(
			'vat_number_missing' => __( 'Please enter a VAT number.', 'easy-digital-downloads' ),
			'country_missing'    => __( 'Please select a country.', 'easy-digital-downloads' ),
			'ajax_error'         => __( 'There was an error trying to validate your VAT number. Please try reloading the page.', 'easy-digital-downloads' ),
		);

		return $variables;
	}

	/**
	 * Gets the VAT check result text.
	 *
	 * @param Result $vat_details The VAT details.
	 * @return string
	 */
	private function get_vat_result_text( $vat_details ): string {
		if ( $vat_details->is_valid() ) {
			if ( $this->cart->is_reverse_charged() ) {
				$result_text = __( 'VAT number valid - reverse charge applied.', 'easy-digital-downloads' );
			} else {
				/* translators: %s: Country name */
				$result_text = sprintf( __( 'Your VAT number is valid, but we cannot apply the reverse charge in %s.', 'easy-digital-downloads' ), edd_get_country_name( $vat_details->country_code ) );
			}
		} else {
			$result_text = $vat_details::get_message( $vat_details->error );
		}

		/**
		 * Filters the VAT check result text.
		 *
		 * @since 3.5.0
		 *
		 * @param string $result_text The result text.
		 * @param Result $vat_details The VAT details.
		 * @return string
		 */
		return apply_filters( 'edd_vat_check_result_text', $result_text, $vat_details );
	}

	/**
	 * Gets the VAT check result class.
	 *
	 * @param Result $vat_details The VAT details.
	 * @return string
	 */
	private function get_vat_result_class( $vat_details ): string {
		$result_class = $vat_details->is_valid() ? 'edd-vat__check--success' : 'edd-vat__check--error';

		/**
		 * Filters the VAT check result class.
		 *
		 * @since 3.5.0
		 *
		 * @param string $result_class The result class.
		 * @param Result $vat_details  The VAT details.
		 * @return string
		 */
		return apply_filters( 'edd_vat_check_result_class', $result_class, $vat_details );
	}

	/**
	 * Verifies the checkout address nonce.
	 *
	 * @since 3.5.0
	 * @return bool
	 */
	private function verify_checkout_address_nonce(): bool {
		$nonce = filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_SPECIAL_CHARS );

		return $nonce && wp_verify_nonce( $nonce, 'edd-checkout-address-fields' );
	}

	/**
	 * Gets the country code for the order.
	 *
	 * @since 3.5.0
	 * @param int   $order_id     The order ID.
	 * @param array $payment_data The payment data.
	 * @return string
	 */
	private function get_order_country_code( $order_id, $payment_data ) {
		$order   = edd_get_order( $order_id );
		$address = $order->get_address();
		if ( ! empty( $address->country ) && in_array( $address->country, \EDD\Utils\Countries::get_eu_countries(), true ) ) {
			edd_update_order_meta( $order_id, '_edd_payment_vat_is_eu', 1 );
			return $address->country;
		}

		return $payment_data['user_info']['address']['country'];
	}
}
