<?php
/**
 * Abstract Payment Gateway Base Class
 *
 * Provides the foundation for all payment gateway implementations.
 *
 * @package GGM_Commerce
 */

// If this file is called directly, abort.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter

/**
 * Abstract class GGM_Commerce_Payment_Gateway
 *
 * Base class that all payment gateways must extend.
 */
abstract class GGM_Commerce_Payment_Gateway {

	/**
	 * Gateway unique identifier.
	 *
	 * @var string
	 */
	protected $id = '';

	/**
	 * Gateway title for display.
	 *
	 * @var string
	 */
	protected $title = '';

	/**
	 * Gateway description.
	 *
	 * @var string
	 */
	protected $description = '';

	/**
	 * Premium component slug for license check.
	 *
	 * @var string
	 */
	protected $premium_component_slug = '';

	/**
	 * Gateway settings from database.
	 *
	 * @var array
	 */
	protected $settings = array();

	/**
	 * Supported features.
	 *
	 * @var array
	 */
	protected $supports = array();

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->load_settings();
	}

	/**
	 * Get gateway unique identifier.
	 *
	 * @return string
	 */
	public function get_id() {
		return $this->id;
	}

	/**
	 * Get gateway title.
	 *
	 * @return string
	 */
	public function get_title() {
		return $this->title;
	}

	/**
	 * Get gateway description.
	 *
	 * @return string
	 */
	public function get_description() {
		return $this->description;
	}

	/**
	 * Get gateway icon URL.
	 *
	 * @return string
	 */
	abstract public function get_icon();

	/**
	 * Get settings fields for admin configuration.
	 *
	 * @return array
	 */
	abstract public function get_settings_fields();

	/**
	 * Process payment for an order.
	 *
	 * @param int   $order_id     The order ID.
	 * @param array $payment_data Payment data from frontend.
	 * @return array Result with success/error status.
	 */
	abstract public function process_payment( $order_id, $payment_data );

	/**
	 * Handle webhook callback from payment provider.
	 *
	 * @return void
	 */
	abstract public function handle_webhook();

	/**
	 * Render payment fields for checkout form.
	 *
	 * @return void
	 */
	abstract public function render_payment_fields();

	/**
	 * Get JavaScript dependencies for this gateway.
	 *
	 * @return array Array of script handles and URLs.
	 */
	abstract public function get_scripts();

	/**
	 * Load settings from database.
	 *
	 * @return void
	 */
	protected function load_settings() {
		$option_name    = 'ggm_commerce_gateway_' . $this->id;
		$this->settings = get_option( $option_name, array() );
	}

	/**
	 * Save settings to database.
	 *
	 * @param array $settings Settings to save.
	 * @return bool
	 */
	public function save_settings( $settings ) {
		$option_name    = 'ggm_commerce_gateway_' . $this->id;
		$this->settings = $settings;
		return update_option( $option_name, $settings );
	}

	/**
	 * Get a single option value.
	 *
	 * @param string $key     Option key.
	 * @param mixed  $default Default value.
	 * @return mixed
	 */
	public function get_option( $key, $default = '' ) {
		return isset( $this->settings[ $key ] ) ? $this->settings[ $key ] : $default;
	}

	/**
	 * Update a single option value.
	 *
	 * @param string $key   Option key.
	 * @param mixed  $value Option value.
	 * @return bool
	 */
	public function update_option( $key, $value ) {
		$this->settings[ $key ] = $value;
		return $this->save_settings( $this->settings );
	}

	/**
	 * Check if gateway is enabled in settings.
	 *
	 * @return bool
	 */
	public function is_enabled() {
		// Reload settings from database to ensure freshness.
		$this->load_settings();
		return 'yes' === $this->get_option( 'enabled', 'no' );
	}

	/**
	 * Check if gateway is in test mode.
	 *
	 * @return bool
	 */
	public function is_test_mode() {
		return 'yes' === $this->get_option( 'test_mode', 'yes' );
	}

	/**
	 * Check if gateway is properly configured with API keys.
	 *
	 * @return bool
	 */
	abstract public function is_configured();

	/**
	 * Check if the premium component is activated.
	 *
	 * @return bool
	 */
	public function is_premium_activated() {
		if ( empty( $this->premium_component_slug ) ) {
			return false;
		}

		$license = ggm_commerce_license();
		return $license->is_widget_activated( $this->premium_component_slug );
	}

	/**
	 * Check if gateway is available for use.
	 * Must be enabled, configured, and premium activated.
	 *
	 * @return bool
	 */
	public function is_available() {
		$enabled   = $this->is_enabled();
		$configured = $this->is_configured();
		$activated = $this->is_premium_activated();

		return $enabled && $configured && $activated;
	}

	/**
	 * Check if gateway supports a feature.
	 *
	 * @param string $feature Feature name.
	 * @return bool
	 */
	public function supports( $feature ) {
		return in_array( $feature, $this->supports, true );
	}

	/**
	 * Get the API key (test or live based on mode).
	 *
	 * @param string $key_type The key type (e.g., 'secret_key', 'publishable_key').
	 * @return string
	 */
	protected function get_api_key( $key_type ) {
		$prefix = $this->is_test_mode() ? 'test_' : 'live_';
		$key    = $this->get_option( $prefix . $key_type, '' );

		// Decrypt if stored encrypted.
		if ( ! empty( $key ) && strpos( $key_type, 'secret' ) !== false ) {
			$key = $this->decrypt_key( $key );
		}

		return $key;
	}

	/**
	 * Set an API key (encrypts secret keys).
	 *
	 * @param string $key_type The key type.
	 * @param string $value    The key value.
	 * @return void
	 */
	protected function set_api_key( $key_type, $value ) {
		// Encrypt secret keys.
		if ( strpos( $key_type, 'secret' ) !== false && ! empty( $value ) ) {
			$value = $this->encrypt_key( $value );
		}

		$prefix = $this->is_test_mode() ? 'test_' : 'live_';
		$this->update_option( $prefix . $key_type, $value );
	}

	/**
	 * Encrypt a sensitive key.
	 *
	 * @param string $key The key to encrypt.
	 * @return string
	 */
	protected function encrypt_key( $key ) {
		if ( ! defined( 'AUTH_KEY' ) || ! defined( 'AUTH_SALT' ) ) {
			return $key;
		}

		$iv = substr( AUTH_SALT, 0, 16 );
		if ( strlen( $iv ) < 16 ) {
			$iv = str_pad( $iv, 16, '0' );
		}

		$encrypted = openssl_encrypt( $key, 'AES-256-CBC', AUTH_KEY, 0, $iv );
		return $encrypted ? base64_encode( $encrypted ) : $key;
	}

	/**
	 * Decrypt a sensitive key.
	 *
	 * Supports both legacy format and new 'enc:' prefixed format.
	 *
	 * @param string $encrypted_key The encrypted key.
	 * @return string
	 */
	protected function decrypt_key( $encrypted_key ) {
		if ( empty( $encrypted_key ) ) {
			return '';
		}

		if ( ! defined( 'AUTH_KEY' ) || ! defined( 'AUTH_SALT' ) ) {
			return $encrypted_key;
		}

		// Check for 'enc:' prefix (new format from admin settings).
		if ( strpos( $encrypted_key, 'enc:' ) === 0 ) {
			$encoded = substr( $encrypted_key, 4 );
			$decoded = base64_decode( $encoded );
		} else {
			// Legacy format - just base64 decode.
			$decoded = base64_decode( $encrypted_key );
		}

		if ( false === $decoded ) {
			return $encrypted_key;
		}

		$iv = substr( AUTH_SALT, 0, 16 );
		if ( strlen( $iv ) < 16 ) {
			$iv = str_pad( $iv, 16, '0' );
		}

		$decrypted = openssl_decrypt( $decoded, 'AES-256-CBC', AUTH_KEY, 0, $iv );

		return $decrypted ? $decrypted : $encrypted_key;
	}

	/**
	 * Log a payment event.
	 *
	 * @param int    $order_id       Order ID.
	 * @param string $transaction_id Transaction ID from gateway.
	 * @param string $event_type     Type of event (e.g., 'payment_intent.succeeded').
	 * @param float  $amount         Payment amount.
	 * @param string $currency       Currency code.
	 * @param string $status         Payment status.
	 * @param mixed  $raw_response   Raw response from gateway.
	 * @return int|false Log entry ID or false on failure.
	 */
	protected function log_payment_event( $order_id, $transaction_id, $event_type, $amount, $currency, $status, $raw_response = null ) {
		global $wpdb;

		$data = array(
			'order_id'       => $order_id,
			'gateway'        => $this->id,
			'transaction_id' => $transaction_id,
			'event_type'     => $event_type,
			'amount'         => $amount,
			'currency'       => $currency,
			'status'         => $status,
			'raw_response'   => is_array( $raw_response ) || is_object( $raw_response )
				? wp_json_encode( $raw_response )
				: $raw_response,
			'created_at'     => current_time( 'mysql' ),
		);

		$result = $wpdb->insert( "{$wpdb->prefix}ggmc_payment_logs", $data );

		return $result ? $wpdb->insert_id : false;
	}

	/**
	 * Update order payment status.
	 *
	 * @param int    $order_id       Order ID.
	 * @param string $status         New payment status ('paid' for success, 'failed' for failure).
	 * @param string $transaction_id Transaction ID.
	 * @param string $error_message  Error message if failed.
	 * @return bool
	 */
	protected function update_order_payment_status( $order_id, $status, $transaction_id = '', $error_message = '' ) {
		global $wpdb;

		// Guard: Don't process if order_id is invalid.
		if ( ! $order_id || $order_id <= 0 ) {
			return false;
		}

		$data = array(
			'payment_status' => $status,
			'payment_method' => $this->id,
			'updated_at'     => current_time( 'mysql' ),
		);

		if ( ! empty( $transaction_id ) ) {
			$data['payment_transaction_id'] = $transaction_id;
		}

		if ( ! empty( $error_message ) ) {
			$data['payment_error_message'] = $error_message;
		}

		// Determine order status based on payment status.
		$order_status = null;
		if ( 'paid' === $status ) {
			$order_status = 'processing';
			$data['status'] = $order_status;
		} elseif ( 'failed' === $status ) {
			$order_status = 'failed';
			$data['status'] = $order_status;
		}

		$result = $wpdb->update(
			"{$wpdb->prefix}ggmc_orders",
			$data,
			array( 'id' => $order_id )
		);

		// Fire hooks after successful update.
		if ( false !== $result && null !== $order_status ) {
			// Fire general status change hook.
			do_action( 'ggm_commerce_order_status_changed', $order_id, $order_status, $status );

			// Fire specific status hook for downloads/virtual products.
			do_action( 'ggmc_order_status_' . $order_status, $order_id );

			// When payment succeeds, activate any linked subscriptions immediately.
			if ( 'processing' === $order_status && class_exists( 'GGM_Commerce_Subscriptions' ) && GGM_Commerce_Subscriptions::is_activated() ) {
				$sub_manager = GGM_Commerce_Subscriptions::instance();
				$linked_subs = $wpdb->get_results( $wpdb->prepare(
					"SELECT id, status FROM {$wpdb->prefix}ggmc_subscriptions WHERE parent_order_id = %d",
					$order_id
				) );

				foreach ( $linked_subs as $sub ) {
					if ( in_array( $sub->status, array( 'pending', 'trial' ), true ) ) {
						$sub_manager->activate_subscription( $sub->id );
					}
				}
			}

			// For digital-only orders, auto-complete when payment succeeds.
			if ( 'processing' === $order_status && class_exists( 'GGM_Commerce_Virtual_Products' ) ) {
				$virtual_helper = GGM_Commerce_Virtual_Products::instance();
				$is_all_digital = $virtual_helper->is_all_digital_order( $order_id );

				if ( $is_all_digital ) {
					// Update to completed status.
					$wpdb->update(
						"{$wpdb->prefix}ggmc_orders",
						array(
							'status'     => 'completed',
							'updated_at' => current_time( 'mysql' ),
						),
						array( 'id' => $order_id )
					);

					// Fire completed hooks.
					do_action( 'ggm_commerce_order_status_changed', $order_id, 'completed', $status );
					do_action( 'ggmc_order_status_completed', $order_id );
					do_action( 'ggmc_order_auto_completed', $order_id );
				}
			}
		}

		return false !== $result;
	}

	/**
	 * Get order data for payment processing.
	 *
	 * @param int $order_id Order ID.
	 * @return object|null Order object or null if not found.
	 */
	protected function get_order( $order_id ) {
		global $wpdb;

		return $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}ggmc_orders WHERE id = %d",
				$order_id
			)
		);
	}

	/**
	 * Get currency code from settings.
	 *
	 * @return string
	 */
	protected function get_currency() {
		$currency_options = get_option( 'ggm_commerce_currency_options', array() );
		return isset( $currency_options['currency_code'] ) ? $currency_options['currency_code'] : 'USD';
	}

	/**
	 * Convert amount to smallest currency unit (e.g., cents).
	 *
	 * @param float  $amount   The amount.
	 * @param string $currency Currency code.
	 * @return int
	 */
	protected function get_amount_in_cents( $amount, $currency = null ) {
		if ( null === $currency ) {
			$currency = $this->get_currency();
		}

		// Zero-decimal currencies.
		$zero_decimal_currencies = array( 'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF' );

		if ( in_array( strtoupper( $currency ), $zero_decimal_currencies, true ) ) {
			return (int) round( $amount );
		}

		return (int) round( $amount * 100 );
	}

	/**
	 * Format error response.
	 *
	 * @param string $message Error message.
	 * @param string $code    Error code.
	 * @return array
	 */
	protected function error_response( $message, $code = 'payment_error' ) {
		return array(
			'success' => false,
			'error'   => array(
				'message' => $message,
				'code'    => $code,
			),
		);
	}

	/**
	 * Format success response.
	 *
	 * @param array $data Additional response data.
	 * @return array
	 */
	protected function success_response( $data = array() ) {
		return array_merge(
			array( 'success' => true ),
			$data
		);
	}

	/**
	 * Verify webhook signature - must be implemented by child classes.
	 *
	 * @param string $payload    Raw webhook payload.
	 * @param string $signature  Signature header.
	 * @return bool
	 */
	protected function verify_webhook_signature( $payload, $signature ) {
		return false;
	}
}
