<?php
/**
 * EU VAT exporter.
 *
 * @package     EDD\Admin\Exports\Exporters
 * @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\Admin\Exports\Exporters;

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

use EDD\Admin\Exports\Exporters\Exporter;

/**
 * EUVAT Class.
 *
 * @since 3.5.0
 */
class EUVAT extends Exporter {
	use \EDD\Admin\Exports\Exporters\Traits\TempData;

	/**
	 * The export key.
	 *
	 * @since 3.5.0
	 * @var string
	 */
	private $export_key = '';

	/**
	 * The cache key.
	 *
	 * @since 3.5.0
	 * @var string
	 */
	private $cache_key;

	/**
	 * The countries to export.
	 *
	 * @var array
	 */
	private $countries = array();

	/**
	 * Set the properties specific to the payments export
	 *
	 * @param array $request The Form Data passed into the batch processing.
	 */
	public function set_properties( $request ): void {
		if ( ! empty( $request['eu-vat-export-start'] ) ) {
			$this->start = sanitize_text_field( $request['eu-vat-export-start'] );
		}
		if ( ! empty( $request['eu-vat-export-end'] ) ) {
			$this->end = sanitize_text_field( $request['eu-vat-export-end'] );
		}
		if ( ! empty( $request['export-key'] ) ) {
			$this->export_key = sanitize_text_field( $request['export-key'] );
		}
		if ( ! empty( $request['eu_vat_country'] ) ) {
			$this->countries[] = sanitize_text_field( $request['eu_vat_country'] );
		} else {
			$this->countries = \EDD\Utils\Countries::get_eu_countries();
			$invoice_country = edd_get_option( 'edd_vat_address_invoice', '' );
			if ( ! empty( $invoice_country ) ) {
				$key = array_search( $invoice_country, $this->countries, true );
				if ( false !== $key ) {
					unset( $this->countries[ $key ] );
				}
			}
		}
		$this->cache_key = "edd_eu_vat_export_{$this->export_key}";
	}

	/**
	 * Process the current step.
	 *
	 * Clears any stale temp file on step 1 so an interrupted prior export
	 * does not inflate the current run's totals.
	 *
	 * @since 3.6.8
	 * @return bool
	 */
	public function process_step() {
		if ( $this->step < 2 ) {
			$this->reset_temp_file();
		}

		return parent::process_step();
	}

	/**
	 * Get the export type.
	 *
	 * @since 3.5.0
	 * @return string
	 */
	protected function get_export_type(): string {
		return 'eu_vat';
	}

	/**
	 * Set the CSV columns.
	 *
	 * @since 3.5.0
	 * @return array The columns for the CSV file.
	 */
	protected function get_data_headers(): array {
		return array(
			'country'  => __( 'Country', 'easy-digital-downloads' ),
			'currency' => __( 'Currency', 'easy-digital-downloads' ),
			'vat_rate' => __( 'Standard VAT Rate', 'easy-digital-downloads' ),
			'amount'   => __( 'Value of Items (Excluding VAT)', 'easy-digital-downloads' ),
			'vat'      => __( 'VAT', 'easy-digital-downloads' ),
		);
	}

	/**
	 * Get the total number of items to export.
	 *
	 * @since 3.5.0
	 * @return int
	 */
	protected function get_total(): int {
		add_filter( 'edd_orders_query_clauses', array( $this, 'filter_by_eu_countries' ) );
		$total = edd_count_orders( $this->get_order_args() );
		remove_filter( 'edd_orders_query_clauses', array( $this, 'filter_by_eu_countries' ) );
		return $total;
	}

