<?php
/**
 * Per-entity donation receipt customisation.
 *
 * @since   1.0.0
 * @package DonationRouter
 */

declare( strict_types=1 );
namespace DonationRouter;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Registers GiveWP email template tags that resolve to the correct legal entity
 * details for each donation, and generates per-account receipt sequence numbers.
 *
 * Tags registered (use inside GiveWP email templates):
 *   {paypal_account_entity_name}
 *   {paypal_account_entity_address}
 *   {paypal_account_entity_tax_id}
 *   {paypal_account_entity_country}
 *   {paypal_account_tax_deductibility_statement}
 *   {paypal_account_donor_country}
 *   {paypal_account_receipt_number}
 *
 * All tags fall back gracefully (empty string) when the donation was not routed
 * through this plugin, so GiveWP's built-in receipts are unaffected for the
 * primary PayPal account.
 *
 * @since 1.0.0
 */
class Receipt_Customizer {

	use Template_Loader;

	/**
	 * WordPress options key prefix for per-account tax-deductibility statement overrides.
	 *
	 * @since 1.0.0
	 *
	 * @var string
	 */
	public const OPTION_TEMPLATE_PREFIX = 'ador_receipt_template_';

	/**
	 * Donation post-meta key for the allocated receipt number.
	 *
	 * @since 1.0.0
	 *
	 * @var string
	 */
	public const META_RECEIPT_NUMBER = '_ador_receipt_number';

	/**
	 * Account data source.
	 *
	 * @since 1.0.0
	 *
	 * @var Account_Repository_Interface
	 */
	private Account_Repository_Interface $account_repo;

	/**
	 * Receipt sequence allocator.
	 *
	 * @since 1.0.0
	 *
	 * @var Receipt_Numbering
	 */
	private Receipt_Numbering $numbering;

	/**
	 * Initialise with required dependencies.
	 *
	 * @since 1.0.0
	 *
	 * @param Account_Repository_Interface $account_repo  Account data source.
	 * @param Receipt_Numbering            $numbering     Receipt sequence allocator.
	 */
	public function __construct(
		Account_Repository_Interface $account_repo,
		Receipt_Numbering $numbering
	) {
		$this->account_repo = $account_repo;
		$this->numbering    = $numbering;
	}

	/**
	 * Register WordPress hooks.
	 *
	 * Hooks into both the GiveWP email tag system and the payment-insertion
	 * action to allocate a receipt number for each routed donation.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function register_hooks(): void {
		add_action( 'give_add_email_tags', array( $this, 'register_email_tags' ) );
		// Priority 25 — after PayPal_Credential_Router::record_donation_meta() at priority 10.
		add_action( 'give_insert_payment', array( $this, 'allocate_receipt_number' ), 25, 2 );
		add_action( 'give_payment_receipt_after', array( $this, 'render_receipt_rows' ), 10, 2 );
	}

	/**
	 * Register all per-entity email template tags with GiveWP.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function register_email_tags(): void {
		$tags = array(
			array(
				'tag'     => 'paypal_account_entity_name',
				'desc'    => __( 'The legal entity name of the PayPal account that received this donation.', 'alamin-donation-router' ),
				'func'    => array( $this, 'tag_entity_name' ),
				'context' => 'donation',
			),
			array(
				'tag'     => 'paypal_account_entity_address',
				'desc'    => __( 'The mailing address of the PayPal account that received this donation.', 'alamin-donation-router' ),
				'func'    => array( $this, 'tag_entity_address' ),
				'context' => 'donation',
			),
			array(
				'tag'     => 'paypal_account_entity_tax_id',
				'desc'    => __( 'The EIN / BN of the PayPal account that received this donation.', 'alamin-donation-router' ),
				'func'    => array( $this, 'tag_entity_tax_id' ),
				'context' => 'donation',
			),
			array(
				'tag'     => 'paypal_account_entity_country',
				'desc'    => __( 'The country of the PayPal account that received this donation (ISO alpha-2).', 'alamin-donation-router' ),
				'func'    => array( $this, 'tag_entity_country' ),
				'context' => 'donation',
			),
			array(
				'tag'     => 'paypal_account_tax_deductibility_statement',
				'desc'    => __( 'Country-specific tax-deductibility statement for the receiving entity.', 'alamin-donation-router' ),
				'func'    => array( $this, 'tag_tax_deductibility_statement' ),
				'context' => 'donation',
			),
			array(
				'tag'     => 'paypal_account_donor_country',
				'desc'    => __( 'The country the donor selected at checkout.', 'alamin-donation-router' ),
				'func'    => array( $this, 'tag_donor_country' ),
				'context' => 'donation',
			),
			array(
				'tag'     => 'paypal_account_receipt_number',
				'desc'    => __( 'The unique per-entity receipt number for this donation.', 'alamin-donation-router' ),
				'func'    => array( $this, 'tag_receipt_number' ),
				'context' => 'donation',
			),
		);

		foreach ( $tags as $tag_def ) {
			give_add_email_tag( $tag_def );
		}
	}

	// ── Tag callbacks ─────────────────────────────────────────────────────────

	/**
	 * Resolve {paypal_account_entity_name}.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string,mixed> $tag_args  GiveWP tag arguments (contains payment_id).
	 * @param string              $_tag      Tag name (unused).
	 *
	 * @return string
	 */
	public function tag_entity_name( array $tag_args, string $_tag = '' ): string {
		$account = $this->get_account_for_donation( (int) ( $tag_args['payment_id'] ?? 0 ) );
		return $account ? esc_html( $account->legal_entity_name ) : '';
	}

