<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\GlobalSetting;

class WompiService
{
    private const GLOBAL_KEY_PUBLIC = 'wompi_public_key';
    private const GLOBAL_KEY_INTEGRITY = 'wompi_integrity_secret';
    private const GLOBAL_KEY_EVENTS = 'wompi_events_secret';
    private const GLOBAL_KEY_REDIRECT_URL = 'wompi_redirect_url';

    private static ?array $globalSettingsCache = null;

    public function isConfiguredForCheckout(): bool
    {
        return $this->publicKey() !== '' && $this->integritySecret() !== '';
    }

    public function isConfiguredForWebhook(): bool
    {
        return $this->eventsSecret() !== '';
    }

    public function checkoutUrl(): string
    {
        return (string) config('wompi.checkout_url', 'https://checkout.wompi.co/p/');
    }

    public function currency(): string
    {
        return strtoupper((string) config('wompi.currency', 'COP'));
    }

    public function paidCycleDays(): int
    {
        return max(1, (int) config('wompi.paid_cycle_days', 30));
    }

    public function redirectUrl(?string $companySlug = null): string
    {
        $configured = trim($this->runtimeValue(
            self::GLOBAL_KEY_REDIRECT_URL,
            (string) config('wompi.redirect_url', '')
        ));

        $redirectUrl = $this->isValidAbsoluteUrl($configured)
            ? $configured
            : trim((string) url('login'));

        if (!$this->isValidAbsoluteUrl($redirectUrl)) {
            $fallbackBase = rtrim(trim((string) config('app.url', '')), '/');
            if ($fallbackBase !== '') {
                $redirectUrl = $fallbackBase . '/login';
            }
        }

        $slug = trim((string) $companySlug);
        if ($slug === '') {
            return $redirectUrl;
        }

        return $this->appendQueryParam($redirectUrl, 'empresa', $slug);
    }

    public function buildCheckoutFields(
        string $reference,
        int $amountInCents,
        string $redirectUrl,
        ?string $customerEmail = null
    ): array {
        $currency = $this->currency();

        $fields = [
            'public-key' => $this->publicKey(),
            'currency' => $currency,
            'amount-in-cents' => (string) $amountInCents,
            'reference' => $reference,
            'signature:integrity' => $this->integritySignature($reference, $amountInCents, $currency),
            'redirect-url' => $redirectUrl,
        ];

        if ($customerEmail !== null && trim($customerEmail) !== '') {
            $fields['customer-data:email'] = strtolower(trim($customerEmail));
        }

        return $fields;
    }

    public function integritySignature(
        string $reference,
        int $amountInCents,
        string $currency,
        ?string $expirationTime = null
    ): string {
        $parts = [
            $reference,
            (string) $amountInCents,
            strtoupper($currency),
        ];

        if ($expirationTime !== null && $expirationTime !== '') {
            $parts[] = $expirationTime;
        }

        $parts[] = $this->integritySecret();

        return hash('sha256', implode('', $parts));
    }

    public function verifyEventChecksum(array $payload): bool
    {
        if (!$this->isConfiguredForWebhook()) {
            return false;
        }

        $signature = is_array($payload['signature'] ?? null) ? $payload['signature'] : [];
        $properties = is_array($signature['properties'] ?? null) ? $signature['properties'] : [];
        $checksum = strtoupper((string) ($signature['checksum'] ?? ''));
        $timestamp = (string) ($payload['timestamp'] ?? '');
        $data = is_array($payload['data'] ?? null) ? $payload['data'] : [];

        if ($checksum === '' || $timestamp === '' || $properties === []) {
            return false;
        }

        $concatenated = '';
        foreach ($properties as $propertyPath) {
            if (!is_string($propertyPath) || trim($propertyPath) === '') {
                return false;
            }

            $value = $this->valueByPath($data, $propertyPath);
            if (is_array($value) || is_object($value)) {
                return false;
            }

            $concatenated .= (string) $value;
        }

        $concatenated .= $timestamp . $this->eventsSecret();
        $generated = strtoupper(hash('sha256', $concatenated));

        return hash_equals($checksum, $generated);
    }

    public function valueByPath(array $data, string $path): mixed
    {
        $segments = explode('.', $path);
        $current = $data;

        foreach ($segments as $segment) {
            if (!is_array($current) || !array_key_exists($segment, $current)) {
                return null;
            }
            $current = $current[$segment];
        }

        return $current;
    }

    public function probeConnection(): array
    {
        $publicKey = $this->publicKey();
        $integritySecret = $this->integritySecret();
        $eventsSecret = $this->eventsSecret();

        if ($publicKey === '') {
            return [
                'ok' => false,
                'message' => 'Falta WOMPI_PUBLIC_KEY.',
            ];
        }

        if ($integritySecret === '' || $eventsSecret === '') {
            return [
                'ok' => false,
                'message' => 'Faltan llaves de firma (integrity y/o events).',
            ];
        }

        $probeUrls = $this->merchantProbeUrls($publicKey);
        $errors = [];

        foreach ($probeUrls as $url) {
            $response = $this->httpGetJson($url, 10);
            $status = (int) ($response['status'] ?? 0);
            $error = trim((string) ($response['error'] ?? ''));
            $json = is_array($response['json'] ?? null) ? $response['json'] : [];
            $data = is_array($json['data'] ?? null) ? $json['data'] : [];
            $merchantName = trim((string) ($data['name'] ?? ''));
            $merchantId = trim((string) ($data['id'] ?? ''));

            if ($error !== '') {
                $errors[] = $error;
                continue;
            }

            if ($status >= 200 && $status < 300 && $data !== []) {
                return [
                    'ok' => true,
                    'message' => 'Conexión Wompi correcta. Merchant: '
                        . ($merchantName !== '' ? $merchantName : 'ID ' . $merchantId)
                        . '.',
                ];
            }

            if ($status === 401 || $status === 403 || $status === 404) {
                $errors[] = 'La llave pública no fue reconocida por Wompi.';
                continue;
            }

            if ($status >= 400) {
                $errors[] = 'Wompi respondió HTTP ' . $status . '.';
                continue;
            }
        }

        return [
            'ok' => false,
            'message' => $errors !== []
                ? 'No se pudo validar conexión con Wompi: ' . implode(' ', array_unique($errors))
                : 'No fue posible validar conexión con Wompi en este momento.',
        ];
    }

