<?php
/**
 * YOCO Payment Gateway
 * @class  		KKART_Gateway_Yoco
 * @package 	Kkart
 * @version 	1.0.0
 * @category 	Payment Gateways
 * @author 		Kkart
 */
 
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

define( 'KKART_GATEWAY_YOCO_VERSION', KKART_VERSION );
define( 'KKART_GATEWAY_YOCO_URL', untrailingslashit( plugin_dir_url(__FILE__) ) );
define( 'KKART_YOCO_ONLINE_CHECKOUT_URL', 'https://payments.yoco.com/api/checkouts' );
 
class KKART_Gateway_Yoco extends KKART_Payment_Gateway {

	/**
	 * Version
	 *
	 * @var string
	 */
	public $version;
		
	public $mode;
	
	/**
	 * Constructor
	 */
	public function __construct() {

		//NOTE:: Update payment method description according to supported payment methods
		$this->version = KKART_GATEWAY_YOCO_VERSION;
		$this->credentials = $this->get_credential();
		$this->id = 'yoco';
		$this->enabled = $this->get_option( 'enabled' );
		$this->icon  = KKART_GATEWAY_YOCO_URL . '/assets/yoco-2024.svg';
		$this->has_fields = false;
		$this->init_form_fields();
		$this->init_settings();
		
		// Supported functionality.
		$this->supports = array( 'products', 'pre-orders', 'refunds');
		$this->title = $this->get_option( 'title', esc_html__( 'Yoco', 'kkart' ) );
		$this->description = $this->get_option( 'description', esc_html__( 'Pay securely using a credit/debit card or other payment methods via Yoco.', 'kkart' ) );

		$this->mode = $this->get_option( 'mode' );
		$this->method_title = esc_html__( 'Yoco', 'kkart' );
		$this->method_description = esc_html__( 'Yoco redirects customers to PayPal to enter their payment information.', 'kkart' );
		$this->available_currencies = (array)apply_filters('kkart_gateway_yoco_available_currencies', array( 'ZAR' ) );
		
		add_action( 'kkart_update_options_payment_gateways_' . $this->id, array( &$this, 'process_admin_options' ) );
		add_action( 'admin_notices', array( $this, 'admin_notices' ) );	
		add_action( 'yoco_payment_gateway/checkout/created', array( $this, 'updateOrderCheckoutMeta' ), 10, 2 );
		add_action( 'yoco_payment_gateway/order/refunded', array( $this, 'updateOrderRefundId' ), 10, 2 );
	}
	
	public function init_form_fields() {
		$this->form_fields = array(
			'enabled'		 => array(
				'title'	   => __( 'Enable/Disable', 'kkart' ),
				'label'	   => __( 'Enable Yoco Payments', 'kkart' ),
				'type'		=> 'checkbox',
				'description' => '',
				'default'	 => 'no',
			),
			'title'		   => array(
				'title'	   => __( 'Title', 'kkart' ),
				'type'		=> 'text',
				'description' => __( 'This controls the title which the user sees during checkout.', 'kkart' ),
				'default'	 => __( 'Yoco', 'kkart' ),
				'desc_tip'	=> true,
			),
			'description'		   => array(
				'title'	   => __( 'Description', 'kkart' ),
				'type'		=> 'text',
				'description' => __( 'This controls the description which the user sees during checkout.', 'kkart' ),
				'default'	 => __( 'Pay securely using a credit/debit card or other payment methods via Yoco.', 'kkart' ),
				'css'		 => 'max-width:400px;',
			),
			'mode'			=> array(
				'title'	   => __( 'Mode', 'kkart' ),
				'label'	   => __( 'Mode', 'kkart' ),
				'type'		=> 'select',
				'description' => __( 'Test mode allow you to test the plugin without processing money.<br>Set the plugin to Live mode and click on "Save changes" for real customers to use it.', 'kkart' ),
				'default'	 => 'Test',
				'options'	 => array(
					'live' => 'Live',
					'test' => 'Test',
				),
			),
			'live_secret_key' => array(
				'title'	   => __( 'Live Secret Key', 'kkart' ),
				'type'		=> 'password',
				'description' => __( 'Live Secret Key', 'kkart' ),
				'class'	   => 'input password-input',
			),
			'test_secret_key' => array(
				'title'	   => __( 'Test Secret Key', 'kkart' ),
				'type'		=> 'password',
				'description' => __( 'Test Secret Key', 'kkart' ),
				'class'	   => 'input password-input',
			),
			
		);
	}

