<?php
/**
 * Abstract APM Gateway.
 *
 * @package flo-checkout
 *
 * @phpcs:disable Squiz.Commenting.FunctionComment.MissingParamTag
 */

namespace Flo_Checkout\Gateways\Shift4_APM;

use Exception;
use Flo_Checkout\Gateways\Helpers\Gateway_Logger_Trait;
use Flo_Checkout\Gateways\Helpers\Interface_Flo_WC_Payment_Gateway;
use Flo_Checkout\Gateways\Shift4_APM\Requests\Abstract_Request_Gateway_Payment;
use Flo_Checkout\Gateways\Shift4_APM\Responses\Response_Factory_Gateway;
use Flo_Checkout\Shared;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Data\Payment_Methods;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Data\Payment_Methods\Enums\Settings_Mode;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Helpers\HTTP_Request_Helpers;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Modules\Loggers\Log_Level_Enum;
use WC_Order;
use WC_Payment_Gateway;
use WP_Error;

defined( 'ABSPATH' ) || exit;

/**
 * Class Abstract_APM_Gateway
 */
abstract class Abstract_APM_Gateway extends WC_Payment_Gateway implements Interface_Flo_WC_Payment_Gateway {
	use Gateway_Logger_Trait;

	public const ORDER_META_KEY_TOKEN = '_wc_nm_shift4_apm_token';

	/**
	 * Environment mode.
	 *
	 * @var Settings_Mode
	 */
	public Settings_Mode $mode = Settings_Mode::PRODUCTION;

	/**
	 * Merchant ID.
	 *
	 * @var string
	 */
	public string $merchant_id;

	/**
	 * Sub-merchant ID.
	 *
	 * @var string
	 */
	public string $sub_merchant_id;

	/**
	 * API Secret.
	 *
	 * @var string
	 */
	public string $secret;

	/**
	 * Billing descriptor.
	 *
	 * @var string|null
	 */
	public ?string $billing_descriptor;

	/**
	 * Minimum order total.
	 *
	 * @var float
	 */
	public float $min_order_total;

	/**
	 * Debug mode.
	 *
	 * @var bool
	 */
	public bool $debug;

	/**
	 * Shared instance.
	 *
	 * @var Shared
	 */
	protected Shared $shared;

	/**
	 * The response factory.
	 *
	 * @var Response_Factory_Gateway
	 */
	public Response_Factory_Gateway $response_factory;

	/**
	 * Is sandbox mode.
	 *
	 * @var bool
	 */
	public bool $is_integration_mode = false;

	/**
	 * {@inheritDoc}
	 */
	public static function public_setup(): void {}

	/**
	 * Abstract_APM_Gateway constructor.
	 */
	public function __construct() {
		$this->shared = Shared::instance();

		$this->has_fields          = true;
		$this->mode                = Settings_Mode::tryFrom( $this->get_option( 'mode' ) ) ?? Settings_Mode::PRODUCTION;
		$this->is_integration_mode = Settings_Mode::INTEGRATION === $this->mode;
		$this->merchant_id         = $this->is_integration_mode ? $this->get_option( 'integration_merchant_id' ) : $this->get_option( 'merchant_id' );
		$this->sub_merchant_id     = $this->is_integration_mode ? $this->get_option( 'integration_sub_merchant_id' ) : $this->get_option( 'sub_merchant_id' );
		$this->secret              = $this->is_integration_mode ? $this->get_option( 'integration_secret' ) : $this->get_option( 'secret' );
		$this->billing_descriptor  = $this->get_option( 'billing_descriptor' );
		$this->min_order_total     = (float) $this->get_option( 'min_order_total' );
		$this->max_amount          = (float) $this->get_option( 'max_order_total' );
		$this->debug               = $this->get_option( 'log_enabled' );
		$this->response_factory    = new Response_Factory_Gateway( $this );

		$this->supports[] = 'refunds';

		$this->method_title       = Payment_Methods::get_method_title( $this->id );
		$this->method_description = Payment_Methods::get_method_description( $this->id );

		$icon_path = Payment_Methods::get_icon( $this->id );
		if ( ! empty( $icon_path ) ) {
			$this->icon = $icon_path;
		}

		$options_title = $this->get_option( 'title' );
		$this->title   = $options_title ? $options_title : Payment_Methods::get_default_title( $this->id );

		add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );

		// Load the form fields.
		$this->init_form_fields();