	/**
	 * Resolve {paypal_account_entity_address}.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string,mixed> $tag_args  GiveWP tag arguments.
	 * @param string              $_tag      Tag name (unused).
	 *
	 * @return string
	 */
	public function tag_entity_address( array $tag_args, string $_tag = '' ): string {
		$account = $this->get_account_for_donation( (int) ( $tag_args['payment_id'] ?? 0 ) );
		return $account ? esc_html( $account->legal_entity_address ) : '';
	}

	/**
	 * Resolve {paypal_account_entity_tax_id}.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string,mixed> $tag_args  GiveWP tag arguments.
	 * @param string              $_tag      Tag name (unused).
	 *
	 * @return string
	 */
	public function tag_entity_tax_id( array $tag_args, string $_tag = '' ): string {
		$account = $this->get_account_for_donation( (int) ( $tag_args['payment_id'] ?? 0 ) );
		return $account ? esc_html( $account->legal_entity_tax_id ) : '';
	}

	/**
	 * Resolve {paypal_account_entity_country}.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string,mixed> $tag_args  GiveWP tag arguments.
	 * @param string              $_tag      Tag name (unused).
	 *
	 * @return string
	 */
	public function tag_entity_country( array $tag_args, string $_tag = '' ): string {
		$account = $this->get_account_for_donation( (int) ( $tag_args['payment_id'] ?? 0 ) );
		return $account ? esc_attr( $account->account_country ) : '';
	}

	/**
	 * Resolve {paypal_account_tax_deductibility_statement}.
	 *
	 * Returns the operator-customised statement when one is saved, otherwise
	 * falls back to the country-specific compliance template.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string,mixed> $tag_args  GiveWP tag arguments.
	 * @param string              $_tag      Tag name (unused).
	 *
	 * @return string
	 */
	public function tag_tax_deductibility_statement( array $tag_args, string $_tag = '' ): string {
		$account = $this->get_account_for_donation( (int) ( $tag_args['payment_id'] ?? 0 ) );
		if ( null === $account ) {
			return '';
		}

		$custom = (string) get_option( self::OPTION_TEMPLATE_PREFIX . $account->id, '' );
		if ( '' !== $custom ) {
			return wp_kses_post( $custom );
		}

		return $this->default_tax_statement( $account );
	}

	/**
	 * Resolve {paypal_account_donor_country}.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string,mixed> $tag_args  GiveWP tag arguments.
	 * @param string              $_tag      Tag name (unused).
	 *
	 * @return string
	 */
	public function tag_donor_country( array $tag_args, string $_tag = '' ): string {
		$payment_id = (int) ( $tag_args['payment_id'] ?? 0 );
		if ( $payment_id <= 0 ) {
			return '';
		}
		return esc_html( (string) get_post_meta( $payment_id, PayPal_Credential_Router::META_DONOR_COUNTRY, true ) );
	}

	/**
	 * Resolve {paypal_account_receipt_number}.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string,mixed> $tag_args  GiveWP tag arguments.
	 * @param string              $_tag      Tag name (unused).
	 *
	 * @return string
	 */
	public function tag_receipt_number( array $tag_args, string $_tag = '' ): string {
		$payment_id = (int) ( $tag_args['payment_id'] ?? 0 );
		if ( $payment_id <= 0 ) {
			return '';
		}
		return esc_html( (string) get_post_meta( $payment_id, self::META_RECEIPT_NUMBER, true ) );
	}

	// ── Receipt number generation ─────────────────────────────────────────────

