<?php

namespace App\Modules\Payment\SSLCommerz;

use App\Core\Payment\AbstractPayment;

class SSLCommerz extends AbstractPayment
{
    protected $apiUrl;

    public function __construct(array $config = [])
    {
        parent::__construct($config);
        $this->apiUrl = $this->getApiUrl();
    }

    /**
     * Validate payment parameters before processing
     */
    public function attemptPayment(array $params)
    {
        $requiredParams = ['amount', 'currency', 'customer_name', 'customer_email', 'customer_phone'];

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

        if ($params['amount'] <= 0) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.sslcommerz_invalid_amount'),
                'data' => [],
            ];
        }

        return [
            'status' => 'ready',
            'message' => __('admin/modules.sslcommerz_ready'),
            'data' => [
                'amount' => $params['amount'],
                'currency' => strtoupper($params['currency']),
                'customer_name' => $params['customer_name'],
                'customer_email' => $params['customer_email'],
            ],
        ];
    }

    /**
     * Initialize a payment session via SSLCommerz API and redirect to gateway page
     * POST /gwprocess/v4/api.php
     */
    public function makePayment(array $params)
    {
        $currency = strtoupper($params['currency'] ?? $this->getConfig('currency', 'BDT'));
        $tranId = $params['tran_id'] ?? $params['order_id'] ?? $params['invoice_id'] ?? $this->generateTransactionId();

        // WiseCP ref cus_name = firstname.' '.lastname (SSLCommerz.php:100).
        $cusName = trim(($params['customer_name'] ?? '') . ' ' . ($params['customer_surname'] ?? ''));

        // IPN endpoint (ref post_data['ipn_url'] :103) — server-to-server notification.
        // Fall back to success_url so the IPN channel is always registered.
        $ipnUrl = $params['ipn_url'] ?? $params['success_url'] ?? '';

        $postData = [
            'store_id' => $this->getConfig('store_id'),
            'store_passwd' => $this->getConfig('store_passwd'),
            'total_amount' => $params['amount'],
            'currency' => $currency,
            'tran_id' => $tranId,
            'success_url' => $params['success_url'] ?? '',
            'fail_url' => $params['fail_url'] ?? '',
            'cancel_url' => $params['cancel_url'] ?? '',
            'ipn_url' => $ipnUrl,
            'cus_name' => $cusName !== '' ? $cusName : ($params['customer_name'] ?? ''),
            'cus_email' => $params['customer_email'] ?? '',
            'cus_phone' => $params['customer_phone'] ?? '',
            'shipping_method' => $params['shipping_method'] ?? 'NO',
            'num_of_item' => $params['num_of_item'] ?? '1',
            'product_name' => $params['product_name'] ?? 'Service',
            'product_category' => $params['product_category'] ?? 'Service',
            'product_profile' => $params['product_profile'] ?? 'non-physical-goods',
        ];

        // Optional billing address fields
        if (isset($params['cus_add1'])) {
            $postData['cus_add1'] = $params['cus_add1'];
        }
        if (isset($params['cus_city'])) {
            $postData['cus_city'] = $params['cus_city'];
        }
        if (isset($params['cus_country'])) {
            $postData['cus_country'] = $params['cus_country'];
        }
        if (isset($params['cus_postcode'])) {
            $postData['cus_postcode'] = $params['cus_postcode'];
        }

        $response = $this->callSslCommerzApi('/gwprocess/v4/api.php', $postData);

        if ($response['http_code'] === 200 && isset($response['response']['GatewayPageURL']) && ! empty($response['response']['GatewayPageURL'])) {
            return [
                'status' => 'redirect',
                'message' => __('admin/modules.sslcommerz_redirect_to_gateway'),
                'redirect_url' => $response['response']['GatewayPageURL'],
                'payment_url' => $response['response']['GatewayPageURL'],
                'data' => [
                    'sessionkey' => $response['response']['sessionkey'] ?? '',
                    'tran_id' => $tranId,
                    'gateway_page_url' => $response['response']['GatewayPageURL'],
                ],
            ];
        }

        $errorMessage = $response['response']['failedreason']
            ?? $response['response']['error']
            ?? __('admin/modules.sslcommerz_session_init_failed');

        return [
            'status' => 'error',
            'message' => $errorMessage,
            'data' => $this->safeResponseData($response),
        ];
    }

    /**
     * Handle IPN (Instant Payment Notification) callback from SSLCommerz
     * Verify payment via validation API
     */
    public function handlePaymentCallback(array $params)
    {
        $valId = $params['val_id'] ?? null;
        $tranId = $params['tran_id'] ?? null;

        if (! $valId) {
            return [
                'status' => 'failed',
                'message' => __('admin/modules.sslcommerz_missing_val_id'),
                'data' => [],
            ];
        }

        // Verify with SSLCommerz validation API
        $verification = $this->verifyIPN($valId);

        if ($verification['status'] === 'error') {
            return [
                'status' => 'failed',
                'message' => $verification['message'],
                'data' => $verification,
            ];
        }

        $validationData = $verification['data'];
        $sslStatus = $validationData['status'] ?? '';
        $mappedStatus = $this->mapSSLCommerzStatus($sslStatus);

        // FAIL-CLOSED extra gates (WiseCP ref SSLCommerz.php:290):
        // ($status VALID/VALIDATED) && ($order_amount == $base_amount) && $risk_level == 0.
        // Only enforced when we are about to mark the invoice paid.
        if ($mappedStatus === 'success') {
            // (1) Amount cross-check: gateway-validated amount MUST equal the invoice
            // total we expect, otherwise an under/wrong-amount VALID must not flip paid.
            // base_amount = currency_amount (store-currency); amount = paid amount.
            $gatewayAmount = (float) ($validationData['currency_amount'] ?? $validationData['amount'] ?? 0);
            $expectedAmount = isset($params['amount']) ? (float) $params['amount'] : null;

            if ($expectedAmount !== null && $expectedAmount > 0
                && round($gatewayAmount, 2) !== round($expectedAmount, 2)) {
                return [
                    'status' => 'failed',
                    'message' => __('admin/modules.sslcommerz_amount_mismatch'),
                    'transaction_id' => $validationData['tran_id'] ?? $tranId,
                    'data' => [
                        'val_id' => $valId,
                        'tran_id' => $validationData['tran_id'] ?? $tranId,
                        'expected_amount' => $expectedAmount,
                        'gateway_amount' => $gatewayAmount,
                        'sslcommerz_status' => $sslStatus,
                    ],
                ];
            }

            // (2) risk_level gate: SSLCommerz flags risky transactions (risk_level>0);
            // treat non-zero risk as not-safe-to-settle.
            $riskLevel = (int) ($validationData['risk_level'] ?? 0);
            if ($riskLevel !== 0) {
                return [
                    'status' => 'failed',
                    'message' => __('admin/modules.sslcommerz_risk_flagged'),
                    'transaction_id' => $validationData['tran_id'] ?? $tranId,
                    'data' => [
                        'val_id' => $valId,
                        'tran_id' => $validationData['tran_id'] ?? $tranId,
                        'risk_level' => $riskLevel,
                        'sslcommerz_status' => $sslStatus,
                    ],
                ];
            }
        }

        return [
            'status' => $mappedStatus,
            'message' => __('admin/modules.sslcommerz_payment_status_' . $mappedStatus),
            'transaction_id' => $validationData['tran_id'] ?? $tranId,
            'data' => [
                'val_id' => $valId,
                'tran_id' => $validationData['tran_id'] ?? $tranId,
                'amount' => $validationData['amount'] ?? $params['amount'] ?? 0,
                'currency' => $validationData['currency'] ?? '',
                'card_type' => $validationData['card_type'] ?? '',
                // PCI: SSLCommerz sends a masked PAN; re-mask defensively, never surface raw.
                'card_no' => isset($validationData['card_no']) && $validationData['card_no'] !== ''
                    ? $this->maskCardNumber((string) $validationData['card_no'])
                    : '',
                'bank_tran_id' => $validationData['bank_tran_id'] ?? '',
                'card_issuer' => $validationData['card_issuer'] ?? '',
                'card_brand' => $validationData['card_brand'] ?? '',
                'card_issuer_country' => $validationData['card_issuer_country'] ?? '',
                'store_amount' => $validationData['store_amount'] ?? '',
                'tran_date' => $validationData['tran_date'] ?? '',
                'sslcommerz_status' => $sslStatus,
            ],
        ];
    }

    /**
     * Initiate refund via the SSLCommerz Refund API.
     * GET /validator/api/merchantTransIDvalidationAPI.php with refund action params.
     * Official refund-initiate requires bank_tran_id + refund_amount + refund_remarks;
     * response carries APIConnect + status (success|failed|processing) + refund_ref_id.
     */
    public function refundPayment(array $params)
    {
        $tranId = $params['transaction_id'] ?? $params['tran_id'] ?? null;
        $bankTranId = $params['bank_tran_id'] ?? null;

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

        // Refund is keyed by the gateway bank_tran_id, not the merchant tran_id.
        if (! $bankTranId) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.sslcommerz_missing_bank_tran_id'),
                'data' => [],
            ];
        }

        $query = http_build_query([
            'bank_tran_id' => $bankTranId,
            'refund_amount' => $params['amount'] ?? 0,
            'refund_remarks' => $params['reason'] ?? 'Refund initiated by admin',
            'store_id' => $this->getConfig('store_id'),
            'store_passwd' => $this->getConfig('store_passwd'),
            'v' => '1',
            'format' => 'json',
        ]);

        $response = $this->getSslCommerzApi('/validator/api/merchantTransIDvalidationAPI.php?' . $query);
        $body = $response['response'] ?? [];

        // Official refund success: APIConnect=DONE + status 'success' (+ refund_ref_id).
        // 'processing' means accepted-async; surface as pending, never fabricate success.
        $apiConnect = $body['APIConnect'] ?? '';
        $refundStatus = strtolower((string) ($body['status'] ?? ''));
        $refundRefId = $body['refund_ref_id'] ?? '';

        if ($response['http_code'] === 200 && $apiConnect === 'DONE' && $refundStatus === 'success') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.sslcommerz_refund_success'),
                'data' => [
                    'refund_ref_id' => $refundRefId,
                    'tran_id' => $tranId,
                    'bank_tran_id' => $bankTranId,
                    'amount' => $params['amount'] ?? 0,
                    'sslcommerz_status' => $refundStatus,
                ],
            ];
        }

        if ($response['http_code'] === 200 && $apiConnect === 'DONE' && $refundStatus === 'processing') {
            return [
                'status' => 'pending',
                'message' => __('admin/modules.sslcommerz_refund_processing'),
                'data' => [
                    'refund_ref_id' => $refundRefId,
                    'tran_id' => $tranId,
                    'bank_tran_id' => $bankTranId,
                    'amount' => $params['amount'] ?? 0,
                    'sslcommerz_status' => $refundStatus,
                ],
            ];
        }

        $errorMessage = $body['errorReason'] ?? $body['error'] ?? __('admin/modules.sslcommerz_refund_failed');

        return [
            'status' => 'error',
            'message' => $errorMessage,
            'data' => $this->safeResponseData($response),
        ];
    }

    /**
     * Generate a payment link (same flow as makePayment for SSLCommerz)
     */
    public function generatePaymentLink(array $params)
    {
        return $this->makePayment($params);
    }

    /**
     * Test connection by verifying store credentials with SSLCommerz
     */
    public function testConnection(array $params = [])
    {
        $postData = [
            'store_id' => $this->getConfig('store_id'),
            'store_passwd' => $this->getConfig('store_passwd'),
            'total_amount' => '10',
            'currency' => 'BDT',
            'tran_id' => 'TEST_' . time(),
            'success_url' => 'https://example.com/success',
            'fail_url' => 'https://example.com/fail',
            'cancel_url' => 'https://example.com/cancel',
            'cus_name' => 'Test Customer',
            'cus_email' => 'test@example.com',
            'cus_phone' => '01700000000',
            'shipping_method' => 'NO',
            'product_name' => 'Test',
            'product_category' => 'Test',
            'product_profile' => 'non-physical-goods',
        ];

        $response = $this->callSslCommerzApi('/gwprocess/v4/api.php', $postData);

        if ($response['http_code'] === 200 && isset($response['response']['GatewayPageURL']) && ! empty($response['response']['GatewayPageURL'])) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.sslcommerz_connection_success'),
                'data' => [
                    'store_id' => $this->getConfig('store_id'),
                ],
            ];
        }

        $errorMessage = $response['response']['failedreason']
            ?? $response['response']['error']
            ?? __('admin/modules.sslcommerz_connection_failed');

        return [
            'status' => 'error',
            'message' => $errorMessage,
            'data' => $this->safeResponseData($response),
        ];
    }

    /**
     * Validate module configuration - check required fields
     */
    public function validateConfig()
    {
        $required = ['store_id', 'store_passwd'];
        $missing = [];

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

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

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

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

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

    /**
     * Get configuration fields for admin panel
     */
    public function getConfigFields(): array
    {
        return array_merge(parent::getConfigFields(), [
            'store_id' => [
                'type' => 'text',
                'label' => 'Store ID',
                'description' => 'SSLCommerz Store ID provided in merchant panel',
                'required' => true,
                'value' => '',
                'placeholder' => 'Enter your Store ID',
            ],
            'store_passwd' => [
                'type' => 'password',
                'label' => 'Store Password',
                'description' => 'SSLCommerz Store Password (API/Secret Key)',
                'required' => true,
                'value' => '',
                'placeholder' => 'Enter your Store Password',
            ],
        ]);
    }

    /**
     * Get the appropriate API URL based on test/live mode
     */
    protected function getApiUrl(): string
    {
        return $this->isTestMode()
            ? 'https://sandbox.sslcommerz.com'
            : 'https://securepay.sslcommerz.com';
    }

    /**
     * Verify IPN (Instant Payment Notification) with SSLCommerz validation API
     * GET /validator/api/validationserverAPI.php?val_id=X&store_id=Y&store_passwd=Z
     */
    protected function verifyIPN(string $valId): array
    {
        $queryParams = http_build_query([
            'val_id' => $valId,
            'store_id' => $this->getConfig('store_id'),
            'store_passwd' => $this->getConfig('store_passwd'),
            'v' => '1',
            'format' => 'json',
        ]);

        $result = $this->getSslCommerzApi('/validator/api/validationserverAPI.php?' . $queryParams);
        $httpCode = $result['http_code'];
        $decoded = $result['response'];

        if ($httpCode !== 200 || ! is_array($decoded)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.sslcommerz_validation_api_failed'),
                'data' => [],
            ];
        }

        $status = $decoded['status'] ?? '';

        if (in_array($status, ['VALID', 'VALIDATED'])) {
            return [
                'status' => 'success',
                'message' => 'IPN verified successfully',
                'data' => $decoded,
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.sslcommerz_ipn_verification_failed'),
            'data' => $decoded,
        ];
    }

    /**
     * Form-urlencoded POST to SSLCommerz (JSON response) via PRC-888 postForm()
     * (inherits TLS-on + timeout + SSRF protocol guard). SSLCommerz replies JSON
     * to a form POST, so decode raw_response ourselves.
     */
    protected function callSslCommerzApi(string $endpoint, array $data): array
    {
        $result = $this->postForm($this->apiUrl . $endpoint, $data, [
            'User-Agent: ProCP/1.0 Payment Gateway Module',
        ]);

        return [
            'http_code' => (int) ($result['http_code'] ?? 0),
            'response' => json_decode((string) ($result['raw_response'] ?? ''), true),
            'raw_response' => (string) ($result['raw_response'] ?? ''),
        ];
    }

    /**
     * Hardened GET (TLS-on + timeout + SSRF protocol guard) for SSLCommerz
     * validation/refund query endpoints. Full URL already carries the query string.
     */
    protected function getSslCommerzApi(string $endpoint): array
    {
        $url = $this->apiUrl . $endpoint;

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
            CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS | CURLPROTO_HTTP,
            CURLOPT_HTTPHEADER => [
                'User-Agent: ProCP/1.0 Payment Gateway Module',
            ],
        ]);

        $response = curl_exec($ch);
        $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return [
            'http_code' => $httpCode,
            'response' => json_decode((string) $response, true),
            'raw_response' => $response === false ? '' : (string) $response,
        ];
    }

    /**
     * PCI/secret hygiene: strip raw gateway body + credentials before an error
     * envelope's 'data' propagates to callers/logs.
     */
    protected function safeResponseData(array $response): array
    {
        unset($response['raw_response']);

        if (isset($response['response']) && is_array($response['response'])) {
            unset($response['response']['store_passwd'], $response['response']['card_no']);
        }

        return $response;
    }

    /**
     * Map SSLCommerz payment status to internal status
     *
     * SSLCommerz statuses: VALID, VALIDATED, FAILED, CANCELLED, UNATTEMPTED, EXPIRED
     * Internal statuses: success, failed, pending
     */
    private function mapSSLCommerzStatus(string $sslStatus): string
    {
        return match ($sslStatus) {
            'VALID', 'VALIDATED' => 'success',
            'FAILED', 'CANCELLED', 'UNATTEMPTED', 'EXPIRED' => 'failed',
            'PENDING' => 'pending',
            default => 'failed',
        };
    }

    /**
     * Generate a unique transaction ID
     */
    private function generateTransactionId(): string
    {
        return 'procp_' . bin2hex(random_bytes(10));
    }
}
