<?php

namespace MPHB\Payments\Gateways\Stripe;

use MPHB\Payments\Gateways\StripeGateway;
use MPHB\Stripe\Exception\ApiErrorException;
use MPHB\Stripe\{ BalanceTransaction, PaymentIntent, Stripe };

/**
 * https://github.com/stripe/stripe-php
 * https://docs.stripe.com/payments/payment-intents
 */
class StripeAPI {
	// https://docs.stripe.com/upgrades#api-versions (Breaking changes)
	// https://docs.stripe.com/changelog#2025-06-30.basil (Version changelog)
	public const API_VERSION = '2025-06-30.basil';

	private const PARTNER_ID = 'pp_partner_Fs0jSMbknaJwVC';

	private static bool $isLibraryLoaded = false;

	private StripeGateway $gateway;

	public function __construct( StripeGateway $stipeGateway ) {
		$this->gateway = $stipeGateway;
	}

	/**
	 * @throws ApiErrorException if the request fails.
	 */
	public function cancelPaymentIntent( PaymentIntent $paymentIntent ): void {
		$this->setApp();

		$paymentIntent->cancel();
	}

	/**
	 * @return float|false Captured amount or false.
	 *
	 * @throws ApiErrorException if requests fail.
	 */
	public function capturePaymentIntent( PaymentIntent $paymentIntent, ?float $amount = null ) {
		if ( $paymentIntent->status !== 'requires_capture' ) {
			return false;
		}

		$currency = strtoupper( $paymentIntent->currency );

		if ( is_null( $amount ) ) {
			$amount = $this->convertFromSmallestUnit( $paymentIntent->amount_capturable, $currency );
		}

		$this->setApp();

		$paymentIntent->capture(
			array(
				'amount_to_capture' => $this->convertToSmallestUnit( $amount, $currency ),
			)
		);

		if ( $paymentIntent->status === 'succeeded' ) {
			return $amount;
		} else {
			return false;
		}
	}

	/**
	 * Checks Stripe minimum amount value authorized per currency.
	 */
	public function checkMinimumAmount( float $amount, string $currency ): bool {
		return $amount >= $this->getMinimumAmount( $currency );
	}

	public function convertFromSmallestUnit( $amount, ?string $currency = null ): float {
		if ( is_null( $currency ) ) {
			$currency = MPHB()->settings()->currency()->getCurrencyCode();
		}

		// See all currencies (presented as links):
		//     https://docs.stripe.com/currencies#presentment-currencies
		switch ( strtoupper( $currency ) ) {
			// Zero decimal currencies
			case 'BIF':
			case 'CLP':
			case 'DJF':
			case 'GNF':
			case 'JPY':
			case 'KMF':
			case 'KRW':
			case 'MGA':
			case 'PYG':
			case 'RWF':
			case 'UGX':
			case 'VND':
			case 'VUV':
			case 'XAF':
			case 'XOF':
			case 'XPF':
				return $amount;

			default:
				return $amount / 100; // With cents
		}
	}

	/**
	 * See also StripeGateway._convertToSmallestUnit() in stripe-gateway.js.
	 */
	public function convertToSmallestUnit( float $amount, ?string $currency = null ): int {
		if ( is_null( $currency ) ) {
			$currency = MPHB()->settings()->currency()->getCurrencyCode();
		}

		// See all currencies (presented as links):
		//     https://docs.stripe.com/currencies#presentment-currencies
		switch ( strtoupper( $currency ) ) {
			// Zero decimal currencies
			case 'BIF':
			case 'CLP':
			case 'DJF':
			case 'GNF':
			case 'JPY':
			case 'KMF':
			case 'KRW':
			case 'MGA':
			case 'PYG':
			case 'RWF':
			case 'UGX':
			case 'VND':
			case 'VUV':
			case 'XAF':
			case 'XOF':
			case 'XPF':
				return absint( $amount );

			default:
				return absint( round( $amount * 100 ) ); // In cents
		}
	}

	/**
	 * @throws ApiErrorException if the request fails.
	 * @throws \Exception if the amount is less than the minimum one.
	 */
	public function createPaymentIntentByConfirmationToken(
		string $confirmationTokenId,
		float $amount,
		string $currency,
		string $description = '',
		string $receiptEmail = '',
		?array $options = null
	) {
		// Check minimum amount
		if ( ! $this->checkMinimumAmount( $amount, $currency ) ) {
			throw new \Exception(
				sprintf(
					// Translators: %s: Minimum allowed payment amount.
					esc_html__( 'Sorry, the minimum allowed payment amount is %s to use this payment method.', 'motopress-hotel-booking' ),
					mphb_format_price( $this->getMinimumAmount( $currency ) )
				)
			);
		}

		// Create PaymentIntent
		$params = array(
			'amount'                       => $this->convertToSmallestUnit( $amount, $currency ),
			'currency'                     => strtolower( $currency ),

			// Args for confirmation token
			'confirm'                      => true,
			'confirmation_token'           => $confirmationTokenId,

			// Additional args for dynamic payments
			'automatic_payment_methods'    => array( 'enabled' => true ),
			'payment_method_configuration' => $this->gateway->getPaymentsConfigurationId(),
		);

		if ( $description !== '' ) {
			$params['description'] = $description;
		}

		if ( $this->gateway->isManualCapture() ) {
			$params['capture_method'] = 'manual';
		}

		if ( $receiptEmail !== '' ) {
			$params['receipt_email'] = $receiptEmail;
		}

		$this->setApp();

		/**
		 * https://docs.stripe.com/api/payment_intents/create
		 *
		 * @throws ApiErrorException
		 */
		return PaymentIntent::create( $params, $options );
	}

