<?php

namespace App\Modules\Payment\YooKassa;

use App\Core\Payment\AbstractPayment;

class YooKassa extends AbstractPayment
{
    protected $apiUrl = 'https://api.yookassa.ru/v3';

    /**
     * YooKassa webhook IP whitelist
     * @see https://yookassa.ru/developers/using-api/webhooks
     */
    protected $webhookIpWhitelist = [
        '185.71.76.0/27',
        '185.71.77.0/27',
        '77.75.153.0/25',
        '77.75.156.11',
        '77.75.156.35',
        '77.75.154.128/25',
        '2a02:5180::/32',
    ];

    public function __construct(array $config = [])
    {
        parent::__construct($config);
    }

    /**
     * Validate payment parameters before processing
     */
    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.yookassa_missing_parameter', ['param' => $param]),
                ];
            }
        }

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

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

    /**
     * Create a payment via YooKassa API and return redirect URL
     */
    public function makePayment(array $params)
    {
        $currency = strtoupper($params['currency'] ?? $this->getConfig('currency', 'RUB'));

        $paymentData = [
            'amount' => [
                'value' => number_format((float) $params['amount'], 2, '.', ''),
                'currency' => $currency,
            ],
            'confirmation' => [
                'type' => 'redirect',
                'return_url' => $params['return_url'] ?? $params['success_url'] ?? $params['redirect_url'] ?? '',
            ],
            'capture' => true,
            'description' => $params['description'] ?? __('admin/modules.yookassa_payment_description', ['order_id' => $params['order_id']]),
            'metadata' => [
                'order_id' => $params['order_id'],
            ],
        ];

        $response = $this->callApi($this->apiUrl . '/payments', 'POST', $paymentData);

        if ($response['http_code'] === 200 && isset($response['response']['confirmation']['confirmation_url'])) {
            $payment = $response['response'];

            return [
                'status' => 'redirect',
                'message' => __('admin/modules.yookassa_payment_created'),
                'transaction_id' => $payment['id'],
                'redirect_url' => $payment['confirmation']['confirmation_url'],
                'data' => [
                    'yookassa_payment_id' => $payment['id'],
                    'confirmation_url' => $payment['confirmation']['confirmation_url'],
                    'amount' => $params['amount'],
                    'currency' => $currency,
                ],
            ];
        }

        $errorMessage = $response['response']['description'] ?? $response['response']['message'] ?? __('admin/modules.yookassa_payment_failed');

        return [
            'status' => 'error',
            'message' => $errorMessage,
            'data' => $response,
        ];
    }

    /**
     * Handle payment webhook callback from YooKassa
     *
     * YooKassa v3 webhook'ları imzasızdır; resmi güvenlik modeli = (1) gönderen
     * IP whitelist + (2) bildirimi ASLA doğrudan güvenme, GET /payments/{id} ile
     * yeniden çek ve taze status'e güven (fail-closed).
     * @see https://yookassa.ru/developers/using-api/webhooks#ip
     */
    public function handlePaymentCallback(array $params)
    {
        // 1) Gönderen IP whitelist — FAIL-CLOSED: IP yoksa doğrulanamaz, reddet.
        $clientIp = $params['client_ip'] ?? $params['ip'] ?? null;
        if (! $clientIp || ! $this->isIpWhitelisted($clientIp)) {
            return [
                'status' => 'failed',
                'message' => __('admin/modules.yookassa_invalid_webhook_ip'),
            ];
        }

        // Parse webhook event
        $event = $params['event'] ?? null;
        $object = $params['object'] ?? null;

        if (! $event || ! $object) {
            return [
                'status' => 'failed',
                'message' => __('admin/modules.yookassa_invalid_webhook_payload'),
            ];
        }

        $paymentId = $object['id'] ?? null;

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

        // 2) Bildirim gövdesine GÜVENME — ödemeyi API'den yeniden çek (fail-closed).
        $response = $this->callApi($this->apiUrl . '/payments/' . rawurlencode($paymentId), 'GET');

        if ($response['http_code'] !== 200 || ! is_array($response['response'] ?? null) || empty($response['response']['id'])) {
            return [
                'status' => 'failed',
                'message' => __('admin/modules.yookassa_payment_verification_failed'),
                'transaction_id' => $paymentId,
            ];
        }

        $payment = $response['response'];
        $freshStatus = $payment['status'] ?? null;
        $paid = $payment['paid'] ?? false;
        $freshAmountValue = $payment['amount']['value'] ?? null;
        $freshCurrency = isset($payment['amount']['currency'])
            ? strtoupper((string) $payment['amount']['currency'])
            : null;

        // Ödeme yalnız taze status=succeeded + paid=true ise başarılı.
        $mappedStatus = ($freshStatus === 'succeeded' && $paid === true)
            ? 'success'
            : $this->mapYooKassaStatus((string) $freshStatus);

        // Mollie dersi (fail-closed reconciliation): re-fetch VAR ama tutar/currency
        // faturayla çapraz doğrulanmazsa düşük-tutar replay açığı kalır. API-dönen
        // (otoriter) amount/currency'yi beklenen fatura değeriyle karşılaştır; uyumsuz
        // ise başarıyı DÜŞÜR (imzasız body'ye değil, gateway'in kendi verisine göre).
        if ($mappedStatus === 'success' && isset($params['expected_amount'])) {
            $expectedMinor = (int) round(((float) $params['expected_amount']) * 100);
            $freshMinor = (int) round(((float) $freshAmountValue) * 100);
            $expectedCurrency = isset($params['expected_currency'])
                ? strtoupper((string) $params['expected_currency'])
                : $freshCurrency;

            if ($freshMinor !== $expectedMinor
                || ($freshCurrency !== null && $expectedCurrency !== null && $freshCurrency !== $expectedCurrency)) {
                return [
                    'status' => 'failed',
                    'message' => __('admin/modules.yookassa_amount_mismatch'),
                    'transaction_id' => $payment['id'] ?? $paymentId,
                    'data' => [
                        'yookassa_status' => $freshStatus,
                        'amount' => $freshAmountValue,
                        'currency' => $freshCurrency,
                    ],
                ];
            }
        }

        return [
            'status' => $mappedStatus,
            'message' => __('admin/modules.yookassa_payment_status_' . $mappedStatus),
            'transaction_id' => $payment['id'],
            'data' => [
                'yookassa_event' => $event,
                'yookassa_status' => $freshStatus,
                'paid' => $paid,
                'amount' => $payment['amount']['value'] ?? null,
                'currency' => $payment['amount']['currency'] ?? null,
                'metadata' => $payment['metadata'] ?? [],
                'payment_method' => $payment['payment_method']['type'] ?? null,
            ],
        ];
    }

    /**
     * Refund a payment via YooKassa API
     */
    public function refundPayment(array $params)
    {
        $paymentId = $params['transaction_id'] ?? $params['payment_id'] ?? $params['yookassa_payment_id'] ?? null;

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

        $currency = strtoupper($params['currency'] ?? $this->getConfig('currency', 'RUB'));

        $refundData = [
            'payment_id' => $paymentId,
            'amount' => [
                'value' => number_format((float) $params['amount'], 2, '.', ''),
                'currency' => $currency,
            ],
        ];

        if (isset($params['description'])) {
            $refundData['description'] = $params['description'];
        }

        $response = $this->callApi($this->apiUrl . '/refunds', 'POST', $refundData);

        if ($response['http_code'] === 200 && isset($response['response']['id'])) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.yookassa_refund_success'),
                'refund_id' => $response['response']['id'],
                'data' => $response['response'],
            ];
        }

        $errorMessage = $response['response']['description'] ?? $response['response']['message'] ?? __('admin/modules.yookassa_refund_failed');

        return [
            'status' => 'error',
            'message' => $errorMessage,
            'data' => $response,
        ];
    }

    /**
     * Generate a payment link via YooKassa API
     */
    public function generatePaymentLink(array $params)
    {
        $currency = strtoupper($params['currency'] ?? $this->getConfig('currency', 'RUB'));

        $paymentData = [
            'amount' => [
                'value' => number_format((float) $params['amount'], 2, '.', ''),
                'currency' => $currency,
            ],
            'confirmation' => [
                'type' => 'redirect',
                'return_url' => $params['return_url'] ?? $params['success_url'] ?? $params['redirect_url'] ?? '',
            ],
            'capture' => true,
            'description' => $params['description'] ?? __('admin/modules.yookassa_payment_description', ['order_id' => $params['order_id']]),
            'metadata' => [
                'order_id' => $params['order_id'],
            ],
        ];

        $response = $this->callApi($this->apiUrl . '/payments', 'POST', $paymentData);

        if ($response['http_code'] === 200 && isset($response['response']['confirmation']['confirmation_url'])) {
            return [
                'status' => 'success',
                'payment_url' => $response['response']['confirmation']['confirmation_url'],
                'transaction_id' => $response['response']['id'],
            ];
        }

        $errorMessage = $response['response']['description'] ?? $response['response']['message'] ?? __('admin/modules.yookassa_payment_link_failed');

        return [
            'status' => 'error',
            'message' => $errorMessage,
            'data' => $response,
        ];
    }

    /**
     * Test API connection by fetching payments list
     */
    public function testConnection(array $params = [])
    {
        $response = $this->callApi($this->apiUrl . '/payments?limit=1', 'GET');

        if ($response['http_code'] === 200) {
            return [
                'status' => 'success',
                'message' => __('admin/modules.yookassa_connection_success'),
                'data' => [
                    'shop_id' => $this->getConfig('shop_id'),
                ],
            ];
        }

        $errorMessage = $response['response']['description'] ?? $response['response']['message'] ?? __('admin/modules.yookassa_connection_failed');

        return [
            'status' => 'error',
            'message' => $errorMessage,
            'data' => $response,
        ];
    }

    /**
     * Validate module configuration
     */
    public function validateConfig()
    {
        if (empty($this->config['shop_id'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.yookassa_shop_id_required'),
            ];
        }

        if (empty($this->config['secret_key'])) {
            return [
                'status' => 'error',
                'message' => __('admin/modules.yookassa_secret_key_required'),
            ];
        }

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

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

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

    /**
     * Get configuration fields for admin panel
     */
    public function getConfigFields(): array
    {
        return array_merge(parent::getConfigFields(), [
            'shop_id' => [
                'type' => 'text',
                'label' => 'Shop ID',
                'description' => 'YooKassa Shop ID from your merchant dashboard',
                'required' => true,
                'value' => '',
                'placeholder' => '123456',
            ],
            'secret_key' => [
                'type' => 'password',
                'label' => 'Secret Key',
                'description' => 'YooKassa Secret Key for API authentication',
                'required' => true,
                'value' => '',
                'placeholder' => 'test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
            ],
            'currency' => [
                'type' => 'select',
                'label' => 'Default Currency',
                'description' => 'Default currency for payments',
                'required' => true,
                'value' => 'RUB',
                'options' => [
                    'RUB' => 'RUB - Russian Ruble',
                    'USD' => 'USD - US Dollar',
                    'EUR' => 'EUR - Euro',
                ],
            ],
        ]);
    }

    /**
     * Override default headers for Basic Auth with Idempotence-Key
     */
    protected function getDefaultHeaders()
    {
        $shopId = $this->getConfig('shop_id', '');
        $secretKey = $this->getConfig('secret_key', '');

        return [
            'Content-Type: application/json',
            'Accept: application/json',
            'User-Agent: ProCP/1.0 Payment Gateway Module',
            'Authorization: Basic ' . base64_encode($shopId . ':' . $secretKey),
            'Idempotence-Key: ' . $this->generateIdempotenceKey(),
        ];
    }

    /**
     * Save a payment card for future use via YooKassa API
     */
    public function saveCard(array $params)
    {
        $currency = strtoupper($params['currency'] ?? $this->getConfig('currency', 'RUB'));

        $paymentData = [
            'amount' => [
                'value' => number_format((float) $params['amount'], 2, '.', ''),
                'currency' => $currency,
            ],
            'confirmation' => [
                'type' => 'redirect',
                'return_url' => $params['return_url'] ?? $params['success_url'] ?? $params['redirect_url'] ?? '',
            ],
            'capture' => true,
            'save_payment_method' => true,
            'description' => $params['description'] ?? __('admin/modules.yookassa_save_card_description'),
            'metadata' => [
                'order_id' => $params['order_id'] ?? null,
            ],
        ];

        $response = $this->callApi($this->apiUrl . '/payments', 'POST', $paymentData);

        if ($response['http_code'] === 200 && isset($response['response']['id'])) {
            $payment = $response['response'];

            return [
                'status' => 'success',
                'message' => __('admin/modules.yookassa_save_card_success'),
                'transaction_id' => $payment['id'],
                'redirect_url' => $payment['confirmation']['confirmation_url'] ?? null,
                'data' => [
                    'yookassa_payment_id' => $payment['id'],
                    'payment_method_id' => $payment['payment_method']['id'] ?? null,
                ],
            ];
        }

        $errorMessage = $response['response']['description'] ?? $response['response']['message'] ?? __('admin/modules.yookassa_save_card_failed');

        return [
            'status' => 'error',
            'message' => $errorMessage,
            'data' => $response,
        ];
    }

    /**
     * Pay with a previously saved card via YooKassa API
     */
    public function payWithSavedCard(array $params)
    {
        $paymentMethodId = $params['payment_method_id'] ?? $params['card_token'] ?? null;

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

        $currency = strtoupper($params['currency'] ?? $this->getConfig('currency', 'RUB'));

        $paymentData = [
            'amount' => [
                'value' => number_format((float) $params['amount'], 2, '.', ''),
                'currency' => $currency,
            ],
            'payment_method_id' => $paymentMethodId,
            'capture' => true,
            'description' => $params['description'] ?? __('admin/modules.yookassa_payment_description', ['order_id' => $params['order_id']]),
            'metadata' => [
                'order_id' => $params['order_id'],
            ],
        ];

        $response = $this->callApi($this->apiUrl . '/payments', 'POST', $paymentData);

        if ($response['http_code'] === 200 && isset($response['response']['id'])) {
            $payment = $response['response'];
            $mappedStatus = $this->mapYooKassaStatus($payment['status'] ?? 'pending');

            return [
                'status' => $mappedStatus,
                'message' => __('admin/modules.yookassa_saved_card_payment_' . $mappedStatus),
                'transaction_id' => $payment['id'],
                'data' => [
                    'yookassa_payment_id' => $payment['id'],
                    'yookassa_status' => $payment['status'],
                    'amount' => $params['amount'],
                    'currency' => $currency,
                ],
            ];
        }

        $errorMessage = $response['response']['description'] ?? $response['response']['message'] ?? __('admin/modules.yookassa_saved_card_payment_failed');

        return [
            'status' => 'error',
            'message' => $errorMessage,
            'data' => $response,
        ];
    }

    /**
     * Generate UUID v4 idempotence key for YooKassa API requests
     */
    protected function generateIdempotenceKey(): string
    {
        $data = random_bytes(16);
        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);

        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
    }

    /**
     * Map YooKassa payment status to internal status
     *
     * YooKassa statuses: succeeded, pending, waiting_for_capture, canceled
     * Internal statuses: success, failed, pending
     */
    private function mapYooKassaStatus(string $yookassaStatus): string
    {
        return match ($yookassaStatus) {
            'succeeded' => 'success',
            'pending' => 'pending',
            'waiting_for_capture' => 'pending',
            'canceled' => 'failed',
            default => 'failed',
        };
    }

    /**
     * Check if an IP address is in the YooKassa webhook whitelist
     */
    private function isIpWhitelisted(string $ip): bool
    {
        foreach ($this->webhookIpWhitelist as $range) {
            if ($this->ipInRange($ip, $range)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if an IP is within a CIDR range or matches exactly
     */
    private function ipInRange(string $ip, string $range): bool
    {
        // IPv6 CIDR (ör. 2a02:5180::/32) — inet_pton + bit-maskesi ile içerme testi.
        // Düz string eşitliği YooKassa'nın yayınladığı /32 IPv6 bloğunu ASLA
        // eşleştiremez → gerçek IPv6 webhook'ları sessizce düşerdi.
        if (str_contains($range, ':') || str_contains($ip, ':')) {
            if (! str_contains($range, '/')) {
                return $ip === $range;
            }
            [$subnet, $bits] = explode('/', $range);
            $ipBin = @inet_pton($ip);
            $subnetBin = @inet_pton($subnet);
            if ($ipBin === false || $subnetBin === false || strlen($ipBin) !== strlen($subnetBin)) {
                return false;
            }

            return $this->binaryPrefixMatches($ipBin, $subnetBin, (int) $bits);
        }

        if (! str_contains($range, '/')) {
            return $ip === $range;
        }

        [$subnet, $bits] = explode('/', $range);
        $subnetLong = ip2long($subnet);
        $ipLong = ip2long($ip);
        if ($ipLong === false || $subnetLong === false) {
            return false;
        }
        $mask = -1 << (32 - (int) $bits);
        $subnetLong &= $mask;

        return ($ipLong & $mask) === $subnetLong;
    }

    /**
     * inet_pton binary IP'lerinde ilk $bits bitinin eşleşip eşleşmediğini test eder.
     */
    private function binaryPrefixMatches(string $ipBin, string $subnetBin, int $bits): bool
    {
        $bytes = intdiv($bits, 8);
        $remBits = $bits % 8;

        if ($bytes > 0 && strncmp($ipBin, $subnetBin, $bytes) !== 0) {
            return false;
        }

        if ($remBits === 0) {
            return true;
        }

        $mask = 0xFF << (8 - $remBits) & 0xFF;

        return (ord($ipBin[$bytes]) & $mask) === (ord($subnetBin[$bytes]) & $mask);
    }
}
