<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase, WordPress.Files.FileName.InvalidClassFileName
/**
 * This class represents the Square payment integration.
 *
 * @package Autoship
 * @since 2.8.7
 */

namespace Autoship\Domain\PaymentIntegrations;

use Autoship\Domain\AbstractPaymentGateway;
use Autoship\Domain\PaymentMethodType;
use Autoship\Services\Logging\Logger;
use Exception;
use QPilotPaymentData;
use WC_Order;
use Autoship\Modules\Payments\PaymentUtilities;
use WC_Payment_Token;

/**
 * Represents the Square payment integration.
 *
 * @package Autoship
 * @since 2.8.7
 */
class SquarePaymentIntegration extends AbstractPaymentGateway {

	/**
	 * Initializing.
	 *
	 * @return void
	 */
	public function initialize(): void {
		if ( Logger::is_tracing_enabled() ) {
			Logger::log( 'Square Payment Integration', 'Initializing Square payment integration.' );
		}
	}

	/**
	 * The allowed payment gateways for this integration.
	 *
	 * @var array
	 */
	private static array $allowed = array(
		'square_credit_card',
	);

	/**
	 * Builds a Square payment integration.
	 *
	 * @param string $gateway_id The gateway ID.
	 * @param array  $settings The gateway settings.
	 * @return SquarePaymentIntegration
	 * @throws Exception When the gateway ID is not supported.
	 */
	public static function build( string $gateway_id, array $settings ): SquarePaymentIntegration {
		if ( ! in_array( $gateway_id, self::$allowed, true ) ) {
			throw new Exception( 'The current payment integration is not supported by this gateway.' );
		}

		// Square is not saving the authentication options in the normal settings, we retrieve the option information.
		$options = get_option( 'wc_square_settings', array() );

		$integration = new self();
		$integration->set_method_id( $gateway_id );
		$integration->set_method_type( PaymentMethodType::SQUARE );
		$integration->set_method_name( $settings['title'] ?? '' );
		$integration->set_authorize_only( false );

		$environment = 'yes' === ( $options['enable_sandbox'] ?? '' ) ? 'test' : 'live';

		// Production access tokens come from OAuth and are stored encrypted in the
		// wc_square_access_tokens option, not in wc_square_settings. The Settings
		// handler decrypts them and returns the right one for the current environment.
		$access_token = self::get_square_access_token( $environment, $options );

		if ( 'test' === $environment ) {
			$integration->set_api_key_1( $access_token );
			$integration->set_api_key_2( $options['sandbox_location_id'] ?? '' );
			$integration->set_test_mode( true );
		} else {
			$integration->set_api_key_1( $access_token );
			$integration->set_api_key_2( $options['production_location_id'] ?? '' );
			$integration->set_test_mode( false );
		}

		return $integration;
	}

	/**
	 * Resolve the Square access token for the requested environment.
	 *
	 * Prefers the Square Settings handler (which decrypts OAuth-issued tokens
	 * stored in `wc_square_access_tokens`). Falls back to the plain `sandbox_token`
	 * field for sandbox so a freshly-saved token works even before the cache
	 * primes.
	 *
	 * @param string $environment 'test' or 'live'.
	 * @param array  $options The wc_square_settings option contents.
	 * @return string The decrypted access token, or empty string if unavailable.
	 */
	private static function get_square_access_token( string $environment, array $options ): string {
		if ( function_exists( 'wc_square' ) ) {
			try {
				$handler = wc_square()->get_settings_handler();
				if ( $handler && $handler->get_environment() === ( 'test' === $environment ? 'sandbox' : 'production' ) ) {
					$token = (string) $handler->get_access_token();
					if ( ! empty( $token ) ) {
						return $token;
					}
				}
			} catch ( \Throwable $e ) {
				// Square plugin unavailable or its settings handler errored — fall through to the options fallback.
				unset( $e );
			}
		}

		// Sandbox fallback: the manual sandbox token field stores plain text.
		if ( 'test' === $environment ) {
			return (string) ( $options['sandbox_token'] ?? '' );
		}

		return '';
	}

