<?php

namespace App\Modules\Payment\PayflowPro;

use App\Core\Payment\AbstractPayment;

class PayflowPro extends AbstractPayment
{
    protected $apiUrl;

    public function __construct(array $config = [])
    {
        parent::__construct($config);
        // NVP endpoint requires /transaction path (WHMCS payflowpro.php:122; official PayPal Payflow Pro NVP)
        $this->apiUrl = $this->isTestMode()
            ? 'https://pilot-payflowpro.paypal.com/transaction'
            : 'https://payflowpro.paypal.com/transaction';
    }

    /**
     * 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.payflowpro_missing_param', ['param' => $param]),
                ];
            }
        }

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

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

    /**
     * Process payment via PayPal Payflow Pro NVP (Sale transaction)
     */
    public function makePayment(array $params)
    {
        $cardNumber = str_replace([' ', '-'], '', $params['card_number']);
        // EXPDATE=MMYY (WHMCS payflowpro.php:135). Accept split month/year OR combined card_expiry.
        [$expiryMonth, $expiryYear] = $this->resolveExpiry($params);
        $expDate = $expiryMonth . $expiryYear;

        if ($expiryMonth === '' || $expiryYear === '') {
            return [
                'status' => 'error',
                'message' => __('admin/modules.payflowpro_missing_param', ['param' => 'card_expiry']),
                'data' => [],
            ];
        }

        $nvpData = [
            'PARTNER' => $this->getConfig('partner', ''),
            'VENDOR' => $this->getConfig('vendor', ''),
            'USER' => $this->getConfig('user', ''),
            'PWD' => $this->getConfig('password', ''),
            'TRXTYPE' => 'S',
            'TENDER' => 'C',
            'ACCT' => $cardNumber,
            'EXPDATE' => $expDate,
            'CVV2' => $params['cvv'] ?? '',
            'AMT' => number_format((float) $params['amount'], 2, '.', ''),
            'CURRENCY' => strtoupper($params['currency'] ?? $this->getConfig('currency', 'USD')),
            'COMMENT1' => (string) ($params['order_id'] ?? ''),
            'EMAIL' => $params['customer_email'] ?? $params['email'] ?? '',
            'FIRSTNAME' => $params['billing_first_name'] ?? $params['billing_address']['first_name'] ?? '',
            'LASTNAME' => $params['billing_last_name'] ?? $params['billing_address']['last_name'] ?? '',
        ];

        // Add optional billing address fields
        if (! empty($params['billing_street'] ?? $params['billing_address']['address'] ?? '')) {
            $nvpData['STREET'] = $params['billing_street'] ?? $params['billing_address']['address'] ?? '';
        }
        if (! empty($params['billing_city'] ?? $params['billing_address']['city'] ?? '')) {
            $nvpData['CITY'] = $params['billing_city'] ?? $params['billing_address']['city'] ?? '';
        }
        if (! empty($params['billing_state'] ?? $params['billing_address']['state'] ?? '')) {
            $nvpData['STATE'] = $params['billing_state'] ?? $params['billing_address']['state'] ?? '';
        }
        if (! empty($params['billing_zip'] ?? $params['billing_address']['zip'] ?? '')) {
            $nvpData['ZIP'] = $params['billing_zip'] ?? $params['billing_address']['zip'] ?? '';
        }
        if (! empty($params['billing_country'] ?? $params['billing_address']['country'] ?? '')) {
            $nvpData['BILLTOCOUNTRY'] = $params['billing_country'] ?? $params['billing_address']['country'] ?? '';
        }

        $response = $this->callPayflowApi($nvpData);

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

        $result = (int) ($response['RESULT'] ?? -1);
        $pnref = $response['PNREF'] ?? '';

        if ($result === 0) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.payflowpro_payment_success'),
                'transaction_id' => $pnref,
                'data' => [
                    'pnref' => $pnref,
                    'auth_code' => $response['AUTHCODE'] ?? '',
                    'avs_addr' => $response['AVSADDR'] ?? '',
                    'avs_zip' => $response['AVSZIP'] ?? '',
                    'cvv2_match' => $response['CVV2MATCH'] ?? '',
                    'iavs' => $response['IAVS'] ?? '',
                    'result' => $result,
                ],
            ];
        }

        return [
            'status' => 'failed',
            'message' => $this->getResultMessage($result, $response['RESPMSG'] ?? ''),
            'data' => [
                'result' => $result,
                'pnref' => $pnref,
                'response_message' => $response['RESPMSG'] ?? '',
            ],
        ];
    }

    /**
     * Handle payment callback / response verification
     */
    public function handlePaymentCallback(array $params)
    {
        // Payflow Pro is a direct server-to-server NVP gateway: PayPal publishes NO
        // return/webhook and NO callback signature (nothing to HMAC-verify). An external
        // caller supplying its own raw_response/RESULT=0 would be a fail-OPEN forgery.
        // Fail-CLOSED: only the module's own trusted NVP response (marked by makePayment's
        // caller path) is honoured; any external/client-originated call is rejected and can
        // NEVER be reported as a successful payment.
        if (empty($params['_internal_trusted'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.payflowpro_callback_not_supported'),
                'data' => [],
            ];
        }

        // Parse NVP response string if raw response is provided
        if (isset($params['raw_response']) && is_string($params['raw_response'])) {
            $params = array_merge($params, $this->parseNvpResponse($params['raw_response']));
        }

        $result = (int) ($params['RESULT'] ?? $params['result'] ?? -1);
        $pnref = $params['PNREF'] ?? $params['pnref'] ?? '';
        $responseMessage = $params['RESPMSG'] ?? $params['respmsg'] ?? '';

        switch ($result) {
            case 0:
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.payflowpro_payment_success'),
                    'transaction_id' => $pnref,
                    'data' => [
                        'pnref' => $pnref,
                        'auth_code' => $params['AUTHCODE'] ?? '',
                        'result' => $result,
                    ],
                ];
            case 12:
                return [
                    'status' => 'failed',
                    'message' => __('admin/modules.payflowpro_payment_declined'),
                    'data' => [
                        'result' => $result,
                        'pnref' => $pnref,
                        'response_message' => $responseMessage,
                    ],
                ];
            case 23:
                return [
                    'status' => 'failed',
                    'message' => __('admin/modules.payflowpro_invalid_account'),
                    'data' => [
                        'result' => $result,
                        'pnref' => $pnref,
                        'response_message' => $responseMessage,
                    ],
                ];
            case 24:
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.payflowpro_invalid_amount'),
                    'data' => [
                        'result' => $result,
                        'pnref' => $pnref,
                        'response_message' => $responseMessage,
                    ],
                ];
            case 50:
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.payflowpro_fraud_suspected'),
                    'data' => [
                        'result' => $result,
                        'pnref' => $pnref,
                        'response_message' => $responseMessage,
                    ],
                ];
            case 125:
            case 126:
            case 127:
                return [
                    'status' => 'error',
                    'message' => __('admin/modules.payflowpro_fraud_filter', ['code' => $result]),
                    'data' => [
                        'result' => $result,
                        'pnref' => $pnref,
                        'response_message' => $responseMessage,
                    ],
                ];
            default:
                return [
                    'status' => 'failed',
                    'message' => $this->getResultMessage($result, $responseMessage),
                    'data' => [
                        'result' => $result,
                        'pnref' => $pnref,
                        'response_message' => $responseMessage,
                    ],
                ];
        }
    }

    /**
     * Process refund via Payflow Pro (Credit transaction)
     */
    public function refundPayment(array $params)
    {
        if (empty($params['transaction_id'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.payflowpro_refund_missing_transaction'),
            ];
        }

        $nvpData = [
            'PARTNER' => $this->getConfig('partner', ''),
            'VENDOR' => $this->getConfig('vendor', ''),
            'USER' => $this->getConfig('user', ''),
            'PWD' => $this->getConfig('password', ''),
            'TRXTYPE' => 'C',
            'TENDER' => 'C',
            'ORIGID' => $params['transaction_id'],
        ];

        // Partial refund: include amount
        if (! empty($params['amount'])) {
            $nvpData['AMT'] = number_format((float) $params['amount'], 2, '.', '');
        }

        $response = $this->callPayflowApi($nvpData);

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

        $result = (int) ($response['RESULT'] ?? -1);
        $pnref = $response['PNREF'] ?? '';

        if ($result === 0) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.payflowpro_refund_success'),
                'refund_id' => $pnref,
                'data' => [
                    'pnref' => $pnref,
                    'result' => $result,
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => $this->getResultMessage($result, $response['RESPMSG'] ?? ''),
            'data' => [
                'result' => $result,
                'pnref' => $pnref,
                'response_message' => $response['RESPMSG'] ?? '',
            ],
        ];
    }

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

    /**
     * Test connection by sending a $0.00 authorization
     */
    public function testConnection(array $params = [])
    {
        $nvpData = [
            'PARTNER' => $this->getConfig('partner', ''),
            'VENDOR' => $this->getConfig('vendor', ''),
            'USER' => $this->getConfig('user', ''),
            'PWD' => $this->getConfig('password', ''),
            'TRXTYPE' => 'A',
            'TENDER' => 'C',
            'ACCT' => '4111111111111111',
            'EXPDATE' => '1230',
            'AMT' => '0.00',
        ];

        $response = $this->callPayflowApi($nvpData);

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

        $result = (int) ($response['RESULT'] ?? -999);

        // Strict success gate: only RESULT=0 ($0.00 auth approved) proves a valid,
        // authenticated, reachable gateway. Any non-zero code (auth failure, decline,
        // invalid account/amount/card, fraud filter) is reported as a failed connection —
        // no fabricated success on transaction-level errors (PRC-899 D2).
        if ($result === 0) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.payflowpro_connection_success'),
            ];
        }

        // Authentication errors (RESULT=1 user auth failed, 26 insufficient params/bad login)
        // get a credential-specific message; all other non-zero codes surface their own message.
        if (in_array($result, [1, 26], true)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.payflowpro_auth_failed'),
            ];
        }

        return [
            'status' => 'error',
            'message' => $this->getResultMessage($result, $response['RESPMSG'] ?? ''),
        ];
    }

    /**
     * Validate configuration fields
     */
    public function validateConfig()
    {
        $required = ['partner', 'vendor', 'user', 'password'];
        $missing = [];

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

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

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

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

    /**
     * Get configuration fields for admin panel
     */
    public function getConfigFields(): array
    {
        return array_merge(parent::getConfigFields(), [
            'partner' => [
                'type' => 'text',
                'label' => 'Partner',
                'description' => 'PayPal partner (provided by PayPal or reseller)',
                'required' => true,
                'value' => '',
                'placeholder' => 'PayPal',
            ],
            'vendor' => [
                'type' => 'text',
                'label' => 'Vendor',
                'description' => 'Merchant login ID',
                'required' => true,
                'value' => '',
                'placeholder' => 'Enter merchant login',
            ],
            'user' => [
                'type' => 'text',
                'label' => 'User',
                'description' => 'API user (if different from vendor, otherwise same as vendor)',
                'required' => true,
                'value' => '',
                'placeholder' => 'Enter API user',
            ],
            'password' => [
                'type' => 'password',
                'label' => 'Password',
                'description' => 'Payflow Pro API password',
                'required' => true,
                'value' => '',
                'placeholder' => 'Enter API password',
            ],
            'currency' => [
                'type' => 'select',
                'label' => 'Currency',
                'description' => 'Default currency for payments',
                'required' => true,
                'value' => 'USD',
                'options' => [
                    'USD' => 'US Dollar (USD)',
                    'EUR' => 'Euro (EUR)',
                    'GBP' => 'British Pound (GBP)',
                    'CAD' => 'Canadian Dollar (CAD)',
                    'AUD' => 'Australian Dollar (AUD)',
                ],
            ],
        ]);
    }

    /**
     * Resolve [MM, YY] from split fields or a combined card_expiry (MMYY/MM/YY/MM-YYYY).
     * Reconciles attemptPayment (card_expiry) with makePayment field names.
     */
    private function resolveExpiry(array $params): array
    {
        $month = $params['expiry_month'] ?? $params['card_expiry_month'] ?? '';
        $year = $params['expiry_year'] ?? $params['card_expiry_year'] ?? '';

        if (($month === '' || $year === '') && ! empty($params['card_expiry'])) {
            $digits = preg_replace('/\D/', '', (string) $params['card_expiry']);
            if (strlen($digits) >= 4) {
                $month = substr($digits, 0, 2);
                $year = substr($digits, -2);
            }
        }

        $month = $month === '' ? '' : str_pad((string) $month, 2, '0', STR_PAD_LEFT);
        $year = $year === '' ? '' : substr((string) $year, -2);

        return [$month, $year];
    }

    /**
     * Build NVP (Name-Value Pair) string from associative array
     */
    private function buildNvpString(array $data): string
    {
        $pairs = [];
        foreach ($data as $key => $value) {
            $pairs[] = strtoupper($key) . '[' . strlen($value) . ']=' . $value;
        }

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

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

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

        return $result;
    }

    /**
     * Call Payflow Pro API with form-encoded NVP data
     *
     * Uses a custom implementation instead of parent::callApi() because
     * Payflow Pro uses application/x-www-form-urlencoded NVP format, not JSON.
     */
    private function callPayflowApi(array $data): ?array
    {
        $nvpString = $this->buildNvpString($data);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->apiUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $nvpString);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: text/namevalue',
            'X-VPS-CLIENT-TIMEOUT: 45',
            'X-VPS-REQUEST-ID: ' . uniqid('PF', true),
        ]);
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);

        $response = curl_exec($ch);
        $curlError = curl_error($ch);
        curl_close($ch);

        if ($response === false || ! empty($curlError)) {
            return null;
        }

        return $this->parseNvpResponse($response);
    }

    /**
     * Get human-readable message for Payflow RESULT code
     */
    private function getResultMessage(int $resultCode, string $responseMessage = ''): string
    {
        $messages = [
            1 => __('admin/modules.payflowpro_error_user_auth'),
            2 => __('admin/modules.payflowpro_error_invalid_tender'),
            3 => __('admin/modules.payflowpro_error_invalid_transaction'),
            4 => __('admin/modules.payflowpro_error_invalid_amount'),
            5 => __('admin/modules.payflowpro_error_invalid_merchant'),
            12 => __('admin/modules.payflowpro_payment_declined'),
            13 => __('admin/modules.payflowpro_error_referral'),
            23 => __('admin/modules.payflowpro_invalid_account'),
            24 => __('admin/modules.payflowpro_invalid_amount'),
            25 => __('admin/modules.payflowpro_error_invalid_card'),
            26 => __('admin/modules.payflowpro_auth_failed'),
            27 => __('admin/modules.payflowpro_error_insufficient_permissions'),
            50 => __('admin/modules.payflowpro_fraud_suspected'),
            99 => __('admin/modules.payflowpro_error_timeout'),
            125 => __('admin/modules.payflowpro_fraud_filter', ['code' => 125]),
            126 => __('admin/modules.payflowpro_fraud_filter', ['code' => 126]),
            127 => __('admin/modules.payflowpro_fraud_filter', ['code' => 127]),
        ];

        if (isset($messages[$resultCode])) {
            return $messages[$resultCode];
        }

        if (! empty($responseMessage)) {
            return __('admin/modules.payflowpro_error_generic', ['message' => $responseMessage]);
        }

        return __('admin/modules.payflowpro_payment_failed');
    }

    public function getSupportedFeatures(): array
    {
        return $this->getFeatures();
    }
}
