<?php
namespace KnitPayUPI\Gateways\UpiQR;

use Pronamic\WordPress\Pay\Payments\Payment;
use Pronamic\WordPress\Pay\Payments\PaymentStatus;
use Exception;
use KnitPay\Gateways\PaymentMethods;
use Pronamic\WordPress\Http\Facades\Http;
use KnitPay\Gateways\UpiQR\Gateway as UPIGateway;

/**
 * Title: Knit Pay UPI Parent Gateway
 * Copyright: 2020-2026 Knit Pay
 *
 * @author Knit Pay
 * @version 1.9.0.0
 * @since 1.9.0.0
 */
class KnitPayUPIGateway extends UPIGateway {
	protected $payment_expiry_seconds         = 300;
	protected $payment_expiry_recheck_seconds = 900;

	/**
	 * Constructs and initializes an UPI QR gateway
	 *
	 * @param Config $config
	 *            Config.
	 */
	public function __construct( $config ) {
		// Supported features.
		$this->supports = [
			'payment_status_request',
		];

		$this->enable_polling = true;

		parent::__construct( $config );

		$this->register_payment_methods();
	}

	protected function register_payment_methods() {
	}

	protected function validate_payment( Payment $payment ) {
		parent::validate_payment( $payment );

		if ( PaymentMethods::UPI_COLLECT === $payment->get_payment_method() ) {
			$upi_id = $payment->get_meta( 'customer_upi_id' );
			if ( empty( $upi_id ) || ! preg_match( '/^[a-zA-Z0-9.\-_]{2,256}@[a-zA-Z]{2,64}$/', $upi_id ) ) {
				throw new Exception( 'Invalid UPI ID format.' );
			} elseif ( 2000 < $payment->get_total_amount()->get_minor_units()->format( 0, '.', '' ) ) {
				throw new Exception( 'UPI Collect payment method is only supported for payments up to ₹2000.' );
			} elseif ( '5' !== $this->config->payment_template ) {
				throw new Exception( 'UPI Collect payment method is currently only supported with Payment Template 5.' );
			}
		} elseif ( PaymentMethods::SMS_PAYMENT_LINK === $payment->get_payment_method() ) {
			if ( empty( $payment->get_billing_address() ) || empty( $payment->get_billing_address()->get_phone() ) ) {
				throw new Exception( 'Phone number is required for receiving payment link via the SMS.' );
			} elseif ( '5' !== $this->config->payment_template ) {
				throw new Exception( 'SMS Payment Link is currently only supported with Payment Template 5.' );
			}
		}
	}

