<?php

namespace App\Modules\Payment\Moneris;

use App\Core\Payment\AbstractPayment;

class Moneris extends AbstractPayment
{
    protected $apiUrl;

    public function __construct(array $config = [])
    {
        parent::__construct($config);
        $this->apiUrl = $this->isTestMode()
            ? 'https://esqa.moneris.com/gateway2/servlet/MpgRequest'
            : 'https://www3.moneris.com/gateway2/servlet/MpgRequest';
    }

    /**
     * Validate card params, perform fraud check, return ready status
     */
    public function attemptPayment(array $params)
    {
        $requiredParams = ['amount', 'currency', 'order_id', 'card_number', 'expiry_month', 'expiry_year', 'cvv'];

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

        // Perform pre-fraud check
        $fraudResult = $this->performPreFraudCheck($params);
        if ($fraudResult['status'] !== 'success') {
            return [
                'status' => 'error',
                'message' => __('admin/modules.moneris_fraud_check_failed'),
                'data' => $fraudResult,
            ];
        }

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

    /**
     * Build XML purchase request, send to Moneris API, parse response
     */
    public function makePayment(array $params)
    {
        $storeId = $this->getConfig('store_id');
        $apiToken = $this->getConfig('api_token');

        $orderId = $params['order_id'];
        $amount = number_format($params['amount'], 2, '.', '');
        $cardNumber = str_replace([' ', '-'], '', $params['card_number']);
        $expDate = substr($params['expiry_year'], -2) . str_pad($params['expiry_month'], 2, '0', STR_PAD_LEFT);
        $cvv = $params['cvv'];
        $cryptType = '7';

        $xmlData = [
            'type' => 'purchase',
            'store_id' => $storeId,
            'api_token' => $apiToken,
            'order_id' => $orderId,
            'amount' => $amount,
            'pan' => $cardNumber,
            'expdate' => $expDate,
            'crypt_type' => $cryptType,
            'cvd_indicator' => '1',
            'cvd_value' => $cvv,
        ];

        // cust_id: merchant-side customer reference (WHMCS moneris.php:545 uses email)
        $custId = $params['cust_id'] ?? $params['email'] ?? $params['client_id'] ?? null;
        if (! empty($custId)) {
            $xmlData['cust_id'] = $custId;
        }

        $xml = $this->buildXmlRequest('purchase', $xmlData);
        $response = $this->callMonerisApi($xml);

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

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

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

        $responseCode = (int) ($parsed['ResponseCode'] ?? 999);

        if ($responseCode >= 0 && $responseCode < 50) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.moneris_payment_success'),
                'data' => [
                    'transaction_id' => $parsed['TransID'] ?? '',
                    'receipt_id' => $parsed['ReceiptId'] ?? '',
                    'reference_num' => $parsed['ReferenceNum'] ?? '',
                    'auth_code' => $parsed['AuthCode'] ?? '',
                    'response_code' => $responseCode,
                    'message' => $parsed['Message'] ?? '',
                ],
            ];
        }