	/**
	 * Allocate and persist a receipt number for a newly created donation.
	 *
	 * Hooked at `give_insert_payment` priority 25 — after
	 * {@see PayPal_Credential_Router::record_donation_meta()} runs at priority 10
	 * and saves the processing account ID we need here.
	 *
	 * Silently skips donations not routed through this plugin.
	 *
	 * @since 1.0.0
	 *
	 * @param int                 $payment_id   Newly created donation post ID.
	 * @param array<string,mixed> $_payment_data Payment data (unused directly).
	 *
	 * @return void
	 */
	public function allocate_receipt_number( int $payment_id, array $_payment_data ): void {
		$account_id = (int) get_post_meta(
			$payment_id,
			PayPal_Credential_Router::META_PROCESSING_ACCOUNT_ID,
			true
		);

		if ( $account_id <= 0 ) {
			return;
		}

		try {
			$account        = $this->account_repo->find_by_id( $account_id );
			$receipt_number = $this->numbering->next( $account_id, $account->account_country );
			update_post_meta( $payment_id, self::META_RECEIPT_NUMBER, $receipt_number );
		} catch ( \Throwable $e ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( '[ADOR] Failed to allocate receipt number for payment ' . $payment_id . ': ' . $e->getMessage() );
		}
	}

	// ── Success-page receipt rows ─────────────────────────────────────────────

	/**
	 * Append entity and receipt-number rows to the GiveWP success-page receipt table.
	 *
	 * Hooked to `give_payment_receipt_after` so the rows appear after GiveWP's
	 * own receipt items. Silently skips donations not routed through this plugin.
	 *
	 * @since 1.0.0
	 *
	 * @param object              $donation      WP_Post donation object.
	 * @param array<string,mixed> $_receipt_args GiveWP receipt arguments (unused).
	 *
	 * @return void
	 */
	public function render_receipt_rows( object $donation, array $_receipt_args ): void {
		$payment_id = (int) ( $donation->ID ?? 0 );
		$account    = $this->get_account_for_donation( $payment_id );

		if ( null === $account ) {
			return;
		}

		$receipt_number = esc_html( (string) get_post_meta( $payment_id, self::META_RECEIPT_NUMBER, true ) );
		$tax_statement  = $this->tag_tax_deductibility_statement( array( 'payment_id' => $payment_id ) );

		$this->render_template( 'receipts/receipt-rows.php', compact( 'account', 'receipt_number', 'tax_statement', 'payment_id' ) );
	}

	// ── Helpers ───────────────────────────────────────────────────────────────

	/**
	 * Load the PayPal account that processed a given donation.
	 *
	 * Returns null when the donation was not routed by this plugin or when the
	 * account can no longer be found.
	 *
	 * @since 1.0.0
	 *
	 * @param int $payment_id  GiveWP donation post ID.
	 *
	 * @return PayPal_Account|null
	 */
	private function get_account_for_donation( int $payment_id ): ?PayPal_Account {
		if ( $payment_id <= 0 ) {
			return null;
		}

		$account_id = (int) get_post_meta(
			$payment_id,
			PayPal_Credential_Router::META_PROCESSING_ACCOUNT_ID,
			true
		);

		if ( $account_id <= 0 ) {
			return null;
		}

		try {
			return $this->account_repo->find_by_id( $account_id );
		} catch ( \Throwable $e ) {
			return null;
		}
	}

	/**
	 * Build the country-specific default tax-deductibility statement.
	 *
	 * These are starting-point templates only. Operators MUST have them
	 * reviewed by qualified accountants before going live.
	 *
	 * @since 1.0.0
	 *
	 * @param PayPal_Account $account  The receiving account.
	 *
	 * @return string  Safe HTML string (may contain entity-escaped characters).
	 */
	public function default_tax_statement( PayPal_Account $account ): string {
		switch ( $account->account_country ) {
			case 'US':
				return sprintf(
					/* translators: 1: legal entity name, 2: EIN / tax ID. */
					__( '%1$s is a 501(c)(3) tax-exempt organization (EIN: %2$s). Your contribution is tax-deductible to the extent allowed by law. No goods or services were provided in exchange for this contribution.', 'alamin-donation-router' ),
					esc_html( $account->legal_entity_name ),
					esc_html( $account->legal_entity_tax_id )
				);

			case 'CA':
				return sprintf(
					/* translators: 1: legal entity name, 2: charitable registration number. */
					__( 'Official Donation Receipt for Income Tax Purposes. %1$s is a registered charity under the Income Tax Act (Charitable Registration No. %2$s RR0001).', 'alamin-donation-router' ),
					esc_html( $account->legal_entity_name ),
					esc_html( $account->legal_entity_tax_id )
				);

			default:
				return '';
		}
	}
}
