<?php

namespace PaychefPaymentGateway\Webhook;

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

use Paychef\Models\Response\Transaction;

use PaychefPaymentGateway\Service\OrderService;
use PaychefPaymentGateway\Service\PaychefApiService;
use PaychefPaymentGateway\Util\StatusUtil;

class Dispatcher
{
    /**
     * @var PaychefApiService
     */
    private $paychef_api_service;

    /**
     * @var OrderService
     */
    private $order_service;

    /**
     * Settings prefix
     *
     * @var string
     */
    private $prefix;

    /**
     * @param $paychef_api_service
     * @param $order_service
     * @param $prefix
     */
    public function __construct($paychef_api_service, $order_service, $prefix)
    {
        $this->paychef_api_service = $paychef_api_service;
        $this->order_service = $order_service;
        $this->prefix = $prefix;
    }

    /**
     * @return void
     * @throws \Exception
     */
    public function check_webhook_response()
    {
        try {
            // Sanitize incoming webhook data
            $resp = $this->sanitize_webhook_data( wp_unslash( $_REQUEST ) );
            $order_id = sanitize_text_field($resp['transaction']['invoice']['referenceId'] ?? '');
            $gateway_id = sanitize_text_field($resp['transaction']['invoice']['paymentRequestId'] ?? '');

            if (empty($order_id)) {
                $this->send_response('Webhook data incomplete');
            }

            if (!empty($this->prefix) && strpos($order_id, $this->prefix) === false) {
                $this->send_response('Prefix mismatch');
            }

            // Check if this is an add_payment_method request
            $is_add_payment_method = strpos($order_id, 'add_payment_method_') === 0;
            
            if ($is_add_payment_method) {
                // Handle add_payment_method webhook
                $this->handle_add_payment_method_webhook($resp);
                return;
            }
            
            $arr = explode('_', $order_id);
            $order_id = end($arr);

            if (!isset($resp['transaction']['status'])) {
                throw new \Exception('Missing transaction status');
            }

	        // Get order first
	        $order = new \WC_Order($order_id);
	        if (!$order_id || !$order) {
		        throw new \Exception('Fraudulent request');
	        }

	        // Initialize PaychefApiService with correct store credentials
	        if (class_exists('\Lieferchef_Stores') && \Lieferchef_Stores::is_multistore()) {
		        $store_id = intval($order->get_meta('lieferchef_shop_id', true));

		        if ($store_id > 0) {
			        $store = \Lieferchef_Stores::get_store($store_id);
			        if ($store) {

				        // Create new PaychefApiService instance with store credentials
				        $this->paychef_api_service = new PaychefApiService(
					        sanitize_text_field($store->paychef_instance),
					        sanitize_text_field($store->paychef_sid),
					        get_option(PAYCHEF_CONFIGS_PREFIX . 'platform'),
					        null
				        );

			        } else {
				        throw new \Exception('Store not found for ID: ' . $store_id);
			        }
		        } else {
			        $this->paychef_api_service = new PaychefApiService(
				        get_option(PAYCHEF_CONFIGS_PREFIX . 'instance'),
				        get_option(PAYCHEF_CONFIGS_PREFIX . 'api_key'),
				        get_option(PAYCHEF_CONFIGS_PREFIX . 'platform'),
				        null
			        );
		        }
	        }

            /**
             * @var \Paychef\Models\Response\Transaction
             */
            $transaction = $this->paychef_api_service->getPaychefTransaction($resp['transaction']['id']);
            if ($transaction->getStatus() !== $resp['transaction']['status']) {
                throw new \Exception('Fraudulent transaction status');
            }

            // Check if subscription to handle accordingly
            $subscriptions = [];
            $preAuthId = null;
            if (!empty($resp['transaction']['preAuthorizationId'])) {
                $preAuthId = $resp['transaction']['preAuthorizationId'];
                
                // Only handle as subscription if WooCommerce Subscriptions is available and subscriptions exist
                if (function_exists('wcs_get_subscriptions_for_order')) {
                    $subscriptions = wcs_get_subscriptions_for_order($order_id, array('order_type' => 'any'));
                    
                    // Only proceed with subscription logic if we actually have subscriptions
                    if (!empty($subscriptions)) {
                        // Identify the correct order_id for subscription processing
                        $firstSubscription = reset($subscriptions);
                        $order_id = $firstSubscription->get_last_order('ids', 'any');
                    }
                }
            }


            // Set additional metadata
            $order->update_meta_data('_paychef_real_transaction_id', $resp['transaction']['id']);
            $order->update_meta_data('_paychef_transaction_uuid', $resp['transaction']['uuid']);
            $order->update_meta_data('_paychef_payment_method', $resp['transaction']['payment']['brand']);
            $order->update_meta_data('_paychef_psp', $resp['transaction']['psp']);
            $order->update_meta_data('_paychef_mode', $resp['transaction']['mode']);
            if (!empty($resp['transaction']['contact']['uuid'])) {
                $order->update_meta_data('paychef_contact_uuid', $resp['transaction']['contact']['uuid']);
            }
            $order->save();

            $orderTotal = round(floatval($order->get_total('edit')), 2);
            $newTransactionStatus = $transaction->getStatus();

            // A confirmed transaction can also be a partial payment (with bank transfer).
            // Therefore the new correct status must be determined
            if (in_array($newTransactionStatus, [Transaction::CONFIRMED, Transaction::REFUNDED, Transaction::PARTIALLY_REFUNDED]) && !$preAuthId) {
                $gateway = $this->paychef_api_service->getPaychefGateway($gateway_id);
                $confirmedAmount = StatusUtil::getAmountByStatusAndGateway($gateway, [Transaction::CONFIRMED]);
                $refundedAmount = StatusUtil::getAmountByStatusAndGateway($gateway, [Transaction::PARTIALLY_REFUNDED, Transaction::REFUNDED]);
                $newTransactionStatus = StatusUtil::determineNewOrderStatus($orderTotal, $confirmedAmount, $refundedAmount);
            }

            $transactionUuid = $transaction->getUuid();
            $additionalData = [
                'payment_brand' => $resp['transaction']['payment']['brand'] ?? '',
                'psp' => $resp['transaction']['psp'] ?? '',
                'mode' => $resp['transaction']['mode'] ?? '',
                'payment' => [
                    'cardNumber' => $resp['transaction']['payment']['cardNumber'] ?? '',
                    'expiry' => $resp['transaction']['payment']['expiry'] ?? '',
                    'brand' => $resp['transaction']['payment']['brand'] ?? '',
                ],
            ];

            $this->order_service->handleTransactionStatus($order, $subscriptions, $newTransactionStatus, $transactionUuid, $preAuthId, $additionalData);
            $this->send_response('Success: Processed webhook response');
        } catch (\Exception $e) {
            error_log('Error in check_webhook_response: ' . $e->getMessage());
            throw new \Exception('Error: ' . $e->getMessage());
        }
    }

