<?php

namespace App\Modules\Payment\AuthorizeCim;

use App\Core\Payment\AbstractPayment;

class AuthorizeCim extends AbstractPayment
{
    protected $apiUrl;

    public function __construct(array $config = [])
    {
        parent::__construct($config);
        $this->apiUrl = $this->isTestMode()
            ? 'https://apitest.authorize.net/xml/v1/request.api'
            : 'https://api.authorize.net/xml/v1/request.api';
    }

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

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

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

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

    /**
     * Process payment: charge saved profile or create profile first then charge
     */
    public function makePayment(array $params)
    {
        // If saved card data exists, charge via profile
        if (! empty($params['customer_profile_id']) && ! empty($params['payment_profile_id'])) {
            return $this->payWithSavedCard([
                'customer_profile_id' => $params['customer_profile_id'],
                'payment_profile_id' => $params['payment_profile_id'],
                'amount' => $params['amount'],
                'order_id' => $params['order_id'] ?? null,
            ]);
        }

        // Otherwise create a customer profile first, then charge
        $profileResult = $this->createCustomerProfile($params);

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

        return $this->payWithSavedCard([
            'customer_profile_id' => $profileResult['data']['customer_profile_id'],
            'payment_profile_id' => $profileResult['data']['payment_profile_id'],
            'amount' => $params['amount'],
            'order_id' => $params['order_id'] ?? null,
        ]);
    }