	public function process_payment( $order_id ): ?array {
		
		$order = kkart_get_order( $order_id );
		
		try {
			$checkoutUrl = $order->get_meta( 'kkart_yoco_order_checkout_url', true, 'kkart' );
			
			if( ! empty( $checkoutUrl ) ){
				return $this->createSuccessRedirectResponse( $checkoutUrl );
			}
			
			$response = $this->send($order, 'payment');
			
			if( ! in_array( (int) $response['code'], array( 200, 201, 202 ), true )){
				$error_message	= isset( $response['body']['errorMessage'] ) ? $response['body']['errorMessage'] : '';
				$error_code	   = isset( $response['body']['errorCode'] ) ? $response['body']['errorCode'] : '';
				$response_message = isset( $response['message'] ) ? $response['message'] : '';
				throw new Exception( sprintf( 'Failed to request checkout. %s', $response_message ) );
			}
			
			do_action( 'yoco_payment_gateway/checkout/created', $order, $response['body']);
			return $this->createSuccessRedirectResponse( $response['body']['redirectUrl'] );
		} catch ( \Throwable $th ){
			kkart_add_notice( __( 'Your order could not be processed by Yoco - please try again later.', 'kkart' ), 'error' );
			return null;
		}
	}
	
	public function get_icon() {
		$icon = '<img class="yoco-payment-method-icon" style="max-height:1em;width:100%;" alt="' . esc_attr( $this->title ) . '" width="100" height="24" src="' . esc_url( $this->icon ) . '"/>';
		return apply_filters( 'kkart_gateway_icon',  $icon, $this->id );
	}

	public function process_refund( $order_id, $amount = null, $reason = '' ){
		$order = kkart_get_order( $order_id );
		
		try{	 
			$response = $this->send($order, 'refund');
			
			if ( isset( $response['body']['description'] ) ) {
				return new WP_Error( 400, $response['body']['description'] );
			}
			
			if ( isset( $response['body']['status'] ) && 'successful' === $response['body']['status'] ) {
				do_action( 'yoco_payment_gateway/order/refunded', $order, $response['body'] );
			}
			
			return new WP_Error( 200, $response['body']['message'] ?? '' );
		}catch( \Throwable $th ){
			return new WP_Error( $th->getCode(), $th->getMessage() );
		}
	}

	private function createSuccessRedirectResponse( string $redirectUrl ): array {
		return array(
			'result'   => 'success',
			'redirect' => $redirectUrl,
		);
	}
	
	public function send($order, string $request_type): array {
		try {
			$url  = $this->getUrl($order, $request_type);
			$args = $this->getArgs($order,$request_type);
			return $this->post( $url, $args );
		} catch ( \Throwable $th ) {
			throw $th;
		}
	}

	public function updateOrderCheckoutMeta( KKART_Order $order, array $data ): void {
		$this->updateOrderMeta( $order, 'kkart_yoco_order_checkout_id', $data['id'] );
		$this->updateOrderMeta( $order, 'kkart_yoco_order_checkout_mode', $data['processingMode'] );
		$this->updateOrderMeta( $order, 'kkart_yoco_order_checkout_url', $data['redirectUrl'] );
	}
	
	public function updateOrderPaymentId( KKART_Order $order, $payload ): void {
		$this->updateOrderMeta( $order, 'kkart_yoco_order_payment_id', $payload->getPaymentId() );
	}

	public function updateOrderRefundId( KKART_Order $order, array $data ): void {
		$this->updateOrderMeta( $order, 'kkart_yoco_order_refund_id', $data['refundId'] );
	}

