<?php

namespace App\Modules\Payment\CcAvenue;

use App\Core\Payment\AbstractPayment;

class CcAvenue extends AbstractPayment
{
    protected $gatewayUrl;

    protected $refundUrl = 'https://api.ccavenue.com/apis/servlet/DoWebTrans';

    public function __construct(array $config = [])
    {
        parent::__construct($config);
        $this->gatewayUrl = $this->isTestMode()
            ? 'https://test.ccavenue.com/transaction/transaction.do?command=initiateTransaction'
            : 'https://secure.ccavenue.com/transaction/transaction.do?command=initiateTransaction';
    }

    /**
     * Validate payment parameters and return ready status
     *
     * @return array
     */
    public function attemptPayment(array $params)
    {
        $requiredParams = ['amount', 'currency', 'order_id', 'customer_email'];

        foreach ($requiredParams as $param) {
            if (! isset($params[$param]) || empty($params[$param])) {
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.ccavenue_missing_param', ['param' => $param]),
                ];
            }
        }

        return [
            'status' => 'ready',
            'message' => __('admin/modules.ccavenue_ready'),
            'data' => [
                'payment_method' => 'ccavenue',
                'amount' => $params['amount'],
                'currency' => $params['currency'],
                'order_id' => $params['order_id'],
                'redirect_url' => $this->gatewayUrl,
            ],
        ];
    }

    /**
     * Build encrypted request data and return redirect to CCAvenue hosted page
     *
     * @return array
     */
    public function makePayment(array $params)
    {
        $merchantId = $this->getConfig('merchant_id');
        $accessCode = $this->getConfig('access_code');
        $workingKey = $this->getConfig('working_key');
        $currency = $params['currency'] ?? $this->getConfig('currency', 'INR');

        $requestData = $this->buildRequestString([
            'merchant_id' => $merchantId,
            'order_id' => $params['order_id'],
            'currency' => $currency,
            'amount' => number_format($params['amount'], 2, '.', ''),
            'redirect_url' => $params['success_url'] ?? '',
            'cancel_url' => $params['cancel_url'] ?? '',
            'language' => $params['language'] ?? 'EN',
            'billing_name' => $params['customer_name'] ?? '',
            'billing_email' => $params['customer_email'] ?? '',
            'billing_tel' => $params['customer_phone'] ?? '',
            'billing_address' => $params['customer_address'] ?? '',
            'billing_city' => $params['customer_city'] ?? '',
            'billing_state' => $params['customer_state'] ?? '',
            'billing_zip' => $params['customer_postcode'] ?? '',
            'billing_country' => $params['customer_country'] ?? '',
            'merchant_param1' => $params['merchant_param1'] ?? '',
            'merchant_param2' => $params['merchant_param2'] ?? '',
        ]);

        $encryptedData = $this->encrypt($requestData, $workingKey);

        return [
            'status' => 'redirect',
            'message' => __('admin/modules.ccavenue_redirect'),
            'redirect_url' => $this->gatewayUrl,
            'redirect_method' => 'POST',
            'redirect_params' => [
                'encRequest' => $encryptedData,
                'access_code' => $accessCode,
            ],
            'data' => [
                'order_id' => $params['order_id'],
                'amount' => $params['amount'],
                'currency' => $currency,
            ],
        ];
    }

    /**
     * Decrypt CCAvenue response and process payment result
     *
     * CCAvenue order_status values: Success, Failure, Aborted, Invalid
     *
     * @return array
     */
    public function handlePaymentCallback(array $params)
    {
        $workingKey = $this->getConfig('working_key');
        $encResponse = $params['encResp'] ?? '';

        if (empty($encResponse)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.ccavenue_empty_response'),
            ];
        }

        $decryptedResponse = $this->decrypt($encResponse, $workingKey);

        // Decrypt success IS the authentication (CCAvenue has no separate HMAC
        // field; the shared working key round-trip is the integrity guarantee).
        // Fail-closed: undecryptable envelope is NOT a valid payment.
        if (empty($decryptedResponse)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.ccavenue_decrypt_failed'),
            ];
        }

        // Parse decrypted response (key=value& query format)
        $responseParams = [];
        parse_str($decryptedResponse, $responseParams);

        $orderStatus = $responseParams['order_status'] ?? '';
        $trackingId = $responseParams['tracking_id'] ?? '';
        $returnedOrderId = $responseParams['order_id'] ?? '';

        // FAIL-CLOSED order binding: the decrypted order_id MUST match the
        // invoice this callback was invoked for (ref: WHMCS callback/
        // ccavenuev2.php:27 `if ($invoiceId != $passedInvoiceId) die`).
        // Guards against replay/cross-invoice envelope injection.
        $expectedOrderId = $params['order_id'] ?? $params['expected_order_id'] ?? $params['invoice_id'] ?? null;
        if ($expectedOrderId !== null && $expectedOrderId !== '') {
            // order_id may carry an appended suffix (`<invoiceid>_<ts>`); compare
            // both the full value and the leading invoice segment.
            $returnedInvoiceSegment = explode('_', (string) $returnedOrderId, 2)[0];
            $matches = $this->secureCompare((string) $expectedOrderId, (string) $returnedOrderId)
                || $this->secureCompare((string) $expectedOrderId, $returnedInvoiceSegment);
            if (! $matches) {
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.ccavenue_order_mismatch'),
                    'transaction_id' => $trackingId,
                ];
            }
        }

        switch ($orderStatus) {
            case 'Success':
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.ccavenue_payment_success'),
                    'transaction_id' => $trackingId,
                    'data' => [
                        'tracking_id' => $trackingId,
                        'order_id' => $responseParams['order_id'] ?? '',
                        'amount' => $responseParams['amount'] ?? '',
                        'currency' => $responseParams['currency'] ?? '',
                        'bank_ref_no' => $responseParams['bank_ref_no'] ?? '',
                        'payment_mode' => $responseParams['payment_mode'] ?? '',
                        'card_name' => $responseParams['card_name'] ?? '',
                        'billing_name' => $responseParams['billing_name'] ?? '',
                        'billing_email' => $responseParams['billing_email'] ?? '',
                    ],
                ];

            case 'Aborted':
                return [
                    'status' => 'cancelled',
                    'message' => __('admin/modules.ccavenue_payment_aborted'),
                    'transaction_id' => $trackingId,
                    'data' => $responseParams,
                ];

            case 'Failure':
                return [
                    'status' => 'failed',
                    'message' => __('admin/modules.ccavenue_payment_failed'),
                    'transaction_id' => $trackingId,
                    'data' => [
                        'tracking_id' => $trackingId,
                        'order_id' => $responseParams['order_id'] ?? '',
                        'failure_message' => $responseParams['failure_message'] ?? '',
                        'status_message' => $responseParams['status_message'] ?? '',
                    ],
                ];

            case 'Invalid':
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.ccavenue_payment_invalid'),
                    'transaction_id' => $trackingId,
                    'data' => $responseParams,
                ];

            default:
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.ccavenue_unknown_status'),
                    'data' => $responseParams,
                ];
        }
    }

    /**
     * Refund payment via CCAvenue API with encrypted request
     *
     * @return array
     */
    public function refundPayment(array $params)
    {
        $workingKey = $this->getConfig('working_key');
        $accessCode = $this->getConfig('access_code');

        $refundData = json_encode([
            'reference_no' => $params['transaction_id'],
            'refund_amount' => number_format($params['amount'], 2, '.', ''),
            'refund_ref_no' => $params['refund_ref_no'] ?? ('REF' . time()),
        ]);

        $encryptedRequest = $this->encrypt($refundData, $workingKey);

        // DoWebTrans refundOrder form-post (ref: CCAvenue API — command +
        // enc_request + JSON request/response types). Uses postForm() helper
        // (PRC-888): TLS-verify ON + timeout foundation, without touching the
        // global callApi().
        $result = $this->postForm($this->refundUrl, [
            'enc_request' => $encryptedRequest,
            'access_code' => $accessCode,
            'command' => 'refundOrder',
            'request_type' => 'JSON',
            'response_type' => 'JSON',
        ], ['Accept: application/json']);

        if (! empty($result['error'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.ccavenue_refund_connection_error'),
            ];
        }

        // CCAvenue returns urlencoded `enc_response=<hex>` (or JSON) — parse both.
        $raw = (string) ($result['raw_response'] ?? '');
        $encResponse = '';
        $decoded = json_decode($raw, true);
        if (is_array($decoded) && ! empty($decoded['enc_response'])) {
            $encResponse = $decoded['enc_response'];
        } else {
            $parsed = [];
            parse_str($raw, $parsed);
            $encResponse = $parsed['enc_response'] ?? '';
        }

        if (empty($encResponse)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.ccavenue_refund_failed'),
            ];
        }

        $decryptedResponse = $this->decrypt($encResponse, $workingKey);
        if (empty($decryptedResponse)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.ccavenue_decrypt_failed'),
            ];
        }

        $refundResult = json_decode($decryptedResponse, true);

        if (is_array($refundResult) && (string) ($refundResult['refund_status'] ?? '') === '0') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.ccavenue_refund_success'),
                'refund_id' => $refundResult['refund_ref_no'] ?? '',
                'data' => [
                    'refund_status' => $refundResult['refund_status'] ?? '',
                    'refund_ref_no' => $refundResult['refund_ref_no'] ?? '',
                    'reason' => $refundResult['reason'] ?? '',
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.ccavenue_refund_failed'),
            'data' => [
                'refund_status' => is_array($refundResult) ? ($refundResult['refund_status'] ?? '') : '',
                'reason' => is_array($refundResult) ? ($refundResult['reason'] ?? '') : '',
            ],
        ];
    }

    /**
     * Generate a CCAvenue payment envelope.
     *
     * CCAvenue initiateTransaction requires a POST form submit (encRequest +
     * access_code) — all references use `<form method="POST">` (WHMCS
     * ccavenuev2.php:45, HostBill class.ccavenue.php:31). A bare GET link is
     * unsupported and would leak the encrypted request into the URL/logs/referer,
     * so we return the POST-form redirect envelope, not a query-string URL.
     *
     * @return array
     */
    public function generatePaymentLink(array $params)
    {
        $makePaymentResult = $this->makePayment($params);

        if (($makePaymentResult['status'] ?? '') !== 'redirect') {
            return [
                'status' => 'error',
                'message' => __('admin/modules.ccavenue_payment_link_failed'),
            ];
        }

        return [
            'status' => 'redirect',
            'message' => __('admin/modules.ccavenue_redirect'),
            'redirect_url' => $makePaymentResult['redirect_url'],
            'redirect_method' => 'POST',
            'redirect_params' => $makePaymentResult['redirect_params'],
            'data' => [
                'order_id' => $params['order_id'] ?? '',
                'amount' => $params['amount'] ?? '',
            ],
        ];
    }

    /**
     * Test connection - CCAvenue has no test API endpoint,
     * return success if configuration is valid
     *
     * @return array
     */
    public function testConnection(array $params = [])
    {
        $configValidation = $this->validateConfig();

        if ($configValidation['status'] === 'error') {
            return [
                'status' => 'error',
                'message' => __('admin/modules.ccavenue_connection_failed'),
                'data' => $configValidation,
            ];
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.ccavenue_connection_success'),
            'data' => [
                'merchant_id' => $this->getConfig('merchant_id'),
                'test_mode' => $this->isTestMode(),
                'gateway_url' => $this->gatewayUrl,
            ],
        ];
    }

    /**
     * Validate required configuration fields
     *
     * @return array
     */
    public function validateConfig()
    {
        $required = ['merchant_id', 'access_code', 'working_key'];
        $missing = [];

        foreach ($required as $field) {
            if (empty($this->config[$field])) {
                $missing[] = $field;
            }
        }

        if (! empty($missing)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.ccavenue_config_missing', ['fields' => implode(', ', $missing)]),
            ];
        }

        return [
            'status' => 'success',
            'message' => __('admin/modules.ccavenue_config_valid'),
        ];
    }

    /**
     * CCAvenue supported features
     */
    public function getFeatures(): array
    {
        return [
            'refund' => true,
            '3d_secure' => false,
            'save_card' => false,
            'recurring' => false,
            'fraud_check' => false,
        ];
    }

    /**
     * Return supported features (alias for AbstractPayment compatibility)
     */
    public function getSupportedFeatures(): array
    {
        return $this->getFeatures();
    }

    /**
     * Get configuration fields for admin panel
     */
    public function getConfigFields(): array
    {
        return array_merge(parent::getConfigFields(), [
            'merchant_id' => [
                'type' => 'text',
                'label' => __('admin/modules.ccavenue_merchant_id'),
                'description' => __('admin/modules.ccavenue_merchant_id_desc'),
                'required' => true,
                'value' => '',
                'placeholder' => __('admin/modules.ccavenue_merchant_id_placeholder'),
            ],
            'access_code' => [
                'type' => 'text',
                'label' => __('admin/modules.ccavenue_access_code'),
                'description' => __('admin/modules.ccavenue_access_code_desc'),
                'required' => true,
                'value' => '',
                'placeholder' => __('admin/modules.ccavenue_access_code_placeholder'),
            ],
            'working_key' => [
                'type' => 'password',
                'label' => __('admin/modules.ccavenue_working_key'),
                'description' => __('admin/modules.ccavenue_working_key_desc'),
                'required' => true,
                'value' => '',
                'placeholder' => '',
            ],
            'currency' => [
                'type' => 'select',
                'label' => __('admin/modules.ccavenue_currency'),
                'description' => __('admin/modules.ccavenue_currency_desc'),
                'required' => true,
                'value' => 'INR',
                'options' => [
                    'INR' => 'INR - Indian Rupee',
                    'USD' => 'USD - US Dollar',
                    'EUR' => 'EUR - Euro',
                    'GBP' => 'GBP - British Pound',
                ],
            ],
        ]);
    }

    /**
     * Encrypt data using AES-128-CBC with CCAvenue working key.
     *
     * Key = hex2bin(md5(working_key)) (16 byte); IV = pack('C*',0..15) SABİT
     * (ref: WHMCS ccavenue/lib/CCAvenue.php:20, HostBill class.ccavenue.php:79).
     */
    private function encrypt(string $plainText, string $workingKey): string
    {
        $key = hex2bin(md5($workingKey));
        $iv = pack('C*', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);

        $encrypted = openssl_encrypt(
            $plainText,
            'AES-128-CBC',
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );

        return $encrypted === false ? '' : bin2hex($encrypted);
    }

    /**
     * Decrypt data using AES-128-CBC with CCAvenue working key (same key/IV).
     */
    private function decrypt(string $encryptedText, string $workingKey): string
    {
        $key = hex2bin(md5($workingKey));
        $iv = pack('C*', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);

        $encryptedBinary = @hex2bin($encryptedText);
        if ($encryptedBinary === false) {
            return '';
        }

        $decrypted = openssl_decrypt(
            $encryptedBinary,
            'AES-128-CBC',
            $key,
            OPENSSL_RAW_DATA,
            $iv
        );

        return $decrypted === false ? '' : $decrypted;
    }

    /**
     * Build request string in key=value& format for CCAvenue
     */
    private function buildRequestString(array $params): string
    {
        $parts = [];

        foreach ($params as $key => $value) {
            $parts[] = $key . '=' . $value;
        }

        return implode('&', $parts);
    }
}