		// Load the settings.
		$this->init_settings();
	}

	/**
	 * Get the payment method as named in APM API.
	 *
	 * @return string
	 */
	abstract public function get_apm_payment_method(): string;

	/**
	 * Get the payment data form field name.
	 * Field name is referenced statically in Express Order.
	 *
	 * @return string
	 */
	protected function get_payment_data_form_field_name(): string {
		return esc_attr( $this->id . '_payment_data' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function payment_fields(): void {
		esc_html_e( 'Powered by Northmill', 'flo-checkout-plugin' );

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Field already escaped for attribute.
		echo '<input type="hidden" name="' . $this->get_payment_data_form_field_name() . '" value="">';
	}

	/**
	 * Get whether the gateway is in integration mode.
	 *
	 * @return bool
	 */
	public function is_integration_mode(): bool {
		return $this->is_integration_mode;
	}

	/**
	 * {@inheritDoc}
	 */
	public function needs_setup(): bool {
		return empty( $this->merchant_id ) ||
				empty( $this->secret );
	}

	/**
	 * Check if the gateway is available for use.
	 *
	 * @return bool
	 */
	public function is_available(): bool {
		$is_available = parent::is_available();

		// Max total is handled in parent.
		if (
			WC()->cart &&
			0 < $this->get_order_total() &&
			0 < $this->min_order_total &&
			$this->min_order_total > $this->get_order_total()
		) {
			$is_available = false;
		}

		return $is_available;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.Missing -- Exceptions are caught.
	 */
	public function process_payment( $order_id ): array {
		$order   = false;
		$request = false;

		try {
			$order = wc_get_order( $order_id );
			if ( ! ( $order && $order->get_id() ) ) {
				throw new Exception(
					"Couldn't load order ID: " . $order_id
				);
			}

			$payment_data = HTTP_Request_Helpers::sanitize_post_key( $this->get_payment_data_form_field_name() );
			$payment_data = $this->process_payment_data( $payment_data );

			$order->update_meta_data( self::ORDER_META_KEY_TOKEN, $payment_data );
			$order->save();

			$request = $this->get_payment_request_handler( $this, $order );

			$request->process();
			$order->payment_complete();

			WC()->cart->empty_cart();

			// Redirect to the thank-you-page.
			return array(
				'result'   => 'success',
				'redirect' => $this->get_return_url( $order ),
			);

		} catch ( Exception $e ) {
			try {
				if ( $order && $order->get_id() ) {
					$request_id = $request ? $request->get_request_id() : null;
					$order->add_order_note(
						sprintf(
							// translators: %s: payment method title.
							__( 'Failed to process %s payment.', 'flo-checkout-plugin' ),
							$this->method_title
						) . '<br>' .
						__( 'Request ID', 'flo-checkout' ) . ': ' . $request_id . '<br>' .
						__( 'Error', 'flo-checkout' ) . ': ' . $e->getMessage()
					);
					$order->update_status( 'failed' );
					$order->save();
				}
			} catch ( Exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
			}

			wc_add_notice( esc_html( $e->getMessage() ), 'error' );
			$this->log(
				'Shift4 APM Processing Exception',
				array(
					'payment_method' => $this->method_title,
					'error'          => $e->getMessage(),
					'code'           => $e->getCode(),
					'file'           => $e->getFile(),
					'line'           => $e->getLine(),
					'trace'          => $e->getTrace(),
				),
				Log_Level_Enum::ERROR
			);

			return array(
				'result'   => 'failure',
				'messages' => $e->getMessage(),
				'redirect' => '',
			);
		}
	}

	/**
	 * Validate and process payment data.
	 * Should throw an exception if data is invalid.
	 *
	 * @param mixed $payment_data Payment data.
	 *
	 * @return mixed Valid payment data.
	 */
	abstract protected function process_payment_data( mixed $payment_data ): mixed;

	/**
	 * Get the request handler.
	 *
	 * @param Abstract_APM_Gateway $gateway The gateway instance.
	 * @param WC_Order|null        $order   Order object.
	 *
	 * @return Abstract_Request_Gateway_Payment
	 */
	abstract protected function get_payment_request_handler(
		Abstract_APM_Gateway $gateway,
		?WC_Order $order = null
	): Abstract_Request_Gateway_Payment;

	/**
	 * {@inheritDoc}
	 */
	public function process_refund( $order_id, $amount = null, $reason = '' ): WP_Error|bool {
		$order = false;
		try {
			$order = wc_get_order( $order_id );

			if ( ! $this->can_refund_order( $order ) ) {
				return false;
			}

			$request = $this->get_refund_request_handler( $this, $order, $amount );
			$request->process();

		} catch ( Exception $e ) {
			try {
				if ( $order && $order->get_id() ) {
					$order->add_order_note(
						sprintf(
							// translators: %s: payment method title.
							__( 'Failed to process %s refund.', 'flo-checkout-plugin' ),
							$this->method_title
						) . '<br>' .
						__( 'Error', 'flo-checkout' ) . ': ' . $e->getMessage()
					);
					$order->save();
				}
			} catch ( Exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
			}

			$this->log(
				'Shift4 APM Refund Exception',
				array(
					'payment_method' => $this->method_title,
					'error'          => $e->getMessage(),
					'trace'          => $e->getTrace(),
				),
				Log_Level_Enum::ERROR
			);

			return new WP_Error(
				'shift4_apm_gateway_process_refund_error',
				$this->method_title . ' Refund Exception: ' . $e->getMessage()
			);
		}

		return true;
	}

	/**
	 * Get the request handler.
	 *
	 * @param Abstract_APM_Gateway $gateway The gateway instance.
	 * @param WC_Order|null        $order   Order object.
	 * @param float|null           $amount  Refund amount.
	 *
	 * @return Abstract_Request_Gateway_Payment
	 */
	abstract protected function get_refund_request_handler(
		Abstract_APM_Gateway $gateway,
		?WC_Order $order = null,
		?float $amount = null
	): Abstract_Request_Gateway_Payment;

	/**
	 * Triggered when payment method is enabled.
	 *
	 * @return void
	 */
	public function on_enable(): void {
		Health_Check::update_cron();
	}

	/**
	 * Triggered when payment method is enabled.
	 *
	 * @return void
	 */
	public function on_disable(): void {
		Health_Check::update_cron();
	}
}