	/**
	 * Gets the data being exported for one step.
	 *
	 * @since 3.5.0
	 * @return array $data Data for export.
	 */
	protected function get_data(): array {
		$data           = array();
		$args           = $this->get_order_args();
		$args['number'] = $this->per_step;
		$args['offset'] = ( $this->step * $this->per_step ) - $this->per_step;

		add_filter( 'edd_orders_query_clauses', array( $this, 'filter_by_eu_countries' ) );
		$orders = edd_get_orders( $args );
		remove_filter( 'edd_orders_query_clauses', array( $this, 'filter_by_eu_countries' ) );

		if ( ! $orders ) {
			return $data;
		}

		foreach ( $orders as $order ) {
			$address = $order->get_address();
			if ( empty( $address->country ) || ! in_array( $address->country, $this->countries, true ) ) {
				continue;
			}
			$country_name = edd_get_country_name( $address->country );
			$key          = implode( '_', array_filter( array( $address->country, $order->currency, $order->tax_rate_id ) ) );
			if ( ! array_key_exists( $key, $data ) ) {
				$data[ $key ] = array(
					'country'  => $country_name,
					'currency' => $order->currency,
					'amount'   => 0,
					'vat'      => 0,
					'vat_rate' => $this->get_tax_rate( $address, $order ),
				);
			}

			$net                     = $this->get_net_amounts( $order );
			$data[ $key ]['amount'] += $net['amount'];
			$data[ $key ]['vat']    += $net['vat'];
		}

		return $data;
	}

	/**
	 * Gets the net amount and VAT for an order, accounting for any partial refunds.
	 *
	 * @since 3.6.8
	 * @param \EDD\Orders\Order $order The order.
	 * @return array {
	 *     @type float $amount Value of items excluding VAT.
	 *     @type float $vat    VAT amount.
	 * }
	 */
	private function get_net_amounts( \EDD\Orders\Order $order ): array {
		$total = $order->total;
		$tax   = $order->tax;

		if ( 'partially_refunded' === $order->status ) {
			foreach ( edd_get_order_refunds( $order->id ) as $refund ) {
				$total += $refund->total;
				$tax   += $refund->tax;
			}
		}

		return array(
			'amount' => $total - $tax,
			'vat'    => $tax,
		);
	}

	/**
	 * Gets the parameters for the orders query.
	 *
	 * @since 3.5.0
	 * @return array The order arguments.
	 */
	private function get_order_args(): array {
		$args = array(
			'type'         => 'sale',
			'status__in'   => array_values( array_diff( edd_get_complete_order_statuses(), array( 'refunded' ) ) ),
			'tax__compare' => array(
				'relation' => 'AND',
				array(
					'value'   => '0',
					'compare' => '>',
				),
			),
		);
		if ( ! empty( $this->start ) || ! empty( $this->end ) ) {
			$args['date_query'] = $this->get_date_query();
		}

		return $args;
	}

	/**
	 * Add a country filter to the orders query via a WHERE EXISTS subquery on order addresses.
	 *
	 * Uses EXISTS rather than INNER JOIN to avoid row fan-out when an order has more
	 * than one billing-type address row in the edd_order_addresses table.
	 *
	 * @since 3.6.8
	 * @param array $clauses The query clauses.
	 * @return array
	 */
	public function filter_by_eu_countries( array $clauses ): array {
		if ( empty( $this->countries ) ) {
			return $clauses;
		}

		global $wpdb;
		$placeholders = implode( ', ', array_fill( 0, count( $this->countries ), '%s' ) );
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$clauses['where'] .= $wpdb->prepare(
			" AND EXISTS (
				SELECT 1 FROM {$wpdb->prefix}edd_order_addresses oa_eu
				WHERE oa_eu.order_id = edd_o.id
				AND oa_eu.type = 'billing'
				AND oa_eu.country IN ({$placeholders})
			)",
			...$this->countries
		);

		return $clauses;
	}

	/**
	 * Get the tax rate for an order.
	 *
	 * @param \EDD\Orders\Order_Address $address The address of the order.
	 * @param \EDD\Orders\Order         $order   The order.
	 * @return string The tax rate.
	 */
	private function get_tax_rate( $address, $order ) {
		$tax_rate_object = $order->get_tax_rate_object();
		if ( $tax_rate_object ) {
			return sprintf( '%.2f%%', $tax_rate_object->amount );
		}

		$rate = edd_get_tax_rate_by_location( array( 'country' => $address->country ) );
		if ( $rate ) {
			$amount = $rate->amount;
		} else {
			$amount = edd_get_order_meta( $order->id, 'tax_rate', true );
		}

		if ( ! $amount ) {
			$amount = 0;
		}

		return sprintf( '%.2f%%', $amount );
	}
}