	/**
	 * @throws ApiErrorException if the request fails.
	 * @throws \Exception if the amount is less than the minimum one.
	 */
	public function createPaymentIntentForLegacyMethods(
		string $paymentMethodType,
		string $paymentMethodId,
		float $amount,
		string $currency,
		string $description = '',
		string $receiptEmail = '',
		?array $options = null
	): PaymentIntent {
		// Check minimum amount
		if ( ! $this->checkMinimumAmount( $amount, $currency ) ) {
			throw new \Exception(
				sprintf(
					// Translators: %s: Minimum allowed payment amount.
					esc_html__( 'Sorry, the minimum allowed payment amount is %s to use this payment method.', 'motopress-hotel-booking' ),
					mphb_format_price( $this->getMinimumAmount( $currency ) )
				)
			);
		}

		// Create PaymentIntent
		$requestArgs = array(
			'amount'               => $this->convertToSmallestUnit( $amount, $currency ),
			'currency'             => strtolower( $currency ),
			'payment_method'       => $paymentMethodId,
			'payment_method_types' => array( $paymentMethodType ),
		);

		if ( $this->gateway->isManualCapture() ) {
			$requestArgs['capture_method'] = 'manual';
		}

		if ( ! empty( $description ) ) {
			$requestArgs['description'] = $description;
		}

		if ( $receiptEmail !== '' ) {
			$requestArgs['receipt_email'] = $receiptEmail;
		}

		$checkoutLocale = $this->gateway->getCheckoutLocale();

		if ( in_array( 'bancontact', $this->gateway->getAllowedPaymentMethods() )
			&& in_array( $checkoutLocale, array( 'de', 'en', 'fr', 'nl' ) )
		) {
			$requestArgs['payment_method_options']['bancontact']['preferred_language'] = $checkoutLocale;
		}

		$this->setApp();

		/**
		 * https://docs.stripe.com/api/payment_intents/create
		 *
		 * @throws ApiErrorException
		 */
		return PaymentIntent::create( $requestArgs, $options );
	}

	public function getMinimumAmount( string $currency ): float {
		// https://docs.stripe.com/currencies#minimum-and-maximum-charge-amounts
		switch ( strtoupper( $currency ) ) {
			case 'GBP':
				return 0.30;

			case 'AUD':
			case 'BRL':
			case 'CAD':
			case 'CHF':
			case 'EUR':
			case 'INR':
			case 'NZD':
			case 'SGD':
			case 'USD':
				return 0.50;

			case 'BGN':
				return 1.00;

			case 'MYR':
				return 2;

			case 'AED':
			case 'PLN':
			case 'RON':
				return 2.00;

			case 'DKK':
				return 2.50;

			case 'NOK':
			case 'SEK':
				return 3.00;

			case 'HKD':
				return 4.00;

			case 'MXN':
			case 'THB':
				return 10;

			case 'CZK':
				return 15.00;

			case 'JPY':
				return 50;

			case 'HUF':
				return 175.00;

			default:
				return 0.50;
		}
	}

	/**
	 * @throws ApiErrorException if the request fails.
	 */
	public function retrieveBalanceTransaction( $transactionId ): BalanceTransaction {
		$this->setApp();

		return BalanceTransaction::retrieve( $transactionId );
	}

	/**
	 * @throws ApiErrorException if the request fails.
	 */
	public function retrievePaymentIntent( $paymentIntentId ): PaymentIntent {
		$this->setApp();

		return PaymentIntent::retrieve( $paymentIntentId );
	}

	public function setApp(): void {
		if ( ! self::$isLibraryLoaded ) {
			$this->registerCustomLoader();

			Stripe::setAppInfo(
				MPHB()->getName(),
				MPHB()->getVersion(),
				MPHB()->getPluginStoreUri(),
				self::PARTNER_ID
			);

			Stripe::setApiKey( $this->gateway->getSecretKey() );
			Stripe::setApiVersion( self::API_VERSION );

			self::$isLibraryLoaded = true;
		}
	}

	/**
	 * @return true|\WP_Error
	 */
	public function updateDescription( PaymentIntent $paymentIntent, string $description ) {
		$this->setApp();

		try {
			$paymentIntent->update( $paymentIntent->id, array( 'description' => $description ) );
		} catch ( \Exception $e ) {
			return new \WP_Error( 'stripe_api_error', $e->getMessage() );
		}
	}

	private function registerCustomLoader(): void {
		// Use autoloader instead of requiring all 385 files each time
		spl_autoload_register(
			function ( string $className ): void {
				if ( strpos( $className, 'MPHB\Stripe' ) !== 0 ) {
					return;
				}

				// "MPHB\Stripe\Checkout\Session" → ".../vendors/stripe-php/lib/Checkout/Session.php"
				$libraryFile = str_replace( 'MPHB\Stripe', 'lib', $className );
				$libraryFile = str_replace( '\\', DIRECTORY_SEPARATOR, $libraryFile );
				$libraryFile = MPHB()->getPluginPath( 'vendors/stripe-php/' ) . $libraryFile;
				$libraryFile .= '.php';

				require $libraryFile;
			}
		);
	}
}