        return [
            'status' => 'failed',
            'message' => __('admin/modules.moneris_payment_declined'),
            'data' => [
                'response_code' => $responseCode,
                'message' => $parsed['Message'] ?? '',
                'receipt_id' => $parsed['ReceiptId'] ?? '',
            ],
        ];
    }

    /**
     * Moneris MPG is a server-to-server no-redirect API — it has NO signed
     * inbound webhook (WHMCS/HostBill ship no callback/response.php for moneris).
     * Payment truth lives ONLY in the TLS response to the merchant's own outbound
     * MpgRequest (makePayment/payWithSavedCard). Minting success here from
     * unauthenticated caller-supplied xml/transaction_id would forge paid invoices,
     * so this endpoint is fail-closed: it NEVER returns success from external input.
     */
    public function handlePaymentCallback(array $params)
    {
        return [
            'status' => 'error',
            'message' => __('admin/modules.moneris_callback_not_supported'),
        ];
    }

    /**
     * Send refund request via XML with original transaction details
     */
    public function refundPayment(array $params)
    {
        $storeId = $this->getConfig('store_id');
        $apiToken = $this->getConfig('api_token');

        $orderId = $params['order_id'];
        $amount = number_format($params['amount'], 2, '.', '');
        $txnNumber = $params['transaction_id'];
        $cryptType = '7';

        $xmlData = [
            'type' => 'refund',
            'store_id' => $storeId,
            'api_token' => $apiToken,
            'order_id' => $orderId,
            'amount' => $amount,
            'txn_number' => $txnNumber,
            'crypt_type' => $cryptType,
        ];

        $xml = $this->buildXmlRequest('refund', $xmlData);
        $response = $this->callMonerisApi($xml);

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

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

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

        $responseCode = (int) ($parsed['ResponseCode'] ?? 999);

        if ($responseCode >= 0 && $responseCode < 50) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.moneris_refund_success'),
                'data' => [
                    'refund_id' => $parsed['TransID'] ?? '',
                    'receipt_id' => $parsed['ReceiptId'] ?? '',
                    'reference_num' => $parsed['ReferenceNum'] ?? '',
                    'response_code' => $responseCode,
                    'message' => $parsed['Message'] ?? '',
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.moneris_refund_failed'),
            'data' => [
                'response_code' => $responseCode,
                'message' => $parsed['Message'] ?? '',
            ],
        ];
    }

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

    /**
     * Tokenize card using Moneris Vault res_add_cc, return DataKey
     */
    public function saveCard(array $params)
    {
        $storeId = $this->getConfig('store_id');
        $apiToken = $this->getConfig('api_token');

        $cardNumber = str_replace([' ', '-'], '', $params['card_number']);
        $expDate = substr($params['expiry_year'], -2) . str_pad($params['expiry_month'], 2, '0', STR_PAD_LEFT);
        // Vault card-on-file registration = crypt_type 1 (WHMCS monerisvault.php:679 res_add_cc)
        $cryptType = '1';

        $xmlData = [
            'type' => 'res_add_cc',
            'store_id' => $storeId,
            'api_token' => $apiToken,
            'pan' => $cardNumber,
            'expdate' => $expDate,
            'crypt_type' => $cryptType,
        ];

        // Add optional customer info (monerisvault.php:712 res_add_cc: cust_id,phone,email,note)
        if (! empty($params['email'])) {
            $xmlData['email'] = $params['email'];
        }
        if (! empty($params['phone'])) {
            $xmlData['phone'] = $params['phone'];
        }
        if (! empty($params['note'])) {
            $xmlData['note'] = $params['note'];
        }
        if (! empty($params['cust_id'])) {
            $xmlData['cust_id'] = $params['cust_id'];
        } elseif (! empty($params['client_id'])) {
            $xmlData['cust_id'] = $params['client_id'];
        }

        $xml = $this->buildXmlRequest('res_add_cc', $xmlData);
        $response = $this->callMonerisApi($xml);

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

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

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

        $responseCode = $parsed['ResponseCode'] ?? null;
        $dataKey = $parsed['DataKey'] ?? null;

        if ($dataKey && $responseCode !== null && (int) $responseCode < 50) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.moneris_card_saved'),
                'data' => [
                    'data_key' => $dataKey,
                    'masked_pan' => $parsed['ResolveData']['masked_pan'] ?? $this->maskCardNumber($cardNumber),
                    'response_code' => $responseCode,
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.moneris_card_save_failed'),
            'data' => [
                'response_code' => $responseCode,
                'message' => $parsed['Message'] ?? '',
            ],
        ];
    }

    /**
     * Charge a tokenized card using res_purchase_cc with DataKey
     */
    public function payWithSavedCard(array $params)
    {
        $storeId = $this->getConfig('store_id');
        $apiToken = $this->getConfig('api_token');

        $orderId = $params['order_id'];
        $amount = number_format($params['amount'], 2, '.', '');
        $dataKey = $params['data_key'];
        $cryptType = '7';

        $xmlData = [
            'type' => 'res_purchase_cc',
            'store_id' => $storeId,
            'api_token' => $apiToken,
            'data_key' => $dataKey,
            'order_id' => $orderId,
            'amount' => $amount,
            'crypt_type' => $cryptType,
        ];

        // cust_id: WHMCS monerisvault.php:752 + HostBill capture_token include it (optional)
        $custId = $params['cust_id'] ?? $params['client_id'] ?? $params['email'] ?? null;
        if (! empty($custId)) {
            $xmlData['cust_id'] = $custId;
        }

        $xml = $this->buildXmlRequest('res_purchase_cc', $xmlData);
        $response = $this->callMonerisApi($xml);

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

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

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

        $responseCode = (int) ($parsed['ResponseCode'] ?? 999);

        if ($responseCode >= 0 && $responseCode < 50) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.moneris_payment_success'),
                'data' => [
                    'transaction_id' => $parsed['TransID'] ?? '',
                    'receipt_id' => $parsed['ReceiptId'] ?? '',
                    'reference_num' => $parsed['ReferenceNum'] ?? '',
                    'auth_code' => $parsed['AuthCode'] ?? '',
                    'response_code' => $responseCode,
                    'data_key' => $dataKey,
                ],
            ];
        }

        return [
            'status' => 'failed',
            'message' => __('admin/modules.moneris_payment_declined'),
            'data' => [
                'response_code' => $responseCode,
                'message' => $parsed['Message'] ?? '',
            ],
        ];
    }

    /**
     * Delete a tokenized card from Moneris Vault using res_delete
     */
    public function deleteCard(array $params)
    {
        $storeId = $this->getConfig('store_id');
        $apiToken = $this->getConfig('api_token');

        $dataKey = $params['data_key'];

        $xmlData = [
            'type' => 'res_delete',
            'store_id' => $storeId,
            'api_token' => $apiToken,
            'data_key' => $dataKey,
        ];

        $xml = $this->buildXmlRequest('res_delete', $xmlData);
        $response = $this->callMonerisApi($xml);

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

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

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

        $responseCode = $parsed['ResponseCode'] ?? null;
        $complete = $parsed['Complete'] ?? 'false';

        if (strtolower($complete) === 'true') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.moneris_card_deleted'),
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.moneris_card_delete_failed'),
            'data' => [
                'response_code' => $responseCode,
                'message' => $parsed['Message'] ?? '',
            ],
        ];
    }

    /**
     * Test API connection by verifying credentials with a zero-dollar purchase
     */
    public function testConnection(array $params = [])
    {
        $storeId = $this->getConfig('store_id');
        $apiToken = $this->getConfig('api_token');

        if (empty($storeId) || empty($apiToken)) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.moneris_config_missing', ['fields' => 'store_id, api_token']),
            ];
        }

        // Use a card verification (zero-dollar) to test credentials
        $xmlData = [
            'type' => 'card_verification',
            'store_id' => $storeId,
            'api_token' => $apiToken,
            'order_id' => 'test_' . time(),
            'pan' => '4242424242424242',
            'expdate' => '2512',
            'crypt_type' => '7',
        ];

        $xml = $this->buildXmlRequest('card_verification', $xmlData);
        $response = $this->callMonerisApi($xml);

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

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

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

        $responseCode = $parsed['ResponseCode'] ?? null;

        // Any valid response (even declined) means credentials work
        if ($responseCode !== null && $responseCode !== 'null') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.moneris_connection_success'),
            ];
        }

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

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

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

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

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

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

    /**
     * Get configuration fields for admin panel
     */
    public function getConfigFields(): array
    {
        return array_merge(parent::getConfigFields(), [
            'store_id' => [
                'type' => 'text',
                'label' => 'Store ID',
                'description' => 'Moneris Store ID',
                'required' => true,
                'value' => '',
                'placeholder' => 'Your Moneris Store ID',
            ],
            'api_token' => [
                'type' => 'password',
                'label' => 'API Token',
                'description' => 'Moneris API Token',
                'required' => true,
                'value' => '',
                'placeholder' => 'Your Moneris API Token',
            ],
            'currency' => [
                'type' => 'select',
                'label' => 'Default Currency',
                'description' => 'Default currency for Moneris payments',
                'required' => true,
                'value' => 'CAD',
                'options' => [
                    'CAD' => 'CAD - Canadian Dollar',
                    'USD' => 'USD - US Dollar',
                ],
            ],
        ]);
    }

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

    /**
     * Build XML request string for Moneris API
     */
    private function buildXmlRequest(string $type, array $data): string
    {
        $storeId = htmlspecialchars($data['store_id'] ?? '', ENT_XML1, 'UTF-8');
        $apiToken = htmlspecialchars($data['api_token'] ?? '', ENT_XML1, 'UTF-8');

        $xml = '<?xml version="1.0" encoding="UTF-8"?>';
        $xml .= '<request>';
        $xml .= '<store_id>' . $storeId . '</store_id>';
        $xml .= '<api_token>' . $apiToken . '</api_token>';

        switch ($type) {
            case 'purchase':
                $xml .= '<purchase>';
                $xml .= '<order_id>' . htmlspecialchars($data['order_id'] ?? '', ENT_XML1, 'UTF-8') . '</order_id>';
                if (! empty($data['cust_id'])) {
                    $xml .= '<cust_id>' . htmlspecialchars($data['cust_id'], ENT_XML1, 'UTF-8') . '</cust_id>';
                }
                $xml .= '<amount>' . htmlspecialchars($data['amount'] ?? '', ENT_XML1, 'UTF-8') . '</amount>';
                $xml .= '<pan>' . htmlspecialchars($data['pan'] ?? '', ENT_XML1, 'UTF-8') . '</pan>';
                $xml .= '<expdate>' . htmlspecialchars($data['expdate'] ?? '', ENT_XML1, 'UTF-8') . '</expdate>';
                $xml .= '<crypt_type>' . htmlspecialchars($data['crypt_type'] ?? '7', ENT_XML1, 'UTF-8') . '</crypt_type>';
                $xml .= '<cvd_info>';
                $xml .= '<cvd_indicator>' . htmlspecialchars($data['cvd_indicator'] ?? '1', ENT_XML1, 'UTF-8') . '</cvd_indicator>';
                $xml .= '<cvd_value>' . htmlspecialchars($data['cvd_value'] ?? '', ENT_XML1, 'UTF-8') . '</cvd_value>';
                $xml .= '</cvd_info>';
                $xml .= '</purchase>';
                break;

            case 'refund':
                $xml .= '<refund>';
                $xml .= '<order_id>' . htmlspecialchars($data['order_id'] ?? '', ENT_XML1, 'UTF-8') . '</order_id>';
                $xml .= '<amount>' . htmlspecialchars($data['amount'] ?? '', ENT_XML1, 'UTF-8') . '</amount>';
                $xml .= '<txn_number>' . htmlspecialchars($data['txn_number'] ?? '', ENT_XML1, 'UTF-8') . '</txn_number>';
                $xml .= '<crypt_type>' . htmlspecialchars($data['crypt_type'] ?? '7', ENT_XML1, 'UTF-8') . '</crypt_type>';
                $xml .= '</refund>';
                break;

            case 'res_add_cc':
                $xml .= '<res_add_cc>';
                $xml .= '<pan>' . htmlspecialchars($data['pan'] ?? '', ENT_XML1, 'UTF-8') . '</pan>';
                $xml .= '<expdate>' . htmlspecialchars($data['expdate'] ?? '', ENT_XML1, 'UTF-8') . '</expdate>';
                $xml .= '<crypt_type>' . htmlspecialchars($data['crypt_type'] ?? '7', ENT_XML1, 'UTF-8') . '</crypt_type>';
                if (! empty($data['cust_id'])) {
                    $xml .= '<cust_id>' . htmlspecialchars($data['cust_id'], ENT_XML1, 'UTF-8') . '</cust_id>';
                }
                if (! empty($data['phone'])) {
                    $xml .= '<phone>' . htmlspecialchars($data['phone'], ENT_XML1, 'UTF-8') . '</phone>';
                }
                if (! empty($data['email'])) {
                    $xml .= '<email>' . htmlspecialchars($data['email'], ENT_XML1, 'UTF-8') . '</email>';
                }
                if (! empty($data['note'])) {
                    $xml .= '<note>' . htmlspecialchars($data['note'], ENT_XML1, 'UTF-8') . '</note>';
                }
                $xml .= '</res_add_cc>';
                break;

            case 'res_purchase_cc':
                $xml .= '<res_purchase_cc>';
                $xml .= '<data_key>' . htmlspecialchars($data['data_key'] ?? '', ENT_XML1, 'UTF-8') . '</data_key>';
                $xml .= '<order_id>' . htmlspecialchars($data['order_id'] ?? '', ENT_XML1, 'UTF-8') . '</order_id>';
                if (! empty($data['cust_id'])) {
                    $xml .= '<cust_id>' . htmlspecialchars($data['cust_id'], ENT_XML1, 'UTF-8') . '</cust_id>';
                }
                $xml .= '<amount>' . htmlspecialchars($data['amount'] ?? '', ENT_XML1, 'UTF-8') . '</amount>';
                $xml .= '<crypt_type>' . htmlspecialchars($data['crypt_type'] ?? '7', ENT_XML1, 'UTF-8') . '</crypt_type>';
                $xml .= '</res_purchase_cc>';
                break;

            case 'res_delete':
                $xml .= '<res_delete>';
                $xml .= '<data_key>' . htmlspecialchars($data['data_key'] ?? '', ENT_XML1, 'UTF-8') . '</data_key>';
                $xml .= '</res_delete>';
                break;

            case 'card_verification':
                $xml .= '<card_verification>';
                $xml .= '<order_id>' . htmlspecialchars($data['order_id'] ?? '', ENT_XML1, 'UTF-8') . '</order_id>';
                $xml .= '<pan>' . htmlspecialchars($data['pan'] ?? '', ENT_XML1, 'UTF-8') . '</pan>';
                $xml .= '<expdate>' . htmlspecialchars($data['expdate'] ?? '', ENT_XML1, 'UTF-8') . '</expdate>';
                $xml .= '<crypt_type>' . htmlspecialchars($data['crypt_type'] ?? '7', ENT_XML1, 'UTF-8') . '</crypt_type>';
                $xml .= '</card_verification>';
                break;
        }

        $xml .= '</request>';

        return $xml;
    }

    /**
     * Parse Moneris XML response into associative array
     */
    private function parseXmlResponse(string $xmlString): ?array
    {
        libxml_use_internal_errors(true);
        $xml = simplexml_load_string($xmlString);

        if ($xml === false) {
            return null;
        }

        // Convert SimpleXMLElement to array recursively
        $json = json_encode($xml);
        $array = json_decode($json, true);

        if (! is_array($array)) {
            return null;
        }

        // Moneris response is nested under <receipt>
        $receipt = $array['receipt'] ?? $array;

        // Moneris global-error XML returns the literal string "null" for
        // ResponseCode (WHMCS moneris.php:53 fabricates
        // "<ResponseCode>null</ResponseCode>"). PHP casts (int)"null" === 0,
        // which lands in the 0-49 "approved" band and forges a fake success.
        // WHMCS (moneris.php:554,582) normalizes: "null" === code ? NULL : (int).
        // Do it once at the parse boundary so every money path (makePayment,
        // refundPayment, payWithSavedCard) and the vault/verify paths treat a
        // global error as a non-numeric response → declined/error (fail-closed).
        if (isset($receipt['ResponseCode']) && $receipt['ResponseCode'] === 'null') {
            $receipt['ResponseCode'] = null;
        }

        return $receipt;
    }

    /**
     * Send XML request to Moneris API and return raw response.
     * Routes through PRC-888 postXml() (TLS peer+host verify, HTTPS-only SSRF guard).
     */
    private function callMonerisApi(string $xml): ?string
    {
        $result = $this->postXml($this->apiUrl, $xml, [
            'Content-Type: application/xml',
            'Accept: application/xml',
        ]);

        if (! empty($result['error'])) {
            return null;
        }

        $raw = $result['raw_response'] ?? '';

        return $raw !== '' ? $raw : null;
    }

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