<?php
/**
 * File: TamiPaymentClient.php
 * Year: 2023
 */

namespace Tami\WooCommerce;

use Exception;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
} // Exit if accessed directly

class TamiPaymentClient {

    const FIXED_KID_VALUE = '00ff6ea8-3511-4d04-946c-ba569208306f';
    const FIXED_K_VALUE = '87919a8f-957b-427b-ae12-167622ab52b5';

	private ?string $merchantId;
	private ?string $terminalId;
	private ?string $secret_key;
	private ?string $fixed_kid_value = self::FIXED_KID_VALUE;
	private ?string $fixed_k_value = self::FIXED_K_VALUE;
	private ?string $api_version = 'v2';
	private ?string $method;

	private bool $testMode = false;

	private bool $debugMode = false;

	private bool $three_d_enabled = false;

	const TEST_ENV_HOST = "https://sandbox-paymentapi.tami.com.tr";
	const PROD_ENV_HOST = "https://paymentapi.tami.com.tr";

	const TEST_HOSTED_BASE_URL = "https://sandbox-portal.tami.com.tr/hostedPaymentPage";
	const PROD_HOSTED_BASE_URL = "https://portal.tami.com.tr/hostedPaymentPage";

	public function __construct() {
		$this->load_request_class();
	}

	public function enableDebug( $status ): TamiPaymentClient {
		$this->debugMode = $status;

		return $this;
	}

	public function getHost(): string {
		return $this->testMode ? self::TEST_ENV_HOST : self::PROD_ENV_HOST;
	}

	public function setTest( bool $t ): TamiPaymentClient {
		$this->testMode = $t;

		return $this;
	}

	public function isTest(): bool {
		return $this->testMode;
	}

	public function setPaymentMethod( string $method ): TamiPaymentClient {
		$this->method          = $method;
		$this->three_d_enabled = $method == '3D';

		return $this;
	}

	public function getPaymentMethod(): string {
		return $this->method;
	}

	public function getThreeDEnabled(): bool {
		return $this->three_d_enabled;
	}

	public function setMerchantId( ?string $merchantId ): TamiPaymentClient {
		$this->merchantId = $merchantId;

		return $this;
	}

	public function setTerminalId( ?string $terminalId ): TamiPaymentClient {
		$this->terminalId = $terminalId;

		return $this;
	}

	public function setFixedKidValue( ?string $fixedKidValue ): TamiPaymentClient {
		if($this->api_version === 'v2')
			return $this;
		$this->fixed_kid_value = $fixedKidValue;

		return $this;
	}

	public function setApiVersion($version) : TamiPaymentClient {
		$this->api_version = $version;
		return $this;
	}

	public function setFixedKValue( ?string $fiexKValue ): TamiPaymentClient {
		if($this->api_version === 'v2')
			return $this;
		$this->fixed_k_value = $fiexKValue;

		return $this;
	}

	public function setSecretKey( ?string $secretKey ): TamiPaymentClient {
		$this->secret_key = $secretKey;

		return $this;
	}

	public static function random_numbers( $digits ): int {
		$min = pow( 10, $digits - 1 );
		$max = pow( 10, $digits ) - 1;

		return wp_rand( $min, $max );
	}