	/**
	 * Gets the value indicating if the payment integration is valid or not.
	 *
	 * @return bool
	 */
	public function is_valid(): bool {
		if ( PaymentMethodType::SQUARE !== $this->get_method_type() ) {
			return false;
		}

		if ( ! in_array( $this->get_method_id(), self::$allowed, true ) ) {
			return false;
		}

		// SQUARE should contain the token and location id.
		if ( empty( $this->get_api_key_1() ) || empty( $this->get_api_key_2() ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Get order payment data for QPilot.
	 *
	 * @param int      $order_id The order ID.
	 * @param WC_Order $order The order object.
	 *
	 * @return ?QPilotPaymentData The payment data.
	 */
	public function get_order_payment_data( int $order_id, WC_Order $order ): ?QPilotPaymentData {
		$payment_method = $order->get_payment_method();

		if ( ! in_array( $payment_method, self::$allowed, true ) ) {
			return null;
		}

		$token_id    = $order->get_meta( '_wc_square_credit_card_payment_token' );
		$customer_id = $order->get_meta( '_wc_square_credit_card_customer_id' );

		if ( ! empty( $token_id ) ) {
			$payment_data = new QPilotPaymentData();
			$card_type    = $order->get_meta( '_wc_square_credit_card_card_type' );
			$last_four    = $order->get_meta( '_wc_square_credit_card_account_four' );
			$expiry_date  = $order->get_meta( '_wc_square_credit_card_card_expiry_date' );

			$expiry_date                       = explode( '-', $expiry_date );
			$expiry_date[0]                    = strlen( $expiry_date[0] ) > 2 ? substr( $expiry_date[0], - 2 ) : $expiry_date[0];
			$payment_data->description         = sprintf( '%s ending in %s (expires %s)', ucfirst( $card_type ), $last_four, $expiry_date[1] . '/' . $expiry_date[0] );
			$payment_data->type                = 'Square';
			$payment_data->gateway_payment_id  = $token_id;
			$payment_data->gateway_customer_id = $customer_id;
			$payment_data->last_four           = $last_four;

			// Get Expiration in MMYY format for Qpilot.
			$expiration               = $expiry_date[1] . $expiry_date[0];
			$payment_data->expiration = $expiration;

			return $payment_data;
		}

		return null;
	}

	/**
	 * Adds the Square credit card Payment Method to QPilot
	 * Does not use the woocommerce_payment_tokens tables
	 *
	 * @param array                                                    $result The result array.
	 * @param \SV_WC_Payment_Gateway_API_Create_Payment_Token_Response $response The API response object.
	 * @param \WC_Order                                                $order The WC Order object.
	 * @param \SV_WC_Payment_Gateway_Direct                            $client The Direct Gateway instance.
	 *
	 * @return mixed
	 * @see wc_payment_gateway_' . $this->get_id() . '_add_payment_method_transaction_result hook.
	 *
	 * Result: {
	 * @type string $message notice message to render
	 * @type bool $success true to redirect to my account, false to stay on page
	 * }
	 */
	public function add_payment_method_data( $result, $response, $order, $client ) {
		// Check if the transaction has been approved.
		if ( $response->transaction_approved() ) {
			$this->get_payment_method_service()->add_from_gateway_response( $response, $order, 'square_credit_card' );
		}

		return $result;
	}

	/**
	 * Add payment method data for QPilot.
	 *
	 * @param array            $payment_method_data The payment method data.
	 * @param string           $type The payment method type.
	 * @param WC_Payment_Token $token The payment token.
	 * @return array The modified payment method data.
	 */
	public function add_payment_method( array $payment_method_data, string $type, WC_Payment_Token $token ): array {
		$ext_type = 'square_credit_card';

		// Apply the test filters.
		$test_ext = apply_filters( 'autoship_payment_method_sandbox_metadata_field_test_ext', '_test', $ext_type );
		// ex. _wc_authorize_net_cim_credit_card_payment_tokens_test or _wc_authorize_net_cim_credit_card_payment_tokens.
		$meta_ext = apply_filters( 'autoship_payment_method_sandbox_metadata_field_ext', '', $test_ext, $ext_type );

		$user_id                                  = $token->get_user_id();
		$payment_method_data['gatewayCustomerId'] = get_user_meta( $user_id, 'wc_square_customer_id' . $meta_ext, true );

		return $payment_method_data;
	}

	/**
	 * Square_credit_card Gateway: Fires additional autoship actions after a payment method is saved.
	 *
	 * @param string                              $token_id new token ID.
	 * @param int                                 $user_id user ID.
	 * @param \SV_WC_Payment_Gateway_API_Response $response API response object.
	 */
	public function autoship_after_save_square_credit_card_payment_method_notice( $token_id, $user_id, $response ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
		PaymentUtilities::after_save_payment_method_notice( $token_id, 'square_credit_card', '' );
	}

	/**
	 * Square
	 * Non-Standard skyverge framework Gateways actions
	 * Outputs the apply action button after each payment method
	 *
	 * @param array                                     $list_item The list item array.
	 * @param \SV_WC_Payment_Gateway_Payment_Token      $payment_token The payment token object.
	 * @param \SV_WC_Payment_Gateway_My_Payment_Methods $instance The instance of the My Payment Methods class.
	 */
	public function autoship_display_apply_payment_method_to_all_scheduled_orders_square_btn( $list_item, $payment_token, $instance ) {
		return PaymentUtilities::display_skyverge_apply_btn( $list_item, $payment_token, $instance, 'square_credit_card' );
	}

	/**
	 * Determine whether a QPilot payment method matches a Square WC token being deleted.
	 *
	 * Square does not associate a gateway customer ID on QPilot's side, so the match
	 * is on `gatewayPaymentId` only. Hooked to `autoship_delete_Square_payment_method_qpilot_match`.
	 *
	 * @param bool             $valid Current match flag from upstream filters.
	 * @param string           $type The QPilot payment method type.
	 * @param WC_Payment_Token $token The WC payment token being deleted.
	 * @param object           $method The QPilot payment method.
	 * @return bool True if this QPilot method matches the token.
	 */
	public function delete_payment_method( bool $valid, string $type, WC_Payment_Token $token, object $method ): bool {
		return ( 'Square' === $type ) ? ( $method->gatewayPaymentId === $token->get_token() ) : $valid; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
	}

	/**
	 * Add metadata for Square.
	 *
	 * @param array    $payment_meta The payment metadata.
	 * @param WC_Order $order The order object.
	 * @param array    $payment_method The payment method data.
	 * @return array The payment metadata.
	 */
	public function add_metadata( array $payment_meta, WC_Order $order, array $payment_method ): array {
		$this->add_adjusted_gateway_metadata( $payment_meta, $order );

		return $payment_meta;
	}

	/**
	 * Add refund metadata for Square (internal implementation).
	 *
	 * @param array    $payment_meta The payment metadata.
	 * @param WC_Order $order The order object.
	 * @return void
	 */
	private function add_adjusted_gateway_metadata( array $payment_meta, WC_Order $order ): void {
		// Set the status for the transaction.
		$payment = $payment_meta['payment'];

		// Adjust Card Expiration.
		$exp  = substr( $payment['card_details']['card']['exp_year'], - 2 );
		$exp .= '-' . $payment['card_details']['card']['exp_month'];

		// Format the date.
		$date = autoship_get_datetime( $payment['created_at'] );

		$metadata = array(
			'_wc_square_credit_card_square_order_id'      => $payment['order_id'],
			'_wc_square_credit_card_square_location_id'   => $payment['location_id'],
			'_wc_square_credit_card_card_type'            => strtolower( $payment['card_details']['card']['card_brand'] ),
			'_wc_square_credit_card_card_expiry_date'     => $exp,
			'_wc_square_credit_card_charge_captured'      => 'yes',
			'_wc_square_credit_card_authorization_amount' => $payment['approved_money']['amount'],
			'_wc_square_credit_card_account_four'         => $payment['card_details']['card']['last_4'],
			'_wc_square_credit_card_customer_id'          => $payment['customer_id'],
			'_wc_square_credit_card_trans_date'           => $date->format( 'Y-m-d H:i:s' ),
			'_wc_square_credit_card_trans_id'             => $payment['id'],
			'_wc_square_credit_card_authorization_code'   => $payment['id'],
			'_wc_square_credit_card_square_version'       => WC_SQUARE_PLUGIN_VERSION,
		);

		$order->set_transaction_id( $payment['id'] );

		foreach ( $metadata as $key => $value ) {
			$order->update_meta_data( $key, $value );
		}

		$order->save();
	}

	/**
	 * Static helper method for Square force save compatibility.
	 *
	 * @param bool $val The current value.
	 * @return bool
	 */
	public static function force_save_source( bool $val ): bool {
		if ( autoship_cart_has_valid_autoship_items() ) {
			return true;
		}
		return $val;
	}

	/**
	 * Handle WooCommerce token deletion for Square tokens.
	 *
	 * Bridges `woocommerce_payment_token_deleted` to the QPilot deletion flow,
	 * since Square does not fire the Skyverge `*_payment_method_deleted` action
	 * and the generic tokenized deletion flow short-circuits because
	 * `square_credit_card` is not a "standard" gateway.
	 *
	 * @param int              $token_id The WC token ID (unused).
	 * @param WC_Payment_Token $token The WC token object being deleted.
	 * @return bool|\WP_Error True on success, false if not applicable / not found, WP_Error on failure.
	 */
	public function handle_token_deleted( $token_id, $token ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
		if ( ! $token instanceof WC_Payment_Token || ! in_array( $token->get_gateway_id(), self::$allowed, true ) ) {
			return false;
		}

		return $this->get_payment_method_service()->delete_non_wc_token(
			$token->get_token(),
			null,
			'Square',
			$token->get_user_id()
		);
	}
}
