<?php

declare(strict_types=1);

namespace App\Services;

final class BoldService
{
    public function probeDataphoneConnection(array $settings): array
    {
        $apiUrl = rtrim(trim((string) ($settings['api_url'] ?? '')), '/');
        $apiKey = trim((string) ($settings['api_key'] ?? ''));

        if ($apiUrl === '') {
            return [
                'ok' => false,
                'message' => 'Falta la URL API de Bold.',
            ];
        }

        if ($apiKey === '') {
            return [
                'ok' => false,
                'message' => 'Falta la API Key de Bold.',
            ];
        }

        if ($this->looksLikeWrongDataphoneKey($apiKey)) {
            return [
                'ok' => false,
                'message' => 'La llave ingresada parece no ser la Llave de identidad de API Datáfono. Usa la Llave de identidad de API Integrations (no llave pública/llave secreta de Botón de pagos).',
            ];
        }

        if (!function_exists('curl_init')) {
            return [
                'ok' => false,
                'message' => 'cURL no está disponible en PHP. No se puede conectar con Bold.',
            ];
        }

        $headers = [
            'Accept: application/json',
            'Content-Type: application/json',
            'Authorization: x-api-key ' . $apiKey,
        ];

        $methodsResponse = $this->requestGet($apiUrl . '/payments/payment-methods', $headers);
        if (!(bool) ($methodsResponse['ok'] ?? false)) {
            return $methodsResponse;
        }

        $terminalsResponse = $this->requestGet($apiUrl . '/payments/binded-terminals', $headers);
        if (
            !(bool) ($terminalsResponse['ok'] ?? false)
            && !$this->isNoTerminalsLinkedResponse($terminalsResponse)
        ) {
            return $terminalsResponse;
        }

        $methodsPayload = is_array($methodsResponse['data'] ?? null) ? $methodsResponse['data'] : [];
        $terminalsPayload = is_array($terminalsResponse['data'] ?? null) ? $terminalsResponse['data'] : [];
        $terminalsMissing = $this->isNoTerminalsLinkedResponse($terminalsResponse);
        if ($terminalsMissing && $terminalsPayload === []) {
            $terminalsPayload = [
                'payload' => [
                    'available_terminals' => [],
                ],
            ];
        }

        $methods = [];
        if (isset($methodsPayload['payload']['payment_methods']) && is_array($methodsPayload['payload']['payment_methods'])) {
            $methods = $methodsPayload['payload']['payment_methods'];
        }

        $terminals = [];
        if (isset($terminalsPayload['payload']['terminals']) && is_array($terminalsPayload['payload']['terminals'])) {
            $terminals = $terminalsPayload['payload']['terminals'];
        } elseif (isset($terminalsPayload['payload']['available_terminals']) && is_array($terminalsPayload['payload']['available_terminals'])) {
            $terminals = $terminalsPayload['payload']['available_terminals'];
        } elseif (isset($terminalsPayload['payload']) && is_array($terminalsPayload['payload'])) {
            $terminals = $terminalsPayload['payload'];
        }

        return [
            'ok' => true,
            'status_code' => 200,
            'message' => $terminalsMissing
                ? 'Conexión API datáfono validada, pero no hay terminales vinculadas en Bold.'
                : 'Conexión API datáfono validada correctamente.',
            'methods_count' => count($methods),
            'terminals_count' => count($terminals),
            'terminals_missing' => $terminalsMissing,
            'data' => [
                'methods' => $methodsPayload,
                'terminals' => $terminalsPayload,
            ],
        ];
    }

