<?php
/**
 * Merchant validator.
 *
 * @package flo-checkout
 */

namespace Flo_Checkout\Gateways\Apple_Pay;

use Exception;
use Flo_Checkout\Gateways\Shift4_Apple_Pay;
use Northmill_Online_Shared_Checkout\Northmill_Online_Shared\Helpers\General_Helpers;

defined( 'ABSPATH' ) || exit;

/**
 * Class Merchant_Validator
 */
readonly class Merchant_Validator {

	/**
	 * Constructor.
	 *
	 * @param Shift4_Apple_Pay $gateway The Apple Pay gateway.
	 */
	public function __construct( private Shift4_Apple_Pay $gateway ) {}

	/**
	 * Validate the merchant.
	 *
	 * @throws Exception If the request fails.
	 */
	public function ajax_validate_merchant(): void {
		try {
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification done in AJAX handler.
			$validation_url = $_POST['validation_url'] ?? '';

			$session_object = $this->get_payment_session_object( $validation_url );
			wp_send_json_success( $session_object );
		} catch ( Exception $e ) {
			wp_send_json_error( $e->getMessage() );
		}
	}

	/**
	 * Validate the URL.
	 *
	 * @param string $validation_url The validation URL.
	 *
	 * @throws Exception If the URL is invalid.
	 */
	private function validate_url( string $validation_url ): void {
		$domain_allowlist = $this->get_apple_domain_allowlist();
		$domain           = wp_parse_url( $validation_url, PHP_URL_HOST );
		if ( ! in_array( $domain, $domain_allowlist, true ) ) {
			throw new Exception( 'Invalid domain' );
		}
	}

	/**
	 * Get the Apple domain allowlist.
	 *
	 * @return array
	 */
	private function get_apple_domain_allowlist(): array {
		if ( $this->gateway->is_integration_mode ) {
			return array(
				'apple-pay-gateway-cert.apple.com',
				'apple-pay-gateway.apple.com',
			);
		} else {
			return array(
				'apple-pay-gateway.apple.com',
			);
		}
	}

	/**
	 * Get payment session object.
	 *
	 * @param string $validation_url Validation URL from Apple Pay JS SDK.
	 *
	 * @return array The opaque Apple Pay session object
	 * @throws Exception If the request fails.
	 * @link https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/requesting_an_apple_pay_payment_session
	 */
	public function get_payment_session_object( string $validation_url ): array {
		$this->validate_url( $validation_url );

		$url = $validation_url;

		$merchant_identifier = self::get_merchant_identifier();

		if ( is_null( $merchant_identifier ) ) {
			$this->gateway->log_and_throw_error(
				__( 'Invalid Apple Pay setup.', 'flo-checkout-plugin' ),
				array(),
				'Merchant ID not set',
			);
		}

		$data = array(
			'merchantIdentifier' => $merchant_identifier,
			'displayName'        => $this->gateway->store_name,
			'initiative'         => 'web',
			'initiativeContext'  => wp_parse_url( get_site_url(), PHP_URL_HOST ),
		);

		$cer_file_path = self::get_cer_file_path();
		if ( is_null( $cer_file_path ) ) {
			$this->gateway->log_and_throw_error(
				__( 'Invalid Apple Pay setup.', 'flo-checkout-plugin' ),
				array( 'cert_path' => $cer_file_path ),
				'Certificate file not found',
			);
		}

		$data_json = wp_json_encode( $data );
		$response  = $this->curl_mtls_request( $url, $data_json, $cer_file_path );

		$session_object = json_decode( $response, true );

		if ( ! $session_object ) {
			$this->gateway->log_and_throw_error(
				__( 'Invalid response from Apple Merchant server.', 'flo-checkout-plugin' ),
				array( 'response' => $response ),
				'Failed to decode JSON response',
			);
		}

		return $session_object;
	}

	/**
	 * Get the merchant identifier.
	 *
	 * @return string|null Null if the merchant ID is not set.
	 */
	public static function get_merchant_identifier(): string|null {
		$mid = General_Helpers::get_constant( 'NMF_CHECKOUT_APPLE_PAY_MID' );

		if ( ! $mid ) {
			return null;
		}

		return $mid;
	}

	/**
	 * Get the path to the combined .cer file.
	 *
	 * @return string|null Null if the file doesn't exist.
	 */
	public static function get_cer_file_path(): string|null {
		$cert_path = General_Helpers::get_constant( 'NMF_CHECKOUT_APPLE_PAY_MID_CERT', '' );

		if ( ! file_exists( $cert_path ) ) {
			return null;
		}

		return $cert_path;
	}

	/**
	 * Make a mTLS request.
	 *
	 * @param string $url            The URL to request.
	 * @param string $json_data      The JSON data to send.
	 * @param string $cert_file_path The path to the PEM certificate file.
	 *
	 * @return string
	 * @throws Exception If the request fails.
	 *
	 * @phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init
	 * @phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_setopt
	 * @phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_exec
	 * @phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_close
	 * @phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_errno
	 * @phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_error
	 * @phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_getinfo
	 */
	private function curl_mtls_request( string $url, string $json_data, string $cert_file_path ): string {
		$ch = curl_init();

		curl_setopt( $ch, CURLOPT_URL, $url );
		curl_setopt( $ch, CURLOPT_SSLCERT, $cert_file_path );
		curl_setopt( $ch, CURLOPT_POST, true );
		curl_setopt( $ch, CURLOPT_POSTFIELDS, $json_data );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
		curl_setopt(
			$ch,
			CURLOPT_HTTPHEADER,
			array(
				'Content-Type: application/json',
				'Content-Length: ' . strlen( $json_data ),
			)
		);

		$response = curl_exec( $ch );

		$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );

		if ( curl_errno( $ch ) || ! $response ) {
			$error = curl_error( $ch );
			curl_close( $ch );

			$this->gateway->log_and_throw_error(
				__( 'Failed to contact Apple Merchant Server.', 'flo-checkout-plugin' ),
				array(
					'error'     => $error,
					'cert_path' => $cert_file_path,
					'url'       => $url,
				),
				'cURL error',
			);
		} elseif ( 200 !== $http_code ) {
			curl_close( $ch );

			$this->gateway->log_and_throw_error(
				__( 'Failed to contact Apple Merchant Server.', 'flo-checkout-plugin' ),
				array(
					'http_code' => $http_code,
					'cert_path' => $cert_file_path,
					'url'       => $url,
					'response'  => $response,
				),
				'HTTP Error',
			);
		}

		curl_close( $ch );

		return $response;
	}
}