	/**
	 * Update status of the specified payment.
	 *
	 * @param Payment $payment
	 *            Payment.
	 */
	public function update_status( Payment $payment ) {
		if ( PaymentStatus::SUCCESS === $payment->get_status() ) {
			return;
		}

		if ( array_key_exists( 'status', $_REQUEST ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$payment_status = sanitize_text_field( $_REQUEST['status'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			if ( 'Expired' === $payment_status || 'Cancelled' === $payment_status ) {
				$payment->set_status( $payment_status );
				return;
			}
		}

		if ( $this->supports( 'bulk_payment_status_request' ) ) {
			// Don't check status if already checking.
			if ( get_transient( 'knit_pay_upi_checking_status_' . $this->config->config_id ) ) {
				return;
			}
			set_transient( 'knit_pay_upi_checking_status_' . $this->config->config_id, true, 5 );

			$transactions = $this->get_bulk_transaction_data( $payment );

			$this->bulk_update_status( $transactions );

			// Get the updated payment.
			$payment = get_pronamic_payment( $payment->get_id() );

			$this->expire_old_upi_payment( $payment );
		}
	}

	protected function get_bulk_transaction_data( Payment $payment ) {
		return [];
	}

	protected function bulk_update_status( $transactions ) {
		if ( empty( $transactions ) ) {
			return;
		}

		foreach ( $transactions as $transaction ) {
			if ( PaymentStatus::OPEN === $transaction['status'] ) {
				continue;
			}

			// Don't proceed if the transaction UTR is already linked to a payment.
			$payment = get_pronamic_payment_by_transaction_id( $transaction['utr'] );
			if ( null !== $payment ) {
				break;
			}

			// Don't proceed if the transaction with transaction ID not found.
			$payment = get_pronamic_payment_by_transaction_id( $transaction['kp_transaction_id'] );
			if ( null === $payment ) {
				continue;
			}

			if ( floatval( $payment->get_total_amount()->number_format( null, '.', '' ) ) !== floatval( $transaction['amount'] ) ) {
				$payment->set_status( PaymentStatus::FAILURE );
				return;
			}

			if ( PaymentStatus::SUCCESS === $transaction['status'] ) {
				$payment->set_transaction_id( $transaction['utr'] );
			}

			$payment->set_status( $transaction['status'] );

			//phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
			$payment->add_note( '<strong>Status Response:</strong><br><pre>' . print_r( $transaction['provider_response'], true ) . '</pre><br>' );

			$this->before_bulk_update_save( $payment, $transaction );

			$payment->save();
		}
	}

	protected function before_bulk_update_save( Payment $payment, $transaction ) {
		// Do nothing.
	}

	protected function make_amount_unique( Payment $payment ) {
		$actual_amount_minor = $payment->get_total_amount()->get_minor_units()->to_int();

		if ( get_transient( 'knit_pay_upi_' . $this->config->config_id . '_' . $actual_amount_minor ) ) {
			$unique_amount_minor = $this->find_unique_amount( $actual_amount_minor );
			$unique_amount       = $unique_amount_minor / 100;
			$actual_amount       = $actual_amount_minor / 100;
			$payment->add_note( 'Actual amount: ₹' . $actual_amount . '<br>Unique amount generated: ₹' . $unique_amount );

			$unique_money = new \Pronamic\WordPress\Money\Money( $unique_amount, $payment->get_total_amount()->get_currency() );
			$payment->set_total_amount( $unique_money );
		} else {
			$unique_amount_minor = $actual_amount_minor;
		}

		// Hold the amount for 15 minutes and can be allocated to other transaction if not paid.
		set_transient( 'knit_pay_upi_' . $this->config->config_id . '_' . $unique_amount_minor, $payment->get_id(), 15 * MINUTE_IN_SECONDS );
	}

	private function find_unique_amount( $actual_amount_minor, $retry = 1 ) {
		if ( $retry > 10 ) {
			throw new Exception( 'Unable to generate a unique amount. Please try again after sometime.' );
		}

		$range               = $retry ** 2;
		$random_amount_minor = rand( -$range, $range ) + $actual_amount_minor; // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_rand

		if ( get_transient( 'knit_pay_upi_' . $this->config->config_id . '_' . $random_amount_minor ) ) {
			return $this->find_unique_amount( $actual_amount_minor, ++$retry );
		}

		return $random_amount_minor;
	}

	// TODO: Change access level to protected after 30 Jun 2026.
	public function expire_old_upi_payment( Payment $payment ) {
		// Payment can be expired only if the payment current status is Pending.
		if ( PaymentStatus::OPEN !== $payment->get_status() ) {
			return;
		}

		// Make payment status as expired for payment older than 5 min.
		$payment_timestamp = $payment->get_date()->getTimestamp();
		if ( $this->payment_expiry_seconds < time() - $payment_timestamp ) {
			$payment->set_status( PaymentStatus::EXPIRED );

			// Recheck status after 15 min if payment getting expired very soon.
			if ( time() - $payment_timestamp < $this->payment_expiry_recheck_seconds ) {
				\as_schedule_single_action(
					time() + $this->payment_expiry_recheck_seconds,
					'knit_pay_upi_payment_status_check',
					[
						'payment_id' => $payment->get_id(),
						'gateway_id' => $this->config->gateway_id,
					],
					'knit-pay-upi'
				);
			}
		}
	}

	// TODO: Change access level to protected after 30 Jun 2026.
	public function knit_pay_upi_data( $payment ) {
		return [
			'knitpay_payment_id'  => $payment->get_id(),
			'mode'                => $payment->get_mode(),
			'gateway'             => \get_post_meta( $payment->get_config_id(), '_pronamic_gateway_id', true ),
			'payment_method'      => $payment->get_payment_method(),
			'source'              => $payment->get_source(),
			'amount'              => $payment->get_total_amount()->number_format( null, '.', '' ),
			'currency'            => $payment->get_total_amount()->get_currency()->get_alphabetic_code(),
			'knitpay_version'     => KNITPAY_VERSION,
			'knitpay_upi_version' => KNITPAY_UPI_VERSION,
			'php_version'         => PHP_VERSION,
			'website_url'         => home_url( '/' ),
			'data'                => [
				'payment_template' => $this->config->payment_template,
				'transaction_id'   => $payment->get_transaction_id(),
			],
		];
	}

	protected function get_gateway_data( $payment, $additional_data = [] ) {
		\KnitPayPro::check_knit_pay_pro_setup();

		try {
			$knit_pay_upi_data         = $this->knit_pay_upi_data( $payment );
			$knit_pay_upi_data['data'] = wp_parse_args(
				$knit_pay_upi_data['data'],
				$additional_data
			);

			$response = Http::post(
				\KNIT_PAY_UPI_RAPIDAPI_BASE_URL . 'payments/upi/request',
				[
					'body'    => wp_json_encode(
						$knit_pay_upi_data
					),
					'headers' => [
						'X-RapidAPI-Host' => \KNIT_PAY_UPI_RAPIDAPI_HOST,
						'X-RapidAPI-Key'  => get_option( 'knit_pay_pro_setup_rapidapi_key' ),
					],
				]
			);

			$result = $response->json();

			if ( '403' === (string) $response->status() ) {
				$api_link = ' https://rapidapi.com/knitpay/api/knit-pay-upi/pricing';
				throw new Exception( 'RapidAPI Error: ' . $result->message . $api_link );
			} elseif ( '200' !== (string) $response->status() ) {
				throw new Exception( 'RapidAPI Error: ' . $result->message );
			}

			return $result;
		} catch ( Exception $e ) {
			throw new Exception( $e->getMessage() );
		}
	}
}
