<?php
/**
 * The file that defines the subscriptions functionality
 *
 * A class definition that includes attributes and functions used across both the
 * public-facing side of the site and the admin area.
 *
 * @package    Authorize_Net_Payments_For_Woocommerce
 * @subpackage Authorize_Net_Payments_For_Woocommerce/includes
 */

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

require_once plugin_dir_path(__DIR__) . 'includes/gateway/class-anet-credit-card-gateway.php';
require_once plugin_dir_path(__FILE__) . 'trait-anet-order-meta.php';

/**
 * The subscriptions plugin class.
 *
 *
 * @package    Authorize_Net_Payments_For_Woocommerce
 * @subpackage Authorize_Net_Payments_For_Woocommerce/includes
 */
class Anet_Subscriptions
{
	use Anet_Order_Meta;
	/**
	 * Configuration settings.
	 *
	 * @var mixed
	 */
	private $settings;
	/**
	 * The gateway ID property.
	 *
	 * @var string
	 */
	private $id;
	/**
	 * Indicates whether the subscription is currently active.
	 *
	 * @var bool
	 */
	public  $is_subscription_active;
	/**
	 * Subscription constructor.
	 */
	public function __construct()
	{
		$this->settings = get_option('woocommerce_anet_settings', []);
		$this->id = ANET_CREDIT_CARD_GATEWAY_ID;
		$this->is_subscription_active =
			class_exists('WC_Subscriptions') && class_exists('WC_Subscription') && version_compare(WC_Subscriptions::$version, '2.2.0', ANET_GREATER_THAN_EQUAL_TO);
	}
	/**
	 * Add subscription actions.
	 */
	public function add_subscription_actions()
	{
		add_action('woocommerce_scheduled_subscription_payment_' . $this->id, array($this, 'scheduled_subscription_payment'), ANET_ACTION_HOOK_DEFAULT_PRIORITY, ANET_VAL_TWO);
		add_filter('woocommerce_my_subscriptions_payment_method', array($this, 'render_payment_method_for_subscriptions'), ANET_ACTION_HOOK_DEFAULT_PRIORITY, ANET_VAL_TWO);
		add_filter('woocommerce_account_payment_methods_column_subscriptions', array($this, 'add_payment_method_subscriptions'));
		add_filter('woocommerce_subscription_payment_meta', array($this, 'admin_add_payment_meta'), ANET_VAL_NINE, ANET_VAL_TWO);
		add_filter('woocommerce_payment_gateways_renewal_support_status_html', array($this, 'subscriptions_maybe_edit_renewal_support_status'), ANET_ACTION_HOOK_DEFAULT_PRIORITY, ANET_VAL_TWO);
		add_filter('woocommerce_subscriptions_process_payment_for_change_method_via_pay_shortcode', array($this, 'remove_order_meta_from_change_payment'), ANET_ACTION_HOOK_DEFAULT_PRIORITY, ANET_VAL_TWO);
		add_action("woocommerce_update_options_checkout_anet", array($this, 'anet_subscription_notice'));
		add_action('woocommerce_subscription_validate_payment_meta_' . $this->id, array($this, 'admin_validate_payment_meta'), ANET_VAL_NINE);
		// Add filter to prevent AFDS review early renewal orders from being considered as "needs payment".
		add_filter('woocommerce_order_needs_payment', array($this, 'afds_review_early_renewal_needs_payment'), ANET_ACTION_HOOK_DEFAULT_PRIORITY, ANET_VAL_TWO);
	}
	/**
	 * Save the setting for subscription notice.
	 */
	public function anet_subscription_notice()
	{ //Check WC subscription enable/disable throwing error.
		$this->settings = get_option('woocommerce_anet_settings', []);
		if (ANET_YES === $this->settings['enabled'] && !(ANET_YES === $this->settings['tokenization'])) {
			$message = sprintf(
				/* translators: %1$s: Payment gateway title */
				esc_html__('To use WooCommerce subscriptions with %1$s, please enable Tokenization.', 'authorize-net-payments-for-woocommerce'),
				esc_html($this->settings['title'])
			);
			wp_admin_notice(
				$message,
				array(
					'id'                 => 'message',
					'additional_classes' => array('error'),
					'dismissible'        => true,
				)
			);
		}
	}
	/**
	 * Subscription maybe Edit renewal support status.
	 *
	 * @param string $html html.
	 * @param object $gateway The current payment gateways object.
	 */
	public function subscriptions_maybe_edit_renewal_support_status($html, $gateway)
	{ //BO config module enable/disable check throwing error.
		if ($gateway->id === $this->id) {
			if (ANET_YES === $this->settings['enabled']  && !(ANET_YES === $this->settings['tokenization'])) {
				$tool_tip = esc_attr__('You must enable tokenization for this gateway in order to support automatic renewal payments with the WooCommerce Subscriptions extension.', 'authorize-net-payments-for-woocommerce');
				$status   = esc_html__('Inactive', 'authorize-net-payments-for-woocommerce');
				$html = sprintf(
					'<a href="%1$s"><span class="anet-wc-payment-gateway-renewal-status-inactive tips" data-tip="%2$s">%3$s</span></a>',
					esc_url(admin_url('admin.php?page=wc-settings&tab=checkout&section=' . $this->id)),
					$tool_tip,
					$status
				);
			}
		}
		return $html;
	}
	/**
	 * Get a WooCommerce payment token by token ID.
	 *
	 * @param string $token_id Token ID to search for.
	 * @param int    $current_user_id User ID to search tokens for.
	 * @return \WC_Payment_Token|null Matching token or null if not found.
	 */
	public function get_wc_token($token_id, $current_user_id)
	{
		$selected_token = null;
		$core_tokens    = \WC_Payment_Tokens::get_customer_tokens($current_user_id, $this->id);
		if (! empty($core_tokens)) {
			foreach ($core_tokens as $token) {
				$data = $token->get_data();
				if (! strcmp($token_id, $data['token'])) {
					$selected_token = $token;
					break;
				}
			}
		}
		return $selected_token;
	}
	/**
	 * Scheduled subscription payment.
	 *
	 * @param int    $amount_to_charge Amount to charge.
	 * @param object $order order details.
	 *
	 * @return array $result
	 */
	public function scheduled_subscription_payment($amount_to_charge, $order)
	{ //Auto renewal or early renewal.
		$result = array('success' => false);
		if ($order->get_payment_method('edit') === $this->id) {
			try {
				$token = null;
				$token = $this->get_order_meta($order, 'anet_subscription_token');
				$current_user_id = $order->get_user_id();
				if (!empty($token)) {
					$token = $this->get_wc_token($token, $current_user_id);
				}
				if (!empty($token) && $token instanceof \WC_Payment_Token) {
					$anet_wc_gateway = new Anet_Credit_Card_Gateway();
					$tokenization = new Anet_Credit_Card_Tokenization();
					$payment_args = $anet_wc_gateway->generate_payment_request_args($order, true, $token->get_id());
					$result = $tokenization->stored_card($order, $payment_args, wcs_order_contains_early_renewal($order) ? false : true);
					$anet_wc_gateway->process_response($result, $order);
					if (isset($result['success']) && $result['success']) {
						$this->update_order_subscription_token($order, $payment_args['transactionRequest']['payment']['tokenData']['paymentProfileId']);
					}
				} else {
					$subscriptions = wcs_get_subscriptions_for_order($order, [
						'order_type' => ['any'],
						'customer_id'  =>  $current_user_id,
					]);
					Anet_Setting_Helper::log('Payment token not found for autorenewal transaction for order ' . $order->get_id() . ' of subscription ' . wp_json_encode($subscriptions), ANET_PAYMENT_TYPE_CREDIT_CARD, 'error');
				}
				// Process both success and failure scenarios AFTER process_response has updated order status.
				if ($result['success']) {
					// Refresh order to get latest status after transaction.
					$order = wc_get_order($order->get_id());
					// Check if this is an early renewal via modal.
					$is_early_renewal = (bool) $order->get_meta('_subscription_renewal_early');
					$accepted_via_modal = (ANET_YES === get_option('woocommerce_subscriptions_enable_early_renewal_via_modal', ANET_NO));
					// Check if order is in AFDS review using the gateway's AFDS meta.
					$is_afds_review = (ANET_YES === $this->get_order_meta($order, '_anet_credit_card_fds_review'));
					// For early renewal orders in AFDS review, add a flag.
					if ($is_afds_review && $is_early_renewal && $accepted_via_modal) {
						$order->update_meta_data('_anet_afds_review_early_renewal', 'yes');
						$order->save();
						// Add success message for AFDS review orders.
						if (function_exists('wc_add_notice')) {
							wc_add_notice(__('Your early renewal order was successful.', 'authorize-net-payments-for-woocommerce'), 'success');
						}
					}
					// Only update subscription payment data if NOT in AFDS review.
					if (!$is_afds_review) {
						$this->add_payment_data_to_subscription($order);
					}
				} else {
					// Refresh order to get latest status after process_response.
					$order = wc_get_order($order->get_id());
					// Handle AFDS reject scenario for early renewal orders.
					$is_early_renewal = (bool) $order->get_meta('_subscription_renewal_early');
					$accepted_via_modal = (ANET_YES === get_option('woocommerce_subscriptions_enable_early_renewal_via_modal', ANET_NO));
					$is_afds_reject = $order->has_status(array(ANET_WOOCOMMERCE_ORDER_STATUS_CANCELLED, ANET_WOOCOMMERCE_ORDER_STATUS_FAILED));
					// For early renewal orders that are rejected, add a flag.
					if ($is_afds_reject && $is_early_renewal && $accepted_via_modal) {
						$order->update_meta_data('_anet_afds_reject_early_renewal', 'yes');
						$order->save();
						// Add error message for rejected orders.
						if (function_exists('wc_add_notice')) {
							wc_add_notice(__('Unable to complete your order. Please check your details and try again.', 'authorize-net-payments-for-woocommerce'), 'error');
						}
					}
				}
				return $result;
			} catch (Throwable $e) {
				Anet_Setting_Helper::log(__CLASS__ . __FUNCTION__ . ANET_SPACE . $e->getMessage() . ANET_SPACE . 'for the auto renewal order ' . $order->get_id(), ANET_PAYMENT_TYPE_CREDIT_CARD, 'error');
				WC_Subscriptions_Manager::process_subscription_payment_failure_on_order($order);
			}
		}
	}
	/**
	 * Render payment method for subscriptions.
	 *
	 * @param string $payment_method_to_display Display method name.
	 * @param object $subscription subscription details.
	 *
	 * @return string @payment_method_to_display
	 */
	public function render_payment_method_for_subscriptions($payment_method_to_display, $subscription)
	{ // My account-subs orders page card name to display.
		// Bail for other payment methods.
		if ($subscription->get_payment_method('edit') === $this->id && !is_add_payment_method_page()) {
			$token = $this->get_order_meta($subscription, 'anet_subscription_token');
			$token = $this->get_wc_token($token, get_current_user_id());
			if ($token instanceof \WC_Payment_Token) {
				$payment_method_to_display = sprintf(
					/* translators: 1: Card brand/type, 2: Last 4 digits of the card number */
					__('%1$s card ending with %2$s', 'authorize-net-payments-for-woocommerce'),
					$payment_method_to_display,
					$token->get_meta('last4')
				);
			}
		}
		return $payment_method_to_display;
	}
	/**
	 * Get payment token subscriptions.
	 *
	 * @param array $token token.
	 *
	 * @return object $subscriptions
	 */
	protected function get_payment_token_subscriptions($token)
	{
		$subscriptions = wcs_get_users_subscriptions(get_current_user_id());
		foreach ($subscriptions as $key => $subscription) {
			$payment_method  = $subscription->get_payment_method('edit');
			$stored_token_id = $this->get_order_meta($subscription, 'anet_subscription_token');
			if ($stored_token_id !== (string) $token['method']['payment_profile_id'] || $payment_method !== $this->id) {
				unset($subscriptions[$key]);
			}
		}
		return $subscriptions;
	}
	/**
	 * Add payment method subscriptions.
	 *
	 * @param array $token token.
	 */
	public function add_payment_method_subscriptions($token)
	{ // In my account tokens subs orders.
		if (isset($token['method']['payment_profile_id']) && $token['method']['gateway'] === $this->id) {
			$subscription_ids = array();
			$subscriptions = $this->get_payment_token_subscriptions($token);
			// Build a link for each subscription.
			foreach ($subscriptions as $subscription) {
				/* translators: 1: URL to view the subscription order, 2: Subscription order number with hash prefix */
				$subscription_ids[] = sprintf('<a href="%1$s">%2$s</a>', esc_url($subscription->get_view_order_url()), esc_attr(sprintf(_x('#%s', 'hash before order number', 'authorize-net-payments-for-woocommerce'), $subscription->get_order_number())));
			}
			if (! empty($subscription_ids)) {
				$html = implode(', ', $subscription_ids);
			} else {
				$html = __('N/A', 'authorize-net-payments-for-woocommerce');
			}
			echo wp_kses_post($html);
		}
	}
	/**
	 * Admin add payment meta.
	 *
	 * @param array  $meta meta.
	 * @param object $subscription subscription details.
	 *
	 * @return array $meta
	 */
	public function admin_add_payment_meta($meta, $subscription)
	{ //  BO subscription creation.
		$meta[$this->id] = array(
			'post_meta' => array(
				$this->id . '_subscription_token' => array(
					'value' => $this->get_order_meta($subscription, 'anet_subscription_token'),
					'label' => __('Payment Token', 'authorize-net-payments-for-woocommerce'),
				)
			)
		);
		return $meta;
	}
	/**
	 * Add payment data to subscription.
	 *
	 * @param object $order order details.
	 */
	public function add_payment_data_to_subscription($order)
	{
		try {
			$subscriptions = wcs_get_subscriptions_for_order($order, [
				'order_type' => ['any'],
				'customer_id'  =>   $order->get_user_id(),
			]);
			foreach ($subscriptions as $subscription) {
				$updated = false;
				if (!empty($this->get_order_meta($order, 'anet_subscription_token'))) {
					if (!empty($this->get_order_meta($subscription, 'anet_subscription_token'))) {
						$this->update_order_meta($subscription, 'anet_subscription_token', $this->get_order_meta($order, 'anet_subscription_token'));
					} else {
						$this->add_order_meta($subscription, 'anet_subscription_token', $this->get_order_meta($order, 'anet_subscription_token'));
					}
					$updated = true;
				}
				if ($updated) {
					$subscription->save_meta_data();
				}
			}
		} catch (Throwable $e) {
			Anet_Setting_Helper::log(__CLASS__ . __FUNCTION__ . ANET_SPACE . $e->getMessage() . ANET_SPACE . 'Subscription token update failed for order' . ANET_SPACE . $order->get_id(), ANET_PAYMENT_TYPE_CREDIT_CARD, 'error');
		}
	}
	/**
	 * Update order subscription token.
	 *
	 * @param object $order Order details.
	 * @param string $payment_profile_id Payment profile for the subscription.
	 */
	public function update_order_subscription_token($order, $payment_profile_id)
	{
		if (!empty($this->get_order_meta($order, 'anet_subscription_token'))) {
			$this->update_order_meta($order, 'anet_subscription_token', $payment_profile_id);
		} else {
			$this->add_order_meta($order, 'anet_subscription_token', $payment_profile_id);
		}
		$order->save_meta_data();
	}
	/**
	 * Change payment method for a subscription order.
	 *
	 * @param string $payment_profile_id The payment profile ID to update.
	 * @param WC_Order $order The WooCommerce order object.
	 * @param array  $payment_args The payment arguments.
	 *
	 * @return array Response array containing result and redirect URL if successful.
	 */
	public function change_payment_method($payment_profile_id, $order, $payment_args)
	{
		$payment_method_updated = false;
		$subscriptions = array();
		$response_array = array(
			'result'  => 'failure',
		);
		try {
			if (WC_Subscriptions_Change_Payment_Gateway::$is_request_to_change_payment) {
				$error_message = esc_html__('An error occurred, please try again or try an alternate form of payment.', 'authorize-net-payments-for-woocommerce');
				if (!empty($payment_profile_id)) {
					$payment_method_updated = true;
				} elseif (!empty($payment_args)) {
					$user_id = get_current_user_id();
					$result = Anet_Credit_Card_Api::create_customer_profile($user_id, ['email' => get_user_meta($user_id, 'billing_email', true), 'billTo' => $payment_args['transactionRequest']['billTo'], 'opaqueData' => $payment_args['transactionRequest']['payment']['opaqueData']]);
					$anet_wc_gateway = new Anet_Credit_Card_Gateway();
					if (isset($result['result']) && $result['result'] && isset($result['customerPaymentProfileId'])) {
						$payment_method_updated = true;
						$payment_profile_id = $result['customerPaymentProfileId'];
					} elseif (!empty($result['message'])) {
						$error_message = $result['message'];
					}
				}
				$subscriptions = wcs_get_subscriptions_for_order($order->get_parent_id(), [
					'order_type' => ['any'],
					'customer_id'  =>  $order->get_user_id(),
				]);
				if ($payment_method_updated && !empty($payment_profile_id)) {
					$this->update_order_subscription_token($order, $payment_profile_id);
					$parent_order = wc_get_order($order->get_parent_id());
					if (!empty($parent_order)) {
						$this->update_order_subscription_token($parent_order, $payment_profile_id);
						$this->add_payment_data_to_subscription($parent_order);
					}
					Anet_Setting_Helper::log('Payment method updated for order' . ANET_SPACE . $order->get_id() . ' of subscription' . ANET_SPACE . wp_json_encode($subscriptions), ANET_PAYMENT_TYPE_CREDIT_CARD, 'info');
					$response_array = array(
						'result'   => 'success',
						'redirect' => $order->get_checkout_order_received_url()
					);
				} else {
					Anet_Setting_Helper::log('Payment method update failed for order' . ANET_SPACE . $order->get_id() . ' of subscription' . ANET_SPACE . wp_json_encode($subscriptions), ANET_PAYMENT_TYPE_CREDIT_CARD, 'error');
					wc_add_notice($error_message, 'error');
				}
			}
		} catch (Throwable $e) {
			Anet_Setting_Helper::log(__CLASS__ . __FUNCTION__ . ANET_SPACE . $e->getMessage() . ANET_SPACE . 'Payment method update failed for order' . ANET_SPACE . $order->get_id(), ANET_PAYMENT_TYPE_CREDIT_CARD, 'error');
		}
		return $response_array;
	}
	/**
	 * Remove order meta from change payment
	 *
	 * @param boolean $result result.
	 * @param object  $subscription subscription details.
	 *
	 * @return boolean $result
	 */
	public function remove_order_meta_from_change_payment($result, $subscription)
	{
		$subscription = is_numeric($subscription) ? wcs_get_subscription($subscription) : $subscription;
		if (! $subscription instanceof \WC_Subscription) {
			return $result;
		}
		$old_payment_method = $this->get_order_meta($subscription, '_old_payment_method');
		$new_payment_method = $subscription->get_payment_method('edit');
		$gateway_id         = $this->id;
		// If the payment method has been changed to another gateway, additionally remove the old payment token and customer ID meta.
		if ($new_payment_method !== $gateway_id && $old_payment_method === $gateway_id) {
			$this->delete_order_meta($subscription, 'anet_subscription_token');
			$subscription->save_meta_data();
		}
		return $result;
	}
	/**
	 * Admin validate payment meta.
	 *
	 * @param array $meta meta.
	 * @throws \Exception If the subscription token is missing.
	 */
	public function admin_validate_payment_meta($meta)
	{ // BO subs order creation validation.
		// Payment token.
		if (empty($meta['post_meta'][$this->id . '_subscription_token']['value'])) {
			throw new \Exception(sprintf(
				/* translators: %s: Field label that is required */
				esc_html__('%s is required.', 'authorize-net-payments-for-woocommerce'),
				esc_html($meta['post_meta'][$this->id . '_subscription_token']['label'])
			));
		}
	}
	/**
	 * Prevent AFDS review early renewal orders from being considered as "needs payment"
	 * This prevents the WooCommerce Subscriptions modal handler from deleting them
	 *
	 * @param bool $needs_payment Whether the order needs payment.
	 * @param WC_Order $order The order object.
	 * @return bool
	 */
	public function afds_review_early_renewal_needs_payment($needs_payment, $order)
	{
		// Only apply to our payment gateway.
		if ($order->get_payment_method() !== $this->id) {
			return $needs_payment;
		}
		// Check if this is an early renewal order.
		$is_early_renewal = $order->get_meta('_subscription_renewal_early');
		// Check for AFDS flags only.
		$has_afds_review_flag = 'yes' === $order->get_meta('_anet_afds_review_early_renewal');
		$has_afds_reject_flag = 'yes' === $order->get_meta('_anet_afds_reject_early_renewal');
		// Only process early renewal orders in modal context (not on checkout page).
		if ($is_early_renewal && ($has_afds_review_flag || $has_afds_reject_flag)) {
			return false; // Tell WooCommerce this order doesn't need payment for modal context.
		}
		// For all other contexts (including checkout), return normal needs_payment behavior.
		return $needs_payment;
	}
}