	public static function getClientIpAddress() {
		if ( isset( $_SERVER["HTTP_CLIENT_IP"] ) ) {
			$ip = sanitize_text_field( wp_unslash( $_SERVER["HTTP_CLIENT_IP"] ) );
		} elseif ( isset( $_SERVER["HTTP_X_FORWARDED_FOR"] ) ) {
			$ip = sanitize_text_field( wp_unslash( $_SERVER["HTTP_X_FORWARDED_FOR"] ) );
			if ( strstr( $ip, ',' ) ) {
				$tmp = explode( ',', $ip );
				$ip  = trim( $tmp[0] );
			}
		} else {
			$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER["REMOTE_ADDR"] ) ) : '';
		}

		return $ip;
	}

	public static function encodeOrderId( $order_id ): string {
		$order_num = self::random_numbers( 5 );
		$order_num .= self::random_numbers( 5 );
		$order_num .= self::random_numbers( 5 );
		$order_num .= sprintf( "_%s", $order_id );

		return substr( $order_num, - 15 );
	}

	public function getAuthToken(): string {
		$hash_str = sprintf( "%s%s%s", $this->merchantId, $this->terminalId, $this->secret_key );
		$hash     = base64_encode( hash( 'sha256', $hash_str, true ) );

		return sprintf( "%s:%s:%s", $this->merchantId, $this->terminalId, $hash );
	}

	public static function filterOnlyNumbers( $in ) {
		return preg_replace( '/[^0-9]/', '', $in );
	}

	public static function decodeOrderId( string $order_id ): string {
		$order_id = explode( '_', $order_id );

		return $order_id[1];
	}

	public function load_request_class() {
		$firstInclude      = [ 'PostAuthRequest' ];
		$request_directory = TAMI_PLUGIN_PATH . '/lib/Requests/*.php';
		$files             = glob( $request_directory );
		$filesFirst        = [];
		foreach ( $files as $kFile => $file ) {
			foreach ( $firstInclude as $fFile ) {
				if ( strpos( $file, $fFile ) !== false ) {
					$filesFirst[] = $file;
					unset( $files[ $kFile ] );
				}
			}
		}
		foreach ( $filesFirst as $f ) {
			include $f;
		}
		foreach ( $files as $f ) {
			include $f;
		}
	}

	public function generateOneTimeHostedToken( $data ): string {
		$request = new OneTimeHostedTokenRequest();
		$request->set_amount( $data['amount'] );
		$request->set_mobile_phone_number( $data['phonenumber'] );
		$request->set_order_id( $data['orderid'] );
		$request->set_success_callback_url( $data['callback'] );
		$request->set_fail_callback_url( $data['callback'] );

		$response = $this->curl( '/hosted/create-one-time-hosted-token', $request->get() );
		$token    = (string) $response->oneTimeToken;

		$baseUrl = self::PROD_HOSTED_BASE_URL;
		if ( $this->isTest() ) {
			$baseUrl = self::TEST_HOSTED_BASE_URL;
		}

		return sprintf( "%s?token=%s", $baseUrl, $token );
	}

	/**
	 * @throws Exception
	 */
	public function authRequest( $data = [], $extraData = [] ) {
		$orderData   = $data['order'] ?? [];
		$cardData    = $data['card'] ?? [];
		$paymentData = $data['payment'] ?? [];
		$otherData   = $data['other'] ?? [];
		$cartItems   = $data['cart_items'] ?? [];

		$paymentAuth = new AuthRequest();
		$paymentAuth->setOrderId( $orderData['orderId'] ?? '' );
		$paymentAuth->setAmount( $orderData['amount'] );
		$paymentAuth->setCurrency( $orderData['currency'] );
		$paymentAuth->setInstallmentCount( $paymentData['installmentCount'] );
		$paymentAuth->setPaymentGroup( "PRODUCT" );
//		$paymentAuth->setMotoInd( false ); // *
		$paymentAuth->setPaymentChannel( "WEB" );
		$paymentAuth->setSubType( "" );

		if ( $this->three_d_enabled ) {
			$paymentAuth->setCallbackUrl( $otherData['callback'] );
		}

		// card
		$card = new Card();
		$card->setCvv( $cardData['cvv'] );
		$card->setExpireMonth( $cardData['expireMonth'] );
		$card->setExpireYear( $cardData['expireYear'] );
		$card->setHolderName( $cardData['cardHolder'] );
		$card->setNumber( $cardData['number'] );
		$paymentAuth->setCard( $card );

		$billingAddress = new Address();
		$billingAddress->setAddress( $orderData['billingAddress'] ?? '' );
		$billingAddress->setEmailAddress( $orderData['billingEmailAddress'] ?? '' );
		$billingAddress->setCity( $orderData['billingCity'] ?? '' );
		$billingAddress->setCompanyName( $orderData['billingCompany'] ?? '' );
		$billingAddress->setCountry( $orderData['billingCountry'] ?? '' );
		$billingAddress->setContactName( isset($orderData['billingFirstname']) ? sprintf( '%s %s', $orderData['billingFirstname'], $orderData['billingLastname'] ) : '' );
		$billingAddress->setPhoneNumber( $orderData['billingPhone'] ?? '' );
		$billingAddress->setZipCode( $orderData['billingZipcode'] ?? '' );
		$billingAddress->setDistrict( $orderData['billingDistrict'] ?? '' );
		$paymentAuth->setBillingAddress( $billingAddress );

		// auto-fill from billing
		$addressFields = [
			'shippingEmailAddress',
			'shippingAddress',
			'shippingCity',
			'shippingCompany',
			'shippingCountry',
			'shippingContactName',
			'shippingPhone',
			'shippingZipcode',
			'shippingDistrict'
		];

		foreach ( $addressFields as $item ) {
			if ( empty( $orderData[ $item ] ) && ! empty( $orderData[ str_replace( 'shipping', 'billing', $item ) ] ) ) {
				$orderData[ $item ] = $orderData[ str_replace( 'shipping', 'billing', $item ) ];
			}
		}

		$shippingAddress = new Address();
		$shippingAddress->setAddress( $orderData['shippingAddress'] ?? '' );
		$shippingAddress->setCity( $orderData['shippingCity'] ?? '' );
		$shippingAddress->setCompanyName( $orderData['shippingCompany'] ?? '' );
		$shippingAddress->setCountry( $orderData['shippingCountry'] ?? '' );
		$shippingAddress->setContactName( $orderData['shippingContactName'] ?? '' );
		$shippingAddress->setPhoneNumber( $orderData['shippingPhone'] ?? '' );
		$shippingAddress->setZipCode( $orderData['shippingZipcode'] ?? '' );
		$shippingAddress->setDistrict( $orderData['shippingDistrict'] ?? '' );
		$paymentAuth->setShippingAddress( $shippingAddress );

		$buyer = new Buyer();
		$buyer->setIpAddress( $paymentData['ipAddress'] );
		$buyer->setBuyerId( $orderData['buyerId'] );
		$buyer->setName( $orderData['billingFirstname'] );
		$buyer->setSurName( $orderData['billingLastname'] );
//		$buyer->setIdentityNumber( $orderData['billingIdentityNumber'] );
		$buyer->setCity( $orderData['billingCity'] ?? '' );
		$buyer->setCountry( $orderData['billingCountry'] ?? '' );
		$buyer->setEmailAddress( $orderData['email'] );
		$buyer->setPhoneNumber( $orderData['billingPhone'] );
		$buyer->setRegistrationAddress( $orderData['billingAddress'] ?? '' );
		$buyer->setZipCode( $orderData['billingZipcode'] ?? '' );
		//registrationDate empty
		//lastLoginDate empty
		$paymentAuth->setBuyer( $buyer );

		$basket = new Basket();
		$basket->setBasketId( $paymentData['basketId'] );


		$basketItems = [];
		if ( count( $cartItems ) > 0 ) {
			foreach ( $cartItems as $cart_item ) {
				$basketItems[] = ( new BasketItem() )
					->setItemId( $cart_item['id'] )
					->setItemType( ( $cart_item['type'] ?? 'PHYSICAL' ) )
					->setName( $cart_item['name'] )
					->setNumberOfProducts( $cart_item['quantity'] )
					->setUnitPrice( $cart_item['unitPrice'] )
					->setTotalPrice( $cart_item['totalPrice'] );
			}
		} else {
			$basketItems[] = ( new BasketItem() )
				->setItemId( 1 )
				->setItemType( 'PHYSICAL' )
				->setName( 'Order' )
				->setNumberOfProducts( 1 )
				->setUnitPrice( $orderData['amount'] )
				->setTotalPrice( $orderData['amount'] );
		}

		if ( isset( $extraData['installment_fee'] ) && $extraData['installment_fee'] > 0 ) {
			$basketItems[] = ( new BasketItem() )
				->setItemId( 'INSTALLMENT_FEE' )
				->setItemType( 'VIRTUAL' )
				->setName( __( 'Installment Fee', 'tami-payment' ) )
				->setNumberOfProducts( 1 )
				->setUnitPrice( $extraData['installment_fee'] )
				->setTotalPrice( $extraData['installment_fee'] );
		}


		$basket->setBasketItems( $basketItems );


		$paymentAuth->setBasket( $basket );
		$paymentAuth->generateHash( $this->merchantId, $this->terminalId, $this->secret_key, $this->fixed_kid_value, $this->fixed_k_value, json_decode( wp_json_encode( $paymentAuth->get() ), true ) );

		return $this->curl( '/payment/auth', $paymentAuth );
	}

	/**
	 * @throws Exception
	 */
	public function installmentQuery( array $data ) {
		$installmentRequest = new GetInstallmentInfoRequest();
		$installmentRequest->setBinNumber( $data['bin'] ?? 0 );
		$installmentRequest->generateSecurityHash(
			$this->merchantId,
			$this->terminalId,
			$this->secret_key,
			$this->fixed_kid_value,
			$this->fixed_k_value
		);

		return $this->curl( '/installment/bin-info', $installmentRequest->get() );
	}

	/**
	 * @throws Exception
	 */
	public function getInstallmentInfo( $bin ) {
		$installmentRequest = new GetInstallmentInfoRequest();
		$installmentRequest->setBinNumber( $bin ?? 0 );
		$installmentRequest->generateSecurityHash(
			$this->merchantId,
			$this->terminalId,
			$this->secret_key,
			$this->fixed_kid_value,
			$this->fixed_k_value
		);

		return $this->curl( '/installment/installment-info', $installmentRequest->get() );
	}

	/**
	 * @throws Exception
	 */
	public function callback(): array {
		/*if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['nonce'] ) ), \Tami_WooCommerce_GW::CALLBACK_ENDPOINT ) ) {
			throw new \Exception( esc_attr__('Nonce could not verified!', 'tami-payment') );
		}*/

		if ( $this->debugMode ) {
			$logData = [];
			foreach ( array_keys( $_POST ) as $key ) {
				$logData[ $key ] = isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';
			}
			Debugger::log(
				$logData, 'Callback Data'
			);
		}

		$status = isset( $_POST['success'] ) ? sanitize_text_field( wp_unslash( $_POST['success'] ) ) : '';
		if ( $status !== 'true' ) {
			$message = ( isset( $_POST['message'] ) ? sanitize_text_field( wp_unslash( $_POST['message'] ) ) : '' );

			if ( empty( $message ) ) {
				$message = isset( $_POST['mdErrorMessage'] ) ? sanitize_text_field( wp_unslash( $_POST['mdErrorMessage'] ) ) : __( 'Unknown error!', 'tami-payment' );
			}

			if ( empty( $message ) ) {
				$message = isset( $_POST['callbackStatus'] ) ? sanitize_text_field( wp_unslash( $_POST['callbackStatus'] ) ) : '';
			}

			if ( empty( $message ) ) {
				$message = __('An error occurred!','tami-payment');
			}

			throw new Exception( esc_attr( $message ) );
		}

		$transaction_id = isset( $_POST['orderId'] ) ? sanitize_text_field( wp_unslash( $_POST['orderId'] ) ) : '';
		$hash           = isset( $_POST['hashedData'] ) ? sanitize_text_field( wp_unslash( $_POST['hashedData'] ) ) : '';
		if ( ! $this->checkCallbackHash( $hash ) ) {
			throw new \Exception( esc_attr__( 'Hash validation failed!', 'tami-payment' ) );
		}

		$complete3DRequest = new Complete3DAuthRequest();
		$complete3DRequest->setOrderId( $transaction_id );
		$complete3DRequest->generateHash(
			$this->merchantId,
			$this->terminalId,
			$this->secret_key,
			$this->fixed_kid_value,
			$this->fixed_k_value
		);
		$competeRequestResponse = $this->curl( '/payment/complete-3ds', $complete3DRequest );


		if ( ! is_object( $competeRequestResponse ) ) {
			$competeRequestResponse = new \stdClass();
		}

		if ( ! isset( $competeRequestResponse->success ) ) {
			$competeRequestResponse->success = false;
		}
		if ( ! isset( $competeRequestResponse->errorMessage ) ) {
			$competeRequestResponse->errorMessage = __( 'Invalid response returned from 3D Complete Request!', 'tami-payment' );
		}
		if ( ! isset( $competeRequestResponse->errorCode ) ) {
			$competeRequestResponse->errorCode = '0001';
		}

		if ( ! $competeRequestResponse->success ) {
			throw new \Exception( sprintf( '%s - #%s', esc_attr( $competeRequestResponse->errorMessage ), esc_attr( $competeRequestResponse->errorCode ) ) );
		}

		$amount = $competeRequestResponse->amount ?? '0';


		return [
			'transactionId' => $transaction_id,
			'orderId'       => self::decodeOrderId( $transaction_id ),
			'installment'   => (int) ( $competeRequestResponse->installmentCount ?? '' ),
			'amount'        => $amount
		];
	}

	/**
	 * @throws Exception
	 */
	public function curl( $url = '', $postdata = '' ) {
		if ( empty( $url ) ) {
			throw new Exception( esc_attr__('cURL address missing!', 'tami-payment') );
		}
		if ( strpos( $url, 'https://' ) === false && strpos( $url, 'http://' ) === false ) {
			$url = $this->getHost() . $url;
		}

		$header = [
			'PG-Auth-Token'  => $this->getAuthToken(),
			'correlationId'  => $this->getCorrelationId(),
			'PG-Api-Version' => $this->api_version,
		];

		$logData = [
			'url'       => $url,
			'post_data' => $postdata,
			'header'    => $header
		];

		if ( $this->debugMode ) {
			Debugger::log(
				$logData, 'Before Curl'
			);
		}

		if ( is_array( $postdata ) || is_object( $postdata ) ) {
			$postdata               = wp_json_encode( $postdata );
			$header['Content-Type'] = 'application/json';
		}

		$method  = "get";
		$options = [
			'headers'   => $header,
			'timeout'   => 30,
			'sslverify' => false,
		];
		if ( strlen( $postdata ) ) {
			$options['body']        = $postdata;
			$options['data_format'] = 'body';
			$method                 = 'post';
		}

		if ( $method === 'post' ) {
			$data = wp_remote_post( $url, $options );
		} else {
			$data = wp_remote_get(
				$url,
				$options
			);
		}
		$data = wp_remote_retrieve_body( $data );
		if ( $this->debugMode ) {
			Debugger::log(
				$data, 'After Curl - Response'
			);
		}
		if ( strpos( $data, '{' ) === 0 ) {
			$data = json_decode( $data );
		}

		if ( is_object( $data ) && isset( $data->success ) && ! $data->success ) {
			$message = sprintf( "#%s - %s", $data->errorCode, $data->errorMessage );
			throw new Exception( esc_attr( $message ) );
		}

		return $data;
	}

	private function getCorrelationId(): string {
		return 'tami_' . self::random_numbers( 10 ) . '_' . time() . '_' . self::random_numbers( 10 );
	}

	/**
	 * @throws Exception
	 */
	public function check_api_connection() {
		$result = $this->installmentQuery( [ 'bin' => '55496012' ] ); // garantibbva-bonus-example
		if ( ! isset( $result->success ) || ! $result->success ) {
			throw new \Exception( esc_attr__( 'Please check your merchant details!', 'tami-payment' ) );
		}
	}

	/**
	 * @throws Exception
	 */
	public function refund( $transaction_id, $amount, $reason = '' ) {
		$refundRequest = new ReverseAndRefundRequest();
		$refundRequest->setOrderId( $transaction_id );
		$refundRequest->setAmount( $amount );
		$refundRequest->setReason( $reason );
		$refundRequest->generateHash(
			$this->merchantId,
			$this->terminalId,
			$this->secret_key,
			$this->fixed_kid_value,
			$this->fixed_k_value
		);

//        TODO: URL UPDATE and Test!!! - iptal: payment/void | iade: payment/refund
		return $this->curl( '/payment/reverse', $refundRequest->get() );
	}

	private function checkCallbackHash( $hash ): bool {
		$hashStr = "";
		$parts   = [
			'cardOrganization',
			'cardBrand',
			'cardType',
			'maskedNumber',
			'installmentCount',
			'currencyCode',
			'txnAmount',
			'orderId',
			'systemTime',
			'success'
		];
		foreach ( $parts as $part ) {
			if ( isset( $_POST[ $part ] ) ) {
				$hashStr .= sanitize_text_field( wp_unslash( $_POST[ $part ] ) );
			}
		}
		$hashGenerated = base64_encode( hash_hmac( 'sha256', $hashStr, $this->secret_key, true ) );

		return $hashGenerated === $hash;
	}

	public function authResponseHashIsValid( $authResponse ): bool {
		$authResponse->amount = sprintf( '%0.2f', $authResponse->amount );

		$hash_str = $authResponse->amount . $authResponse->orderId . $authResponse->correlationId . $authResponse->currency . $authResponse->installmentCount . $authResponse->systemTime . ( $authResponse->success ? 'true' : 'false' ) . $this->secret_key;
		$hash     = base64_encode( hash( 'sha256', $hash_str, true ) );

		return $hash === $authResponse->securityHash;
	}
}