	public function updateOrderMeta( KKART_Order $order, string $key, string $value ): void {
		$order->update_meta_data( $key, $value );
		$order->save_meta_data();
	}

	private function getUrl($order, string $request_type): string {
	 
		if($request_type === 'refund'){
			$url  = $this->getCheckoutApiUrl();
			return trailingslashit( $url ) . $this->getOrderCheckoutId($order) . '/refund';
		}

		return $this->getCheckoutApiUrl();
	}

	public function getCheckoutApiUrl(): ?string {
		
		if(! defined( 'KKART_YOCO_ONLINE_CHECKOUT_URL' ) ){
			return '';
		}
		
		return KKART_YOCO_ONLINE_CHECKOUT_URL;
	}


	public function getOrderCheckoutId( KKART_Order $order ): string {
		return $this->getOrderMeta( $order, 'kkart_yoco_order_checkout_id' );
	}

	public function getOrderMeta( KKART_Order $order, string $key ): string {
		$meta = $order->get_meta( $key, true, 'yoco' );
		return is_string( $meta ) ? $meta : '';
	}

	private function getArgs($order, string $request_type): array {
		
		if($request_type === 'refund'){
			return array(
				'headers' => $this->getHeaders($request_type),
			);
		}
		
		return array(
			'headers' => $this->getHeaders($request_type),
			'body' => $this->getBody($order)
		);
	}

	public function getHeaders($request_type) {
		
		$headers = array(
			'Content-Type'  => 'application/json',
			'Authorization' => $this->getApiBearer(),
			'X-Product'	 => 'kkart',
		);
		
		if($request_type === 'refund'){
			return apply_filters( 'yoco_payment_gateway/refund/request/headers', $headers );
		}
		
		return apply_filters( 'yoco_payment_gateway/payment/request/headers', $headers );
	}

	public function getHeadersForMode($order) {
		
		$headers = array(
			'Content-Type'  => 'application/json',
			'Authorization' => $this->getApiBearer( $order->get_meta( 'kkart_yoco_order_payment_mode', true ) ),
			'X-Product'	 => 'kkart',
		);
		
		return apply_filters( 'yoco_payment_gateway/payment/request/headers', $headers );
	}

	public function getApiBearer( string $mode = '' ): string {
		return 'Bearer ' . $this->getSecretKey( $mode );
	}

	public function getSecretKey( string $mode = '' ) {
		$mode = ( 'live' === $mode || 'test' === $mode ) ? $mode : $this->mode;
		return $this->get_option($mode . '_secret_key' ) ? $this->get_option($mode . '_secret_key' ) : '';
	}

	public function getBody($order) {
		
		$body = $this->buildPayload($order);
		$body = apply_filters( 'yoco_payment_gateway/payment/request/body', $body );
		
		return json_encode( $body, JSON_UNESCAPED_SLASHES );
	}

	public function getOrderCheckoutPaymentUrl( string $status, $order ): string {
		return add_query_arg(
			array(
				'kkart_yoco_checkout_status' => $status,
			),
			$order->get_checkout_order_received_url()
		);
	}
	
	// This function checked move on to the next
	public function buildPayload($order){
		
		$payload = array();
		$payload['amount'] = $this->format($order->get_total());
		$payload['currency'] = $order->get_currency();
		$payload['successUrl'] = $order->get_checkout_order_received_url();
		$payload['cancelUrl'] = $order->get_checkout_payment_url();
		$payload['failureUrl'] = $this->getOrderCheckoutPaymentUrl( 'failed', $order );
		$payload['totalDiscount'] = $order->get_total_discount();
		$payload['totalTaxAmount'] = $order->get_total_tax();
		$payload['subtotalAmount'] = $order->get_subtotal();
		
		if (!isset($payload['metadata'])) {
			$payload['metadata'] = array();
		}
		
		$metadata = $this->buildMetadata($order);
		$payload['metadata'] = array_merge($payload['metadata'], $metadata);
		$payload['productType'] = 'kkart';
		
		return $payload;
	}
	