    /**
     * Parse transaction response and check responseCode
     */
    public function handlePaymentCallback(array $params)
    {
        if (isset($params['transId'])) {
            $request = [
                'getTransactionDetailsRequest' => [
                    'merchantAuthentication' => $this->buildMerchantAuth(),
                    'transId' => $params['transId'],
                ],
            ];

            $response = $this->callAuthApi($request);

            if ($response && isset($response['transaction'])) {
                $transaction = $response['transaction'];
                $responseCode = $transaction['responseCode'] ?? null;

                // Bind attacker-supplied transId to the local order/amount when
                // the caller provides expected context (fail-closed): a forged
                // or foreign transId must not be accepted as this order's payment.
                if (! $this->transactionMatchesExpectation($transaction, $params)) {
                    return [
                        'status' => 'failed',
                        'message' => __('admin/modules.authorizecim_callback_mismatch'),
                    ];
                }

                if ($responseCode === 1 || $responseCode === '1') {
                    return [
                        'status' => 'success',
                        'message' => __('admin/modules.authorizecim_payment_success'),
                        'data' => [
                            'transaction_id' => $transaction['transId'],
                            'auth_code' => $transaction['authCode'] ?? '',
                            'amount' => $transaction['authAmount'] ?? '',
                        ],
                    ];
                }

                return [
                    'status' => 'failed',
                    'message' => __('admin/modules.authorizecim_payment_declined'),
                    'data' => [
                        'response_code' => $responseCode,
                        'response_reason' => $transaction['responseReasonDescription'] ?? '',
                    ],
                ];
            }
        }

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

    /**
     * Create refund via profile transaction
     */
    public function refundPayment(array $params)
    {
        // refundTransaction requires the original transaction reference (AIM
        // schema). The bare card-number path was removed: Authorize.Net rejects
        // a card-refund without the real last4 + real expirationDate, so the
        // refTransId (+optional profile) path is the sole supported contract.
        if (empty($params['transaction_id'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.authorizecim_refund_missing_transaction'),
            ];
        }

        $request = [
            'createTransactionRequest' => [
                'merchantAuthentication' => $this->buildMerchantAuth(),
                'transactionRequest' => [
                    'transactionType' => 'refundTransaction',
                    'amount' => number_format($params['amount'], 2, '.', ''),
                    'refTransId' => $params['transaction_id'],
                ],
            ],
        ];

        // If profile IDs are available, include them for the refund
        if (! empty($params['customer_profile_id']) && ! empty($params['payment_profile_id'])) {
            $request['createTransactionRequest']['transactionRequest']['profile'] = [
                'customerProfileId' => $params['customer_profile_id'],
                'paymentProfile' => [
                    'paymentProfileId' => $params['payment_profile_id'],
                ],
            ];
        }

        $response = $this->callAuthApi($request);

        if ($response && isset($response['transactionResponse'])) {
            $transResponse = $response['transactionResponse'];
            $responseCode = $transResponse['responseCode'] ?? null;

            if ($responseCode === 1 || $responseCode === '1') {
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.authorizecim_refund_success'),
                    'data' => [
                        'refund_id' => $transResponse['transId'] ?? '',
                        'auth_code' => $transResponse['authCode'] ?? '',
                    ],
                ];
            }

            return [
                'status' => 'error',
                'message' => __('admin/modules.authorizecim_refund_failed'),
                'data' => [
                    'error_code' => $transResponse['errors'][0]['errorCode'] ?? '',
                    'error_text' => $transResponse['errors'][0]['errorText'] ?? '',
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.authorizecim_refund_failed'),
            'data' => $this->parseApiError($response),
        ];
    }

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

    /**
     * Create customer profile with payment profile containing card data
     */
    public function saveCard(array $params)
    {
        $request = [
            'createCustomerProfileRequest' => [
                'merchantAuthentication' => $this->buildMerchantAuth(),
                'profile' => [
                    'merchantCustomerId' => $params['client_id'] ?? uniqid('cust_'),
                    'email' => $params['email'] ?? '',
                    'paymentProfiles' => [
                        'billTo' => [
                            'firstName' => $params['first_name'] ?? '',
                            'lastName' => $params['last_name'] ?? '',
                        ],
                        'payment' => [
                            'creditCard' => [
                                'cardNumber' => $params['card_number'],
                                'expirationDate' => $params['expiry_year'] . '-' . str_pad($params['expiry_month'], 2, '0', STR_PAD_LEFT),
                                'cardCode' => $params['cvv'] ?? '',
                            ],
                        ],
                    ],
                ],
                'validationMode' => $this->getConfig('validation_mode', 'testMode'),
            ],
        ];

        $response = $this->callAuthApi($request);

        if ($response && isset($response['customerProfileId'])) {
            $paymentProfileId = '';
            if (isset($response['customerPaymentProfileIdList'])) {
                $paymentProfileId = $response['customerPaymentProfileIdList'][0] ?? '';
            }

            return [
                'status' => 'success',
                'message' => __('admin/modules.authorizecim_card_saved'),
                'data' => [
                    'customer_profile_id' => $response['customerProfileId'],
                    'payment_profile_id' => $paymentProfileId,
                ],
            ];
        }

        // Handle duplicate profile - try to get existing profile and add payment method
        if ($response && isset($response['messages']['message'][0]['code']) && $response['messages']['message'][0]['code'] === 'E00039') {
            // Extract existing customer profile ID from the error message
            $existingProfileId = $this->extractProfileIdFromDuplicateError(
                $response['messages']['message'][0]['text'] ?? ''
            );

            if ($existingProfileId) {
                return $this->addPaymentProfileToExistingCustomer($existingProfileId, $params);
            }
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.authorizecim_card_save_failed'),
            'data' => $this->parseApiError($response),
        ];
    }

    /**
     * Delete customer payment profile
     */
    public function deleteCard(array $params)
    {
        $request = [
            'deleteCustomerPaymentProfileRequest' => [
                'merchantAuthentication' => $this->buildMerchantAuth(),
                'customerProfileId' => $params['customer_profile_id'],
                'customerPaymentProfileId' => $params['payment_profile_id'],
            ],
        ];

        $response = $this->callAuthApi($request);

        if ($response && isset($response['messages']['resultCode']) && $response['messages']['resultCode'] === 'Ok') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.authorizecim_card_deleted'),
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.authorizecim_card_delete_failed'),
            'data' => $this->parseApiError($response),
        ];
    }

    /**
     * Charge a saved customer payment profile
     */
    public function payWithSavedCard(array $params)
    {
        $request = [
            'createTransactionRequest' => [
                'merchantAuthentication' => $this->buildMerchantAuth(),
                'transactionRequest' => [
                    'transactionType' => 'authCaptureTransaction',
                    'amount' => number_format($params['amount'], 2, '.', ''),
                    'profile' => [
                        'customerProfileId' => $params['customer_profile_id'],
                        'paymentProfile' => [
                            'paymentProfileId' => $params['payment_profile_id'],
                        ],
                    ],
                ],
            ],
        ];

        // Add order reference if provided
        if (! empty($params['order_id'])) {
            $request['createTransactionRequest']['transactionRequest']['order'] = [
                'invoiceNumber' => $params['order_id'],
            ];
        }

        $response = $this->callAuthApi($request);

        if ($response && isset($response['transactionResponse'])) {
            $transResponse = $response['transactionResponse'];
            $responseCode = $transResponse['responseCode'] ?? null;

            if ($responseCode === 1 || $responseCode === '1') {
                return [
                    'status' => 'success',
                    'message' => __('admin/modules.authorizecim_payment_success'),
                    'data' => [
                        'transaction_id' => $transResponse['transId'] ?? '',
                        'auth_code' => $transResponse['authCode'] ?? '',
                        'account_number' => $transResponse['accountNumber'] ?? '',
                        'account_type' => $transResponse['accountType'] ?? '',
                    ],
                ];
            }

            return [
                'status' => 'failed',
                'message' => __('admin/modules.authorizecim_payment_declined'),
                'data' => [
                    'response_code' => $responseCode,
                    'error_code' => $transResponse['errors'][0]['errorCode'] ?? '',
                    'error_text' => $transResponse['errors'][0]['errorText'] ?? '',
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.authorizecim_payment_failed'),
            'data' => $this->parseApiError($response),
        ];
    }

    /**
     * Test API connection using authenticateTestRequest
     */
    public function testConnection(array $params = [])
    {
        $request = [
            'authenticateTestRequest' => [
                'merchantAuthentication' => $this->buildMerchantAuth(),
            ],
        ];

        $response = $this->callAuthApi($request);

        if ($response && isset($response['messages']['resultCode']) && $response['messages']['resultCode'] === 'Ok') {
            return [
                'status' => 'success',
                'message' => __('admin/modules.authorizecim_connection_success'),
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.authorizecim_connection_failed'),
            'data' => $this->parseApiError($response),
        ];
    }

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

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

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

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

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

    /**
     * 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(), [
            'api_login_id' => [
                'type' => 'text',
                'label' => 'API Login ID',
                'description' => 'Authorize.net API Login ID',
                'required' => true,
                'value' => '',
                'placeholder' => 'Your API Login ID',
            ],
            'transaction_key' => [
                'type' => 'password',
                'label' => 'Transaction Key',
                'description' => 'Authorize.net Transaction Key',
                'required' => true,
                'value' => '',
                'placeholder' => 'Your Transaction Key',
            ],
            'validation_mode' => [
                'type' => 'select',
                'label' => 'Validation Mode',
                'description' => 'Card validation mode when saving payment profiles',
                'required' => false,
                'value' => 'testMode',
                'options' => [
                    'testMode' => 'Test Mode',
                    'liveMode' => 'Live Mode',
                    'none' => 'None',
                ],
            ],
            'currency' => [
                'type' => 'select',
                'label' => 'Default Currency',
                'description' => 'Default currency for payments',
                'required' => true,
                'value' => 'USD',
                'options' => [
                    'USD' => 'USD - US Dollar',
                    'EUR' => 'EUR - Euro',
                    'GBP' => 'GBP - British Pound',
                    'CAD' => 'CAD - Canadian Dollar',
                ],
            ],
        ]);
    }

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

    /**
     * Build merchantAuthentication array
     */
    private function buildMerchantAuth(): array
    {
        return [
            'name' => $this->getConfig('api_login_id', ''),
            'transactionKey' => $this->getConfig('transaction_key', ''),
        ];
    }

    /**
     * Create a customer profile and return profile IDs
     */
    private function createCustomerProfile(array $params): array
    {
        $request = [
            'createCustomerProfileRequest' => [
                'merchantAuthentication' => $this->buildMerchantAuth(),
                'profile' => [
                    'merchantCustomerId' => $params['client_id'] ?? uniqid('cust_'),
                    'email' => $params['email'] ?? '',
                    'paymentProfiles' => [
                        'billTo' => [
                            'firstName' => $params['first_name'] ?? '',
                            'lastName' => $params['last_name'] ?? '',
                        ],
                        'payment' => [
                            'creditCard' => [
                                'cardNumber' => $params['card_number'],
                                'expirationDate' => $params['expiry_year'] . '-' . str_pad($params['expiry_month'], 2, '0', STR_PAD_LEFT),
                                'cardCode' => $params['cvv'] ?? '',
                            ],
                        ],
                    ],
                ],
                'validationMode' => $this->getConfig('validation_mode', 'testMode'),
            ],
        ];

        $response = $this->callAuthApi($request);

        if ($response && isset($response['customerProfileId'])) {
            $paymentProfileId = '';
            if (isset($response['customerPaymentProfileIdList'])) {
                $paymentProfileId = $response['customerPaymentProfileIdList'][0] ?? '';
            }

            return [
                'status' => 'success',
                'data' => [
                    'customer_profile_id' => $response['customerProfileId'],
                    'payment_profile_id' => $paymentProfileId,
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => $this->parseApiError($response)['error_text'] ?? __('admin/modules.authorizecim_profile_create_failed'),
        ];
    }

    /**
     * Get an existing customer profile
     */
    private function getCustomerProfile(string $customerProfileId): array
    {
        $request = [
            'getCustomerProfileRequest' => [
                'merchantAuthentication' => $this->buildMerchantAuth(),
                'customerProfileId' => $customerProfileId,
            ],
        ];

        $response = $this->callAuthApi($request);

        if ($response && isset($response['profile'])) {
            return [
                'status' => 'success',
                'data' => $response['profile'],
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.authorizecim_profile_not_found'),
            'data' => $this->parseApiError($response),
        ];
    }

    /**
     * Send API request to Authorize.net via PRC-888 postJson foundation
     * (TLS verify + protocol allowlist + timeout). Response carries a BOM
     * that breaks json_decode, so decode the raw body after stripping it.
     */
    private function callAuthApi(array $request): ?array
    {
        $result = $this->postJson($this->apiUrl, $request, ['Accept: application/json']);

        if (! empty($result['error'])) {
            return [
                'messages' => [
                    'resultCode' => 'Error',
                    'message' => [
                        ['code' => 'CURL_ERROR', 'text' => $result['error']],
                    ],
                ],
            ];
        }

        // Authorize.net returns JSON with a UTF-8 BOM — strip before decoding.
        $body = preg_replace('/^\x{FEFF}/u', '', (string) ($result['raw_response'] ?? ''));

        return json_decode($body, true);
    }

    /**
     * Verify a polled transaction belongs to the expected order/amount.
     *
     * The callback receives an attacker-controllable transId; when the caller
     * supplies expected_invoice / expected_amount we require the gateway-side
     * order.invoiceNumber and authAmount to match, so a foreign or forged
     * transId cannot be replayed as this order's payment (fail-closed). When no
     * expectation is supplied, we do not block (backward-compatible poll).
     */
    private function transactionMatchesExpectation(array $transaction, array $params): bool
    {
        $expectedInvoice = $params['expected_invoice'] ?? $params['order_id'] ?? null;
        if (! empty($expectedInvoice)) {
            $actualInvoice = $transaction['order']['invoiceNumber'] ?? null;
            if ((string) $actualInvoice !== (string) $expectedInvoice) {
                return false;
            }
        }

        $expectedAmount = $params['expected_amount'] ?? $params['amount'] ?? null;
        if (! empty($expectedAmount)) {
            $actualAmount = $transaction['authAmount'] ?? $transaction['settleAmount'] ?? null;
            if ($actualAmount === null
                || number_format((float) $actualAmount, 2, '.', '') !== number_format((float) $expectedAmount, 2, '.', '')) {
                return false;
            }
        }

        return true;
    }

    /**
     * Parse error information from API response
     */
    private function parseApiError(?array $response): array
    {
        if (! $response) {
            return [
                'error_code' => 'NO_RESPONSE',
                'error_text' => __('admin/modules.authorizecim_no_response'),
            ];
        }

        if (isset($response['messages']['message'][0])) {
            return [
                'error_code' => $response['messages']['message'][0]['code'] ?? '',
                'error_text' => $response['messages']['message'][0]['text'] ?? '',
            ];
        }

        return [
            'error_code' => 'UNKNOWN',
            'error_text' => __('admin/modules.authorizecim_unknown_error'),
        ];
    }

    /**
     * Extract profile ID from duplicate profile error message
     */
    private function extractProfileIdFromDuplicateError(string $errorText): ?string
    {
        // Authorize.net duplicate error contains the existing profile ID
        if (preg_match('/(\d+)/', $errorText, $matches)) {
            return $matches[1];
        }

        return null;
    }

    /**
     * Add a payment profile to an existing customer profile
     */
    private function addPaymentProfileToExistingCustomer(string $customerProfileId, array $params): array
    {
        $request = [
            'createCustomerPaymentProfileRequest' => [
                'merchantAuthentication' => $this->buildMerchantAuth(),
                'customerProfileId' => $customerProfileId,
                'paymentProfile' => [
                    'billTo' => [
                        'firstName' => $params['first_name'] ?? '',
                        'lastName' => $params['last_name'] ?? '',
                    ],
                    'payment' => [
                        'creditCard' => [
                            'cardNumber' => $params['card_number'],
                            'expirationDate' => $params['expiry_year'] . '-' . str_pad($params['expiry_month'], 2, '0', STR_PAD_LEFT),
                            'cardCode' => $params['cvv'] ?? '',
                        ],
                    ],
                ],
                'validationMode' => $this->getConfig('validation_mode', 'testMode'),
            ],
        ];

        $response = $this->callAuthApi($request);

        if ($response && isset($response['customerPaymentProfileId'])) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.authorizecim_card_saved'),
                'data' => [
                    'customer_profile_id' => $customerProfileId,
                    'payment_profile_id' => $response['customerPaymentProfileId'],
                ],
            ];
        }

        return [
            'status' => 'error',
            'message' => __('admin/modules.authorizecim_card_save_failed'),
            'data' => $this->parseApiError($response),
        ];
    }
}