    public function createPaymentLink(array $settings, array $payload): array
    {
        $publicKey = trim((string) ($settings['button_public_key'] ?? ''));
        if ($publicKey === '') {
            return [
                'ok' => false,
                'message' => 'Falta la llave pública de Botón de pagos.',
            ];
        }

        if (!function_exists('curl_init')) {
            return [
                'ok' => false,
                'message' => 'cURL no está disponible en PHP. No se puede conectar con Bold.',
            ];
        }

        $amountTypeRaw = strtoupper(trim((string) ($payload['amount_type'] ?? 'CLOSE')));
        $amountType = in_array($amountTypeRaw, ['CLOSE', 'OPEN'], true) ? $amountTypeRaw : 'CLOSE';
        $reference = trim((string) ($payload['reference'] ?? ''));
        $description = trim((string) ($payload['description'] ?? 'Pago de prueba Veltrix POS'));
        $currency = strtoupper(trim((string) ($payload['currency'] ?? 'COP')));
        if ($currency === '') {
            $currency = 'COP';
        }

        if ($reference === '') {
            return [
                'ok' => false,
                'message' => 'La referencia es obligatoria para crear el link de pago.',
            ];
        }

        $requestBody = [
            'amount_type' => $amountType,
            'reference' => $reference,
            'description' => $description,
        ];

        if ($amountType === 'CLOSE') {
            $totalAmount = (int) round((float) ($payload['total_amount'] ?? 0), 0);
            if ($totalAmount <= 0) {
                return [
                    'ok' => false,
                    'message' => 'Para monto cerrado debes enviar un valor mayor a 0.',
                ];
            }

            $requestBody['amount'] = [
                'currency' => $currency,
                'total_amount' => $totalAmount,
            ];
        }

        $endpoint = 'https://integrations.api.bold.co/online/link/v1';
        $ch = curl_init($endpoint);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 25);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Accept: application/json',
            'Content-Type: application/json',
            'Authorization: x-api-key ' . $publicKey,
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, (string) json_encode($requestBody, JSON_UNESCAPED_UNICODE));

        $body = curl_exec($ch);
        $statusCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if (!is_string($body)) {
            $body = '';
        }

        $response = json_decode($body, true);
        if (!is_array($response)) {
            $response = [];
        }

        if ($error !== '') {
            return [
                'ok' => false,
                'status_code' => $statusCode,
                'message' => 'Error de red creando link Bold: ' . $error,
            ];
        }

        if ($statusCode < 200 || $statusCode >= 300) {
            return [
                'ok' => false,
                'status_code' => $statusCode,
                'message' => $this->extractApiMessage($response, 'Bold respondió HTTP ' . $statusCode . ' al crear link de pago.', $body),
                'data' => $response,
            ];
        }

        $url = $this->extractCheckoutUrl($response);
        if ($url === '') {
            return [
                'ok' => false,
                'status_code' => $statusCode,
                'message' => 'Bold respondió exitosamente, pero no devolvió URL de checkout.',
                'data' => $response,
            ];
        }

        $paymentLink = $this->findFirstString($response, ['payment_link', 'paymentLink', 'id', 'identifier']);

        return [
            'ok' => true,
            'status_code' => $statusCode,
            'message' => 'Link de pago creado correctamente en Bold.',
            'url' => $url,
            'payment_link' => $paymentLink,
            'reference' => $reference,
            'data' => $response,
        ];
    }

    public function validatePaymentTransaction(array $settings, string $referenceId, float $expectedAmount = 0.0): array
    {
        $referenceId = trim($referenceId);
        if ($referenceId === '') {
            return [
                'ok' => false,
                'message' => 'La referencia de transacción Bold es obligatoria.',
            ];
        }

        $result = $this->fetchTransaction($settings, $referenceId);
        if (!(bool) ($result['ok'] ?? false)) {
            return $result;
        }

        $payload = is_array($result['data'] ?? null) ? $result['data'] : [];
        $statusRaw = $this->extractStatus($payload);
        $status = $this->normalizeStatus($statusRaw);

        if (!$this->isApprovedStatus($status)) {
            return [
                'ok' => false,
                'message' => 'La transacción Bold no está aprobada (estado: ' . ($statusRaw !== '' ? $statusRaw : 'desconocido') . ').',
                'status' => $status,
                'data' => $payload,
            ];
        }

        $amount = $this->extractAmount($payload);
        if ($expectedAmount > 0.0001 && $amount > 0.0001 && ($amount + 0.01) < $expectedAmount) {
            return [
                'ok' => false,
                'message' => 'El valor aprobado en Bold (' . number_format($amount, 2, '.', '') . ') es menor al total de la venta (' . number_format($expectedAmount, 2, '.', '') . ').',
                'status' => $status,
                'amount' => $amount,
                'data' => $payload,
            ];
        }

        return [
            'ok' => true,
            'message' => 'Transacción Bold validada correctamente.',
            'status' => $status,
            'amount' => $amount,
            'data' => $payload,
        ];
    }

    public function fetchTransaction(array $settings, string $referenceId): array
    {
        $apiUrl = rtrim(trim((string) ($settings['api_url'] ?? '')), '/');
        $apiKey = trim((string) ($settings['api_key'] ?? ''));

        if ($apiUrl === '') {
            return [
                'ok' => false,
                'message' => 'Falta la URL API de Bold.',
            ];
        }

        if ($apiKey === '') {
            return [
                'ok' => false,
                'message' => 'Falta la API Key de Bold.',
            ];
        }

        if (!function_exists('curl_init')) {
            return [
                'ok' => false,
                'message' => 'cURL no está disponible en PHP. No se puede conectar con Bold.',
            ];
        }

        if ($this->isIntegrationsDataphoneBaseUrl($apiUrl)) {
            return $this->fetchWebhookFallbackNotification($apiUrl, $apiKey, $referenceId);
        }

        $url = $apiUrl . '/v1/payment/' . rawurlencode(trim($referenceId));
        return $this->requestGet($url, [
            'Accept: application/json',
            'Authorization: x-api-key ' . $apiKey,
        ], 'Consulta de transacción Bold exitosa.');
    }

    private function fetchWebhookFallbackNotification(string $apiUrl, string $apiKey, string $referenceId): array
    {
        $url = $apiUrl
            . '/payments/webhook/notifications/'
            . rawurlencode(trim($referenceId))
            . '?is_external_reference=true';

        $response = $this->requestGet($url, [
            'Accept: application/json',
            'Authorization: x-api-key ' . $apiKey,
        ], 'Consulta de notificación Bold exitosa.');

        if (!(bool) ($response['ok'] ?? false)) {
            return $response;
        }

        $payload = is_array($response['data'] ?? null) ? $response['data'] : [];
        $notifications = [];
        if (isset($payload['notifications']) && is_array($payload['notifications'])) {
            $notifications = $payload['notifications'];
        }

        if ($notifications === []) {
            return [
                'ok' => false,
                'status_code' => (int) ($response['status_code'] ?? 200),
                'message' => 'No se encontró notificación de pago Bold para esa referencia.',
                'data' => $payload,
            ];
        }

        $first = $notifications[0];
        if (!is_array($first)) {
            return [
                'ok' => false,
                'status_code' => (int) ($response['status_code'] ?? 200),
                'message' => 'Bold respondió un formato de notificación no esperado.',
                'data' => $payload,
            ];
        }

        return [
            'ok' => true,
            'status_code' => (int) ($response['status_code'] ?? 200),
            'message' => 'Consulta de transacción Bold (fallback webhook) exitosa.',
            'data' => $first,
        ];
    }

    private function requestGet(string $url, array $headers, string $successMessage = 'Consulta de Bold exitosa.'): array
    {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 25);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $body = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if (!is_string($body)) {
            $body = '';
        }

        $payload = json_decode($body, true);
        if (!is_array($payload)) {
            $payload = [];
        }

        if ($error !== '') {
            return [
                'ok' => false,
                'status_code' => $status,
                'message' => 'Error de red consultando Bold: ' . $error,
            ];
        }

        if ($status < 200 || $status >= 300) {
            return [
                'ok' => false,
                'status_code' => $status,
                'message' => $this->extractApiMessage($payload, 'Bold respondió HTTP ' . $status . '.', $body),
                'data' => $payload,
            ];
        }

        return [
            'ok' => true,
            'status_code' => $status,
            'message' => $successMessage,
            'data' => $payload,
        ];
    }

    private function extractApiMessage(array $payload, string $fallback, string $rawBody = ''): string
    {
        $message = $this->findFirstString($payload, [
            'message', 'Message',
            'error', 'Error',
            'detail', 'Detail',
            'description', 'Description',
        ]);
        if ($message !== '') {
            $normalized = strtolower($message);
            if (str_contains($normalized, 'explicit deny in an identity-based policy')) {
                return 'Bold bloqueó esta llave por permisos (explicit deny). Tu comercio no tiene habilitada esta operación para API Datáfono o la llave pertenece a otro producto/ambiente.';
            }
            return $message;
        }

        $errors = $payload['errors'] ?? null;
        if (is_array($errors) && $errors !== []) {
            $parts = [];
            foreach ($errors as $err) {
                if (is_string($err) && trim($err) !== '') {
                    $parts[] = trim($err);
                    continue;
                }
                if (is_array($err)) {
                    $candidate = $this->findFirstString($err, ['message', 'detail', 'error', 'code']);
                    if ($candidate !== '') {
                        $parts[] = $candidate;
                    }
                }
            }
            if ($parts !== []) {
                return implode(' | ', array_slice($parts, 0, 3));
            }
        }

        if (isset($payload['payload']) && is_array($payload['payload'])) {
            $nestedMessage = $this->extractApiMessage((array) $payload['payload'], '', '');
            if ($nestedMessage !== '') {
                return $nestedMessage;
            }
        }

        $raw = trim($rawBody);
        if ($raw !== '') {
            $rawLower = strtolower($raw);
            if (str_contains($rawLower, 'missing authentication token')) {
                return 'Bold respondió "Missing Authentication Token": verifica que la URL base sea https://integrations.api.bold.co y que no tenga rutas extra.';
            }
            if (str_contains($raw, 'Invalid key=value pair') || str_contains($raw, 'Authorization header')) {
                return 'Bold rechazó el formato de autenticación. Verifica que uses la Llave de identidad de API Datáfono y no una llave secreta/pública.';
            }
            if (str_contains($rawLower, 'forbidden')) {
                return 'Bold respondió acceso denegado (403). Verifica la Llave de identidad correcta para API Datáfono y permisos activos en Integraciones.';
            }

            $plainRaw = trim((string) preg_replace('/\s+/', ' ', strip_tags($raw)));
            if ($plainRaw !== '') {
                $shortRaw = mb_substr($plainRaw, 0, 220);
                return $shortRaw;
            }
        }

        if (str_contains($fallback, 'HTTP 403')) {
            return 'HTTP 403 de Bold: usa la Llave de identidad de API Datáfono (no Botón de pagos), confirma que la llave sea del ambiente correcto (pruebas/producción) y que exista al menos un datáfono SmartPro vinculado.';
        }

        return $fallback;
    }

    private function looksLikeWrongDataphoneKey(string $apiKey): bool
    {
        $key = trim($apiKey);
        if ($key === '') {
            return false;
        }

        $lower = strtolower($key);
        if (
            str_starts_with($lower, 'pub_')
            || str_starts_with($lower, 'pk_')
            || str_starts_with($lower, 'sec_')
            || str_starts_with($lower, 'sk_')
        ) {
            return true;
        }

        return strlen($key) >= 20 && preg_match('/[=+\/]/', $key) === 1;
    }

    private function isNoTerminalsLinkedResponse(array $response): bool
    {
        $statusCode = (int) ($response['status_code'] ?? 0);
        $message = strtolower(trim((string) ($response['message'] ?? '')));

        if ($message !== '') {
            if (str_contains($message, 'available terminals not found')) {
                return true;
            }
            if (str_contains($message, 'terminal') && str_contains($message, 'not found')) {
                return true;
            }
            if (str_contains($message, 'no terminal')) {
                return true;
            }
        }

        if ($statusCode !== 404 && $statusCode !== 400) {
            return false;
        }

        $payload = $response['data'] ?? null;
        if (!is_array($payload)) {
            return false;
        }

        $serialized = strtolower((string) json_encode($payload, JSON_UNESCAPED_UNICODE));
        if ($serialized === '' || $serialized === 'null') {
            return false;
        }

        return str_contains($serialized, 'available terminals not found')
            || str_contains($serialized, 'terminal')
            || str_contains($serialized, 'no terminal');
    }

    private function extractStatus(array $payload): string
    {
        return $this->findFirstString($payload, ['status', 'payment_status', 'transaction_status', 'state', 'type', 'event_type']);
    }

    private function normalizeStatus(string $status): string
    {
        $status = strtolower(trim($status));
        if ($status === '') {
            return 'desconocido';
        }

        if (
            str_contains($status, 'reject')
            || str_contains($status, 'declin')
            || str_contains($status, 'fail')
            || str_contains($status, 'cancel')
            || str_contains($status, 'error')
        ) {
            return 'rechazada';
        }

        if (
            str_contains($status, 'pend')
            || str_contains($status, 'process')
            || str_contains($status, 'review')
            || str_contains($status, 'hold')
            || str_contains($status, 'wait')
            || str_contains($status, 'init')
        ) {
            return 'pendiente';
        }

        if (
            str_contains($status, 'approv')
            || str_contains($status, 'paid')
            || str_contains($status, 'success')
            || str_contains($status, 'succeed')
            || str_contains($status, 'complet')
            || str_contains($status, 'captur')
            || str_contains($status, 'authoriz')
            || str_contains($status, 'aprob')
        ) {
            return 'aprobada';
        }

        return $status;
    }

    private function isApprovedStatus(string $status): bool
    {
        return $status === 'aprobada';
    }

    private function extractAmount(array $payload): float
    {
        $candidates = [
            ['key' => 'amount', 'cents' => false],
            ['key' => 'transaction_amount', 'cents' => false],
            ['key' => 'total', 'cents' => false],
            ['key' => 'total_amount', 'cents' => false],
            ['key' => 'paid_amount', 'cents' => false],
            ['key' => 'value', 'cents' => false],
            ['key' => 'amount_in_cents', 'cents' => true],
        ];

        $bags = [$payload];
        foreach (['data', 'payment', 'transaction', 'payload', 'result'] as $key) {
            if (isset($payload[$key]) && is_array($payload[$key])) {
                $bags[] = $payload[$key];
            }
        }

        foreach ($bags as $bag) {
            if (isset($bag['amount']) && is_array($bag['amount'])) {
                $amountBag = $bag['amount'];
                foreach (['total', 'total_amount', 'value', 'amount'] as $amountKey) {
                    if (!array_key_exists($amountKey, $amountBag)) {
                        continue;
                    }
                    $parsedAmount = $this->toFloat($amountBag[$amountKey]);
                    if ($parsedAmount > 0.0) {
                        return round($parsedAmount, 2);
                    }
                }
            }

            foreach ($candidates as $candidate) {
                $key = (string) $candidate['key'];
                if (!array_key_exists($key, $bag)) {
                    continue;
                }

                $parsed = $this->toFloat($bag[$key]);
                if ($parsed <= 0.0) {
                    continue;
                }

                if ((bool) $candidate['cents']) {
                    return round($parsed / 100, 2);
                }

                return round($parsed, 2);
            }
        }

        return 0.0;
    }

    private function toFloat(mixed $value): float
    {
        if (is_int($value) || is_float($value)) {
            return (float) $value;
        }

        if (!is_string($value)) {
            return 0.0;
        }

        $raw = trim($value);
        if ($raw === '') {
            return 0.0;
        }

        $raw = preg_replace('/[^0-9,.-]/', '', $raw) ?? '';
        if ($raw === '') {
            return 0.0;
        }

        $lastComma = strrpos($raw, ',');
        $lastDot = strrpos($raw, '.');
        $decimalPos = max($lastComma === false ? -1 : $lastComma, $lastDot === false ? -1 : $lastDot);

        if ($decimalPos >= 0) {
            $integer = preg_replace('/[^0-9-]/', '', substr($raw, 0, $decimalPos)) ?? '0';
            $decimal = preg_replace('/[^0-9]/', '', substr($raw, $decimalPos + 1)) ?? '0';
            $raw = $integer . '.' . $decimal;
        } else {
            $raw = preg_replace('/[^0-9-]/', '', $raw) ?? '0';
        }

        if (!is_numeric($raw)) {
            return 0.0;
        }

        return (float) $raw;
    }

    private function findFirstString(array $payload, array $keys): string
    {
        $bags = [$payload];
        foreach (['data', 'payment', 'transaction', 'payload', 'result'] as $key) {
            if (isset($payload[$key]) && is_array($payload[$key])) {
                $bags[] = $payload[$key];
            }
        }

        foreach ($bags as $bag) {
            foreach ($keys as $key) {
                $value = trim((string) ($bag[$key] ?? ''));
                if ($value !== '') {
                    return $value;
                }
            }
        }

        return '';
    }

    private function extractCheckoutUrl(array $payload): string
    {
        $url = $this->findFirstString($payload, ['url', 'checkout_url', 'checkoutUrl', 'payment_url', 'paymentUrl']);
        if ($url !== '' && filter_var($url, FILTER_VALIDATE_URL)) {
            return $url;
        }

        $paymentLink = $this->findFirstString($payload, ['payment_link', 'paymentLink']);
        if ($paymentLink !== '') {
            $paymentLink = trim($paymentLink, '/');
            return 'https://checkout.bold.co/payment/' . rawurlencode($paymentLink);
        }

        return '';
    }

    private function isIntegrationsDataphoneBaseUrl(string $apiUrl): bool
    {
        $host = strtolower((string) parse_url($apiUrl, PHP_URL_HOST));
        return $host === 'integrations.api.bold.co' || str_ends_with($host, '.integrations.api.bold.co');
    }
}
