<?php

namespace App\Modules\Payment\UsaEpay;

use App\Core\Payment\AbstractPayment;

class UsaEpay extends AbstractPayment
{
    /** Legacy gate endpoint — test-aware (ref: WHMCS-decoded usaepay_capture; HostBill lib usaepay.php:196-197). */
    protected function gateUrl(): string
    {
        return $this->isTestMode()
            ? 'https://sandbox.usaepay.com/gate'
            : 'https://www.usaepay.com/gate';
    }

    /**
     * Validate parameters and perform pre-fraud check before payment
     */
    public function attemptPayment(array $params)
    {
        $requiredParams = ['amount', 'currency', 'order_id', 'card_number', 'card_expiry', 'cvv'];

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

        // Fraud check
        $fraudCheck = $this->performPreFraudCheck($params);
        if ($fraudCheck['status'] === 'error') {
            return $fraudCheck;
        }

        return [
            'status' => 'ready',
            'message' => __('admin/modules.usaepay_ready'),
            'data' => [
                'payment_method' => 'usaepay',
                'amount' => $params['amount'],
                'currency' => $params['currency'],
                'order_id' => $params['order_id'],
            ],
        ];
    }

    /**
     * Process payment via USA ePay (cc:sale)
     */
    public function makePayment(array $params)
    {
        $cardNumber = str_replace([' ', '-'], '', $params['card_number']);
        $expiryMonth = str_pad($params['expiry_month'] ?? $params['card_expiry_month'] ?? '', 2, '0', STR_PAD_LEFT);
        $expiryYear = substr($params['expiry_year'] ?? $params['card_expiry_year'] ?? '', -2);
        $expDate = $expiryMonth . $expiryYear;

        $amount = number_format((float) $params['amount'], 2, '.', '');
        $orderId = (string) ($params['order_id'] ?? '');

        $postData = $this->buildPostData([
            'UMcommand' => 'cc:sale',
            // WHMCS usaepay_capture:40 — suppress accidental double-charge on retry.
            'UMignoreDuplicate' => 'yes',
            'UMcard' => $cardNumber,
            'UMexpir' => $expDate,
            'UMcvv2' => $params['cvv'] ?? '',
            'UMamount' => $amount,
            'UMinvoice' => $orderId,
            'UMname' => $params['card_holder'] ?? $params['billing_first_name'] ?? '',
            'UMemail' => $params['customer_email'] ?? $params['email'] ?? '',
            // AVS street/zip — caller billing_address sub-array uses WHMCS-canonical
            // keys (address1 / postcode; ref WHMCS usaepay_capture:46-47).
            'UMstreet' => $params['billing_street'] ?? $params['billing_address']['address1'] ?? '',
            'UMzip' => $params['billing_zip'] ?? $params['billing_address']['postcode'] ?? '',
            // WHMCS usaepay_capture:49 — buyer IP for fraud/AVS scoring.
            'UMip' => $params['client_ip'] ?? '',
        ]);

        // PIN-derived seeded UMhash — only emitted when a PIN is configured (ref: WHMCS usaepay_capture).
        $postData = $this->withHash($postData, 'cc:sale', $amount, $orderId);

        $response = $this->callUsaEpayApi($postData);

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

        $parsed = $this->parseResponse($response);

        $umStatus = $parsed['UMstatus'] ?? '';
        $umRefNum = $parsed['UMrefNum'] ?? '';

        if (strtolower($umStatus) === 'approved') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.usaepay_payment_success'),
                'transaction_id' => $umRefNum,
                'data' => [
                    'ref_num' => $umRefNum,
                    'auth_code' => $parsed['UMauthCode'] ?? '',
                    'avs_result' => $parsed['UMavsResult'] ?? '',
                    'cvv_result' => $parsed['UMcvv2Result'] ?? '',
                    'result' => $parsed['UMresult'] ?? '',
                ],
            ];
        }

        if (strtolower($umStatus) === 'declined') {
            return [
                'status' => 'failed',
                'message' => __('admin/modules.usaepay_payment_declined'),
                'data' => [
                    'ref_num' => $umRefNum,
                    'error' => $parsed['UMerror'] ?? '',
                    'result' => $parsed['UMresult'] ?? '',
                ],
            ];
        }

        // Error status
        return [
            'status' => 'error',
            'message' => $parsed['UMerror'] ?? __('admin/modules.usaepay_payment_failed'),
            'data' => [
                'ref_num' => $umRefNum,
                'error' => $parsed['UMerror'] ?? '',
                'result' => $parsed['UMresult'] ?? '',
            ],
        ];
    }

    /**
     * Handle payment receipt callback — FAIL-CLOSED signature verification.
     *
     * USA ePay legacy gate is synchronous: the authoritative approval arrives in
     * makePayment()'s response. This endpoint (receipt POST-back) is only trusted
     * when it carries a PIN-derived UMhash that this module can recompute and match
     * via hash_equals. No PIN configured, missing hash, or mismatch → rejected;
     * a payment is NEVER marked success on an unverified callback (ref: gate PIN
     * hash contract — WHMCS/HostBill lib usaepay.php:186-193).
     */
    public function handlePaymentCallback(array $params)
    {
        $umStatus = $params['UMstatus'] ?? $params['umstatus'] ?? '';
        $umRefNum = $params['UMrefNum'] ?? $params['umrefnum'] ?? '';
        $umError = $params['UMerror'] ?? $params['umerror'] ?? '';
        $umAmount = (string) ($params['UMamount'] ?? $params['umamount'] ?? '');
        $umInvoice = (string) ($params['UMinvoice'] ?? $params['uminvoice'] ?? '');
        $received = (string) ($params['UMhash'] ?? $params['umhash'] ?? '');

        if (! $this->verifyCallbackHash($received, 'cc:sale', $umAmount, $umInvoice)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.usaepay_callback_signature_invalid'),
                'data' => ['ref_num' => $umRefNum],
            ];
        }

        switch (strtolower($umStatus)) {
            case 'approved':
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.usaepay_payment_success'),
                    'transaction_id' => $umRefNum,
                    'data' => [
                        'ref_num' => $umRefNum,
                        'auth_code' => $params['UMauthCode'] ?? '',
                    ],
                ];
            case 'declined':
                return [
                    'status' => 'failed',
                    'message' => __('admin/modules.usaepay_payment_declined'),
                    'data' => [
                        'ref_num' => $umRefNum,
                        'error' => $umError,
                    ],
                ];
            case 'error':
                return [
                    'status' => 'error',
                    'message' => $umError ?: __('admin/modules.usaepay_payment_error'),
                    'data' => [
                        'ref_num' => $umRefNum,
                        'error' => $umError,
                    ],
                ];
            default:
                return [
                    'status' => 'failed',
                    'message' => __('admin/modules.usaepay_callback_invalid'),
                    'data' => $params,
                ];
        }
    }

    /**
     * Process refund via USA ePay (cc:credit with original UMrefNum)
     */
    public function refundPayment(array $params)
    {
        if (empty($params['transaction_id'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.usaepay_refund_missing_transaction'),
            ];
        }

        $amount = number_format((float) ($params['amount'] ?? 0), 2, '.', '');
        $orderId = (string) ($params['order_id'] ?? '');

        $postData = $this->buildPostData([
            'UMcommand' => 'cc:credit',
            'UMrefNum' => $params['transaction_id'],
            'UMamount' => $amount,
            'UMinvoice' => $orderId,
        ]);

        $postData = $this->withHash($postData, 'cc:credit', $amount, $orderId);

        $response = $this->callUsaEpayApi($postData);

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

        $parsed = $this->parseResponse($response);

        $umStatus = $parsed['UMstatus'] ?? '';
        $umRefNum = $parsed['UMrefNum'] ?? '';

        if (strtolower($umStatus) === 'approved') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.usaepay_refund_success'),
                'refund_id' => $umRefNum,
                'data' => [
                    'ref_num' => $umRefNum,
                    'result' => $parsed['UMresult'] ?? '',
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => $parsed['UMerror'] ?? __('admin/modules.usaepay_refund_failed'),
            'data' => [
                'ref_num' => $umRefNum,
                'error' => $parsed['UMerror'] ?? '',
                'result' => $parsed['UMresult'] ?? '',
            ],
        ];
    }

    /**
     * Payment link generation is not supported for direct CC processing
     */
    public function generatePaymentLink(array $params)
    {
        return [
            'status' => 'error',
            'message' => __('admin/modules.usaepay_payment_link_not_supported'),
        ];
    }

    /**
     * Save card via USA ePay (cc:save), returns UMcardRef
     */
    public function saveCard(array $params)
    {
        $cardNumber = str_replace([' ', '-'], '', $params['card_number'] ?? '');
        $expiryMonth = str_pad($params['expiry_month'] ?? '', 2, '0', STR_PAD_LEFT);
        $expiryYear = substr($params['expiry_year'] ?? '', -2);
        $expDate = $expiryMonth . $expiryYear;

        $postData = $this->buildPostData([
            'UMcommand' => 'cc:save',
            'UMcard' => $cardNumber,
            'UMexpir' => $expDate,
            'UMname' => $params['card_holder'] ?? '',
            'UMemail' => $params['email'] ?? '',
        ]);

        $postData = $this->withHash($postData, 'cc:save', '0.00', '');

        $response = $this->callUsaEpayApi($postData);

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

        $parsed = $this->parseResponse($response);

        $umStatus = $parsed['UMstatus'] ?? '';
        $cardRef = $parsed['UMcardRef'] ?? '';

        if (strtolower($umStatus) === 'approved' && ! empty($cardRef)) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.usaepay_card_saved'),
                'data' => [
                    'card_ref' => $cardRef,
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => $parsed['UMerror'] ?? __('admin/modules.usaepay_card_save_failed'),
            'data' => [
                'error' => $parsed['UMerror'] ?? '',
            ],
        ];
    }

    /**
     * Pay with a saved card using UMcardRef
     */
    public function payWithSavedCard(array $params)
    {
        if (empty($params['card_ref'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.usaepay_missing_card_ref'),
            ];
        }

        $amount = number_format((float) ($params['amount'] ?? 0), 2, '.', '');
        $orderId = (string) ($params['order_id'] ?? '');

        $postData = $this->buildPostData([
            'UMcommand' => 'cc:sale',
            'UMcardRef' => $params['card_ref'],
            'UMamount' => $amount,
            'UMinvoice' => $orderId,
            'UMemail' => $params['email'] ?? '',
        ]);

        $postData = $this->withHash($postData, 'cc:sale', $amount, $orderId);

        $response = $this->callUsaEpayApi($postData);

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

        $parsed = $this->parseResponse($response);

        $umStatus = $parsed['UMstatus'] ?? '';
        $umRefNum = $parsed['UMrefNum'] ?? '';

        if (strtolower($umStatus) === 'approved') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.usaepay_payment_success'),
                'transaction_id' => $umRefNum,
                'data' => [
                    'ref_num' => $umRefNum,
                    'auth_code' => $parsed['UMauthCode'] ?? '',
                    'result' => $parsed['UMresult'] ?? '',
                ],
            ];
        }

        if (strtolower($umStatus) === 'declined') {
            return [
                'status' => 'failed',
                'message' => __('admin/modules.usaepay_payment_declined'),
                'data' => [
                    'ref_num' => $umRefNum,
                    'error' => $parsed['UMerror'] ?? '',
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => $parsed['UMerror'] ?? __('admin/modules.usaepay_payment_failed'),
            'data' => [
                'ref_num' => $umRefNum,
                'error' => $parsed['UMerror'] ?? '',
            ],
        ];
    }

    /**
     * Delete a saved card using UMcardRef
     */
    public function deleteCard(array $params)
    {
        if (empty($params['card_ref'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.usaepay_missing_card_ref'),
            ];
        }

        $postData = $this->buildPostData([
            'UMcommand' => 'cc:delete',
            'UMcardRef' => $params['card_ref'],
        ]);

        $postData = $this->withHash($postData, 'cc:delete', '0.00', '');

        $response = $this->callUsaEpayApi($postData);

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

        $parsed = $this->parseResponse($response);

        $umStatus = $parsed['UMstatus'] ?? '';

        if (strtolower($umStatus) === 'approved') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.usaepay_card_deleted'),
            ];
        }

        return [
            'status' => 'error',
            'message' => $parsed['UMerror'] ?? __('admin/modules.usaepay_card_delete_failed'),
            'data' => [
                'error' => $parsed['UMerror'] ?? '',
            ],
        ];
    }

    /**
     * Test connection by sending an auth-only test transaction
     */
    public function testConnection(array $params = [])
    {
        $postData = $this->buildPostData([
            'UMcommand' => 'cc:authonly',
            'UMcard' => '4000100011112224',
            'UMexpir' => '1230',
            'UMamount' => '0.00',
        ]);

        $postData = $this->withHash($postData, 'cc:authonly', '0.00', '');

        $response = $this->callUsaEpayApi($postData);

        if ($response === null) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.usaepay_connection_failed'),
            ];
        }

        $parsed = $this->parseResponse($response);

        $umStatus = $parsed['UMstatus'] ?? '';
        $umError = $parsed['UMerror'] ?? '';

        // Check for authentication-related errors
        if (stripos($umError, 'invalid key') !== false || stripos($umError, 'authentication') !== false) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.usaepay_auth_failed'),
            ];
        }

        // Any non-error response (even declined) means credentials are valid
        if (strtolower($umStatus) !== 'error') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.usaepay_connection_success'),
            ];
        }

        return [
            'status' => 'error',
            'message' => $umError ?: __('admin/modules.usaepay_connection_failed'),
        ];
    }

    /**
     * Validate configuration fields
     */
    public function validateConfig()
    {
        $required = ['source_key'];
        $missing = [];

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

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

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

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

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

    /**
     * Get configuration fields for admin panel
     */
    public function getConfigFields(): array
    {
        return array_merge(parent::getConfigFields(), [
            'source_key' => [
                'type' => 'text',
                'label' => 'Source Key',
                'description' => 'USA ePay Source Key (API Key)',
                'required' => true,
                'value' => '',
                'placeholder' => 'Enter Source Key',
            ],
            'pin' => [
                'type' => 'password',
                'label' => 'PIN',
                'description' => 'USA ePay PIN for hash verification',
                'required' => false,
                'value' => '',
                'placeholder' => 'Enter PIN',
            ],
            'currency' => [
                'type' => 'select',
                'label' => 'Currency',
                'description' => 'Default currency for payments',
                'required' => true,
                'value' => 'USD',
                'options' => [
                    'USD' => 'US Dollar (USD)',
                ],
            ],
        ]);
    }

    // -------------------------------------------------------------------------
    // Private Helpers
    // -------------------------------------------------------------------------

    /**
     * Append the seeded, wrapped UMhash — only when a PIN is configured.
     * (ref: WHMCS usaepay_capture emits UMhash only when PIN set; HostBill lib usaepay.php:186-193.)
     */
    private function withHash(array $postData, string $command, string $amount, string $invoice): array
    {
        $pin = trim((string) $this->getConfig('pin', ''));
        if ($pin === '') {
            return $postData;
        }

        [$seed, $hash] = $this->seededHash($command, $pin, $amount, $invoice);
        $postData['UMhash'] = 's/' . $seed . '/' . $hash . '/n';

        return $postData;
    }

    /**
     * Seeded SHA1 hash: sha1("command:pin:amount:invoice:seed"), seed = microtime.rand
     * (ref: HostBill lib usaepay.php:186-193 / WHMCS _usaepay_calculate_UMhash).
     *
     * @return array{0:string,1:string} [seed, sha1hex]
     */
    private function seededHash(string $command, string $pin, string $amount, string $invoice, ?string $seed = null): array
    {
        $seed = $seed ?? (microtime(true) . random_int(0, PHP_INT_MAX));
        $prehash = $command . ':' . $pin . ':' . $amount . ':' . $invoice . ':' . $seed;

        return [$seed, sha1($prehash)];
    }

    /**
     * FAIL-CLOSED verification of an inbound receipt UMhash (wrapped 's/{seed}/{sha1}/n').
     * Returns false on no PIN, missing/malformed hash, or mismatch — via hash_equals (secureCompare).
     */
    private function verifyCallbackHash(string $received, string $command, string $amount, string $invoice): bool
    {
        $pin = trim((string) $this->getConfig('pin', ''));
        if ($pin === '' || $received === '') {
            return false;
        }

        // Expected wrapper: s/{seed}/{sha1hex}/n — extract seed, recompute, timing-safe compare.
        if (! preg_match('#^s/(.+)/([0-9a-f]{40})/n$#i', $received, $m)) {
            return false;
        }

        [, $expectedHash] = $this->seededHash($command, $pin, $amount, $invoice, $m[1]);

        return $this->secureCompare($expectedHash, strtolower($m[2]));
    }

    /**
     * Build POST data array with common fields (UMkey)
     */
    private function buildPostData(array $data): array
    {
        return array_merge([
            'UMkey' => $this->getConfig('source_key', ''),
        ], $data);
    }

    /**
     * Parse URL-encoded response string into associative array
     */
    private function parseResponse(string $response): array
    {
        $result = [];
        $pairs = explode('&', $response);

        foreach ($pairs as $pair) {
            $parts = explode('=', $pair, 2);
            if (count($parts) === 2) {
                $result[urldecode($parts[0])] = urldecode($parts[1]);
            }
        }

        return $result;
    }

    /**
     * Call USA ePay legacy gate (form-urlencoded) via the PRC-888 postForm() helper
     * (TLS peer+host verify + HTTPS-only protocols + timeout). Returns the last non-empty
     * response line (gate may prefix diagnostics — ref: WHMCS explode("\n") last line).
     */
    private function callUsaEpayApi(array $data): ?string
    {
        $result = $this->postForm($this->gateUrl(), $data, [
            'User-Agent: ProCP Payment Gateway Module',
        ], ['timeout' => 60]);

        if (! empty($result['error']) || (int) ($result['http_code'] ?? 0) === 0) {
            return null;
        }

        $raw = (string) ($result['raw_response'] ?? '');
        if ($raw === '') {
            return null;
        }

        $lines = array_values(array_filter(array_map('trim', explode("\n", $raw)), fn ($l) => $l !== ''));

        return empty($lines) ? $raw : end($lines);
    }
}