    /**
     * Returns webhook response.
     *
     * @param string $message success or error message.
     * @param array $data response data.
     * @param string|int $response_code response code.
     */
    private function send_response($message, $data = [], $response_code = 200)
    {
        $response['message'] = $message;
        if (!empty($data)) {
            $response['data'] = $data;
        }
        echo wp_json_encode($response);
        http_response_code($response_code);
        die;
    }

    /**
     * Handle webhook for add_payment_method requests
     *
     * @param array $resp Webhook response data
     * @throws \Exception
     */
    private function handle_add_payment_method_webhook($resp) {
        try {
            $reference_id = $resp['transaction']['invoice']['referenceId'] ?? '';
            
            if (empty($reference_id)) {
                throw new \Exception('Missing reference ID for add_payment_method');
            }

            // Extract user ID and gateway ID from reference
            // Formats: add_payment_method_123_1234567890 or add_payment_method_123_1234567890_paychef
            $parts = explode('_', $reference_id);
            if (count($parts) < 4 || $parts[0] !== 'add' || $parts[1] !== 'payment' || $parts[2] !== 'method') {
                throw new \Exception('Invalid add_payment_method reference format: ' . $reference_id);
            }
            
            $user_id = intval($parts[3] ?? 0);
            if (!$user_id) {
                throw new \Exception('Invalid user ID in add_payment_method reference: ' . $reference_id);
            }
            
            // Extract gateway ID from reference (last part if exists, otherwise default to paychef)
            $gateway_id = (count($parts) > 5) ? end($parts) : 'paychef';
            
            // Verify transaction
            $transaction = $this->paychef_api_service->getPaychefTransaction($resp['transaction']['id']);
            if ($transaction->getStatus() !== $resp['transaction']['status']) {
                throw new \Exception('Fraudulent transaction status for add_payment_method');
            }

            // Only process if transaction is authorized (preAuth)
            if ($transaction->getStatus() === \Paychef\Models\Response\Transaction::AUTHORIZED && 
                !empty($resp['transaction']['preAuthorizationId'])) {
                
                $pre_auth_id = $resp['transaction']['preAuthorizationId'];
                $payment_data = [
                    'payment' => [
                        'cardNumber' => $resp['transaction']['payment']['cardNumber'] ?? '',
                        'expiry' => $resp['transaction']['payment']['expiry'] ?? '',
                        'brand' => $resp['transaction']['payment']['brand'] ?? '',
                    ]
                ];
                
                // Save payment token
                $token_saved = $this->order_service->save_payment_token_for_user(
                    $user_id, 
                    $gateway_id, 
                    $pre_auth_id, 
                    $payment_data
                );

                if ($token_saved) {
                    $this->send_response('Success: Payment method added and token saved');
                } else {
                    $this->send_response('Warning: Payment method processed but token not saved');
                }
            } else {
                $this->send_response('Info: Payment method transaction processed but not authorized');
            }

        } catch (\Exception $e) {
            error_log('Error in handle_add_payment_method_webhook: ' . $e->getMessage());
            throw new \Exception('Error processing add_payment_method webhook: ' . $e->getMessage());
        }
    }

    /**
     * Extract gateway ID from reference
     *
     * @param string $reference
     * @return string
     */
    private function extract_gateway_from_reference($reference) {
        // For now, default to paychef - this could be enhanced to detect the actual gateway
        // from the reference or payment data
        return 'paychef';
    }

    /**
     * Sanitize webhook data recursively
     *
     * @param mixed $data Data to sanitize
     * @return mixed Sanitized data
     */
    private function sanitize_webhook_data($data) {
        if (is_array($data)) {
            $sanitized = array();
            foreach ($data as $key => $value) {
                $sanitized[sanitize_text_field($key)] = $this->sanitize_webhook_data($value);
            }
            return $sanitized;
        } elseif (is_string($data)) {
            return sanitize_text_field($data);
        } elseif (is_numeric($data)) {
            return $data;
        } elseif (is_bool($data)) {
            return $data;
        }
        return $data;
    }
}