    private function publicKey(): string
    {
        return $this->runtimeValue(self::GLOBAL_KEY_PUBLIC, (string) config('wompi.public_key', ''));
    }

    private function integritySecret(): string
    {
        return $this->runtimeValue(self::GLOBAL_KEY_INTEGRITY, (string) config('wompi.integrity_secret', ''));
    }

    private function eventsSecret(): string
    {
        return $this->runtimeValue(self::GLOBAL_KEY_EVENTS, (string) config('wompi.events_secret', ''));
    }

    private function runtimeValue(string $key, string $fallback): string
    {
        $settings = $this->globalSettings();
        $value = trim((string) ($settings[$key] ?? ''));
        if ($value !== '') {
            return $value;
        }

        return trim($fallback);
    }

    private function globalSettings(): array
    {
        if (self::$globalSettingsCache !== null) {
            return self::$globalSettingsCache;
        }

        try {
            self::$globalSettingsCache = (new GlobalSetting())->getMany([
                self::GLOBAL_KEY_PUBLIC,
                self::GLOBAL_KEY_INTEGRITY,
                self::GLOBAL_KEY_EVENTS,
                self::GLOBAL_KEY_REDIRECT_URL,
            ]);
        } catch (\Throwable) {
            self::$globalSettingsCache = [];
        }

        return self::$globalSettingsCache;
    }

    private function merchantProbeUrls(string $publicKey): array
    {
        $publicKey = trim($publicKey);
        if ($publicKey === '') {
            return [];
        }

        $encoded = rawurlencode($publicKey);
        $looksTest = str_contains($publicKey, 'test');

        $urls = $looksTest
            ? [
                'https://sandbox.wompi.co/v1/merchants/' . $encoded,
                'https://production.wompi.co/v1/merchants/' . $encoded,
            ]
            : [
                'https://production.wompi.co/v1/merchants/' . $encoded,
                'https://sandbox.wompi.co/v1/merchants/' . $encoded,
            ];

        return array_values(array_unique($urls));
    }

    private function isValidAbsoluteUrl(string $url): bool
    {
        $url = trim($url);
        if ($url === '') {
            return false;
        }

        if (filter_var($url, FILTER_VALIDATE_URL) === false) {
            return false;
        }

        return preg_match('/^https?:\/\//i', $url) === 1;
    }

    private function appendQueryParam(string $url, string $key, string $value): string
    {
        $parts = parse_url($url);
        if (!is_array($parts)) {
            return $url;
        }

        $query = [];
        parse_str((string) ($parts['query'] ?? ''), $query);
        $query[$key] = $value;

        $parts['query'] = http_build_query($query);

        return $this->buildUrlFromParts($parts);
    }

    private function buildUrlFromParts(array $parts): string
    {
        $scheme = isset($parts['scheme']) ? $parts['scheme'] . '://' : '';
        $user = (string) ($parts['user'] ?? '');
        $pass = (string) ($parts['pass'] ?? '');
        $auth = '';
        if ($user !== '') {
            $auth = $user;
            if ($pass !== '') {
                $auth .= ':' . $pass;
            }
            $auth .= '@';
        }

        $host = (string) ($parts['host'] ?? '');
        $port = isset($parts['port']) ? ':' . (string) $parts['port'] : '';
        $path = (string) ($parts['path'] ?? '');
        $query = (string) ($parts['query'] ?? '');
        $fragment = (string) ($parts['fragment'] ?? '');

        $url = $scheme . $auth . $host . $port . $path;
        if ($query !== '') {
            $url .= '?' . $query;
        }
        if ($fragment !== '') {
            $url .= '#' . $fragment;
        }

        return $url;
    }

    private function httpGetJson(string $url, int $timeoutSeconds = 10): array
    {
        if (!function_exists('curl_init')) {
            return [
                'status' => 0,
                'error' => 'cURL no está disponible en PHP.',
                'body' => null,
                'json' => null,
            ];
        }

        $ch = curl_init($url);
        if ($ch === false) {
            return [
                'status' => 0,
                'error' => 'No se pudo inicializar cURL.',
                'body' => null,
                'json' => null,
            ];
        }

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 2,
            CURLOPT_CONNECTTIMEOUT => max(1, $timeoutSeconds),
            CURLOPT_TIMEOUT => max(1, $timeoutSeconds),
            CURLOPT_HTTPHEADER => [
                'Accept: application/json',
                'User-Agent: VeltrixPOS/1.0',
            ],
        ]);

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

        $json = null;
        if (is_string($body) && $body !== '') {
            $decoded = json_decode($body, true);
            if (is_array($decoded)) {
                $json = $decoded;
            }
        }

        return [
            'status' => $status,
            'error' => trim($error),
            'body' => is_string($body) ? $body : null,
            'json' => $json,
        ];
    }
}