	public function buildMetadata( KKART_Order $order ){
		
		$custom_info = array();
		$note = join(' ',
			array(
				__( 'order', 'kkart' ),
				$order->get_id(),
				__( 'from', 'kkart' ),
				$order->get_billing_first_name(),
				$order->get_billing_last_name(),
				'(' . $order->get_billing_email() . ')',
			)
		);
		
		$custom_info['billNote'] = $note;
		$custom_info['customerEmailAddress'] = $order->get_billing_email();
		$custom_info['customerLastName'] = $order->get_billing_last_name();
		$custom_info['customerFirstName'] =  $order->get_billing_first_name();
		return $custom_info;
	}

	public function post( string $url, array $args ){
		
		if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
			throw new Exception( __( 'Invalid URL for POST request.', 'yoco_wc_payment_gateway' ) );
		}
		
		$response = wp_remote_post( $url, $args );
		
		if ( is_wp_error( $response ) ) {
			throw new Exception( $response->get_error_message(), 0 );
		}
		
		$code = wp_remote_retrieve_response_code( $response );
		
		//start from here
		$message = wp_remote_retrieve_response_message( $response );
		$body = wp_remote_retrieve_body( $response );

		return array(
			'code'	=> $code,
			'message' => $message,
			'body'	=> (array) json_decode( $body ),
		);
	}

	public function format( $value, array $options = array() ): int {
		
		$options = wp_parse_args(
			$options,
			array(
				'decimals'	  => 2,
				'rounding_mode' => PHP_ROUND_HALF_UP,
			)
		);

		$decimals	  = absint( $options['decimals'] );
		$rounding_mode = min( absint( $options['rounding_mode'] ), 4 );
		
		return intval( round( ( (float) kkart_format_decimal( $value ) ) * ( 10 ** $decimals ), 0, $rounding_mode ) );
	}

	public function get_credential(){
		
		$cred_arr = array();
		$cred_arr['livePublic'] = $this->mode === "live" ? $this->get_option( 'live_public_key', '' ) : '';
		$cred_arr['liveSecret'] = $this->mode === "live" ? $this->get_option( 'live_secret_key', '' ) : '';
		$cred_arr['testPublic'] = $this->mode === "test" ? $this->get_option( 'test_public_key', '' ) : '';
		$cred_arr['testSecret'] = $this->mode === "test" ? $this->get_option( 'test_secret_key', '' ) : '';
		
		return $cred_arr;
	}

	public function check_requirements() {

		$errors = [];

		// Check if the store currency is supported by YOCO
		if (!in_array(get_kkart_currency(), $this->available_currencies)) {
			$errors[] = 'kkart-gateway-yoco-error-invalid-currency';
		}
		
		if ($this->mode === 'live' && empty($this->get_option('live_secret_key'))) {
			$errors[] = 'kkart-gateway-yoco-error-missing-livekey';
		}

		if ($this->mode === 'test' && empty($this->get_option('test_secret_key'))) {
			$errors[] = 'kkart-gateway-yoco-error-missing-testkey';
		}
		if ($this->mode === 'test' && !preg_match('/^sk_test/', $this->get_option('test_secret_key'))) {
			$errors[] = 'kkart-gateway-yoco-error-wrong-testkey';
		}

		if ($this->mode === 'live' && !preg_match('/^sk_live/', $this->get_option('live_secret_key'))) {
			$errors[] = 'kkart-gateway-yoco-error-wrong-livekey';
		}

		if($this->is_rest_api_enabled() === false){
			$errors[] = 'rest-api-disabled';
		}
		
		return array_filter( $errors );
	}

	public function is_rest_api_enabled(){
		$rest_api = get_option('sitepad_rest_api');
		if(empty($rest_api)){
			return false;
		}
		return true;
	}

	public function is_available() {
		
		if ( 'yes' === $this->enabled ) {
			$errors = $this->check_requirements();
			return 0 === count( $errors );
		}

		return parent::is_available();
	}

	public function admin_notices() {

		// Get requirement errors.
		$errors_to_show = $this->check_requirements();

		// If everything is in place, don't display it.
		if( ! count( $errors_to_show ) ){
			return;
		}

		// If the gateway isn't enabled, don't show it.
		if ( "no" ===  $this->enabled ) {
			return;
		}

		// Use transients to display the admin notice once after saving values.
		if ( ! get_transient( 'kkart-gateway-YOCO-admin-notice-transient' ) ) {
			set_transient( 'kkart-gateway-YOCO-admin-notice-transient', 1, 1);

			echo '<div class="notice notice-error is-dismissible"><p>'
				. __( 'To use YOCO as a payment provider, you need to fix the problems below:', 'kkart' ) . '</p>'
				. '<ul style="list-style-type: disc; list-style-position: inside; padding-left: 2em;">'
				. array_reduce( $errors_to_show, function( $errors_list, $error_item ) {
					$errors_list = $errors_list . PHP_EOL . ( '<li>' . $this->get_error_message($error_item) . '</li>' );
					return $errors_list;
				}, '' )
				. '</ul></p></div>';
		}
		
	}

	public function get_error_message( $key ) {
		
		$error_msg = '';
		
		switch ( $key ) {
			case 'kkart-gateway-yoco-error-invalid-currency':
				$error_msg = __( 'Your store uses a currency that YOCO doesnt support yet.', 'kkart' );
				break;
			case 'kkart-gateway-yoco-error-missing-livekey':
				$error_msg = __( 'You forgot to fill your live key.', 'kkart' ); 
				break;
			case 'kkart-gateway-yoco-error-missing-testkey':
				$error_msg = __( 'You forgot to fill your test key.', 'kkart' );
				break;
			case 'kkart-gateway-yoco-error-wrong-testkey':
				$error_msg = __( 'Check the formatting for the test key.', 'kkart' );
				break;
			case 'rest-api-disabled' :
				$error_msg = sprintf(
						__('SitePad REST API is disabled. Please <a href="%s">enable</a> it first.', 'kkart'),
						esc_url(admin_url('options-general.php'))
					);
				break;
			case 'kkart-gateway-yoco-error-wrong-livekey':
				$error_msg = __( 'Check the formatting for the live key.', 'kkart' );
				break;
		}
		
		return $error_msg;
		
	}
	
	public function process_admin_options() {
		parent::process_admin_options();
	
		$mode = $this->get_option('mode', 'test');
		$secret_key_option = $mode === 'live' ? 'live_secret_key' : 'test_secret_key';
		
		$secret_key = $this->get_option($secret_key_option);
	
		if (!empty($secret_key)) {
			$this->register_yoco_webhook($secret_key);
		}
	}

	public function register_yoco_webhook($secret_key) {
		
		$webhook_url = get_rest_url(null, 'kkart/v3/webhook');
		$mode = $this->get_option('mode', 'test');
		
		$payload = array(
			'name' => 'kkart-yoco-webhook',
			'url' => $webhook_url,
		);
		
		$args = array(
			'headers' => array(
				'Content-Type' => 'application/json',
				'Authorization' => 'Bearer ' . $secret_key,
			),
			'body' => json_encode($payload),
		);

		$response = wp_remote_post('https://payments.yoco.com/api/webhooks', $args);
		$response_params= wp_remote_retrieve_body( $response );
		$response_data = json_decode($response_params, true);

		if(is_wp_error($response)){
			$error_message = $response->get_error_message();
			error_log('Yoco Webhook Registration Error: ' . $error_message);
		}else{
			$response_code = wp_remote_retrieve_response_code($response);
			
			if($response_code === 200 || $response_code === 201){
				$key = 'live' === $mode ? 'yoco_payment_gateway_live_webhook_secret' : 'yoco_payment_gateway_test_webhook_secret';
		

				$current_secret = get_option( $key );
				if ( $current_secret === $response_data['secret'] ) {
					return;
				}

				update_option( $key, $response_data['secret']);
			}else{
				error_log('Yoco Webhook Registration Failed: ' . wp_remote_retrieve_body($response));
			}
		}
	}

}
