<?php
declare(strict_types=1);

namespace App\Services;

use App\Database\Connection;
use PDO;
use RuntimeException;

class ContribuyenteService
{
    /**
     * Consulta un contribuyente por identificación.
     *
     * Flujo:
     *  1. Normaliza la identificación (solo dígitos).
     *  2. Revisa caché local (contribuyentes_ae).
     *     - Si hay 200 OK y datos recientes => devuelve caché.
     *  3. Si no hay o está viejo, llama al API de Hacienda (fe/ae).
     *  4. Guarda/actualiza en BD y devuelve datos.
     *
     * @return array<string,mixed>|null
     */
    public static function consultar(string $identificacion, bool $forceRefresh = false): ?array
    {
        $identificacion = preg_replace('/\D+/', '', $identificacion ?? '');
        if ($identificacion === '' || strlen($identificacion) < 9 || strlen($identificacion) > 12) {
            // 9–12 dígitos según doc de Hacienda
            return null;
        }

        $pdo = Connection::getPdo();

        // 1) Consultar caché local
        if (!$forceRefresh) {
            $sql = "
                SELECT id, identificacion, nombre, tipo_identificacion, situacion, regimen,
                       es_extranjero, actividades, raw_response, ultimo_http_code, actualizado_en
                FROM contribuyentes_ae
                WHERE identificacion = :id
                LIMIT 1
            ";
            $stmt = $pdo->prepare($sql);
            $stmt->execute([':id' => $identificacion]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($row) {
                // Si el último HTTP fue 200 y los datos no son viejos (ej: 30 días),
                // devolvemos caché.
                if ((int)$row['ultimo_http_code'] === 200 && self::esReciente($row['actualizado_en'])) {
                    return self::mapearDesdeFila($row);
                }
            }
        }

        // 2) Llamar al API de Hacienda (fe/ae)
        $url = 'https://api.hacienda.go.cr/fe/ae?identificacion=' . urlencode($identificacion);
        $data = self::httpGetJson($url, $httpCode);

        // 3) Manejo según código HTTP
        if ($httpCode === 404) {
            // No encontrado: guardamos registro vacío con 404
            self::guardarResultado(
                $identificacion,
                null,
                null,
                null,
                null,
                false,
                null,
                $data,
                404,
                'No encontrado en Hacienda'
            );
            return null;
        }

        if ($httpCode === 429) {
            // Límite de consumo: si tenemos caché viejo, lo devolvemos;
            // si no hay caché, devolvemos null y que el Controller decida.
            $sql = "
                SELECT id, identificacion, nombre, tipo_identificacion, situacion, regimen,
                       es_extranjero, actividades, raw_response, ultimo_http_code, actualizado_en
                FROM contribuyentes_ae
                WHERE identificacion = :id
                LIMIT 1
            ";
            $stmt = $pdo->prepare($sql);
            $stmt->execute([':id' => $identificacion]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($row) {
                return self::mapearDesdeFila($row) + [
                    'warning' => 'Se devolvieron datos de caché porque Hacienda respondió 429 (límite de consumo).',
                ];
            }

            // Sin caché => guardamos error y devolvemos null
            self::guardarResultado(
                $identificacion,
                null,
                null,
                null,
                null,
                false,
                null,
                $data,
                429,
                'Límite de consumo (429) en Hacienda'
            );
            return null;
        }

        if ($httpCode !== 200 || !is_array($data)) {
            // Error genérico
            self::guardarResultado(
                $identificacion,
                null,
                null,
                null,
                null,
                false,
                null,
                $data,
                $httpCode,
                'Error al consultar Hacienda: HTTP ' . $httpCode
            );
            return null;
        }

        // 4) Mapear data de Hacienda a nuestra estructura
        $mapeado = self::mapearDesdeHacienda($identificacion, $data);

        // 5) Guardar / actualizar en caché
        self::guardarResultado(
            $identificacion,
            $mapeado['nombre'],
            $mapeado['tipo_identificacion'],
            $mapeado['situacion'],
            $mapeado['regimen'],
            $mapeado['es_extranjero'],
            $mapeado['actividades'],
            $data,
            200,
            null
        );

        return $mapeado + [
            'raw' => $data,
        ];
    }

    /**
     * Determina si una fecha es "reciente" (ej: 30 días).
     */
    private static function esReciente(?string $fecha): bool
    {
        if ($fecha === null) {
            return false;
        }
        $ts = strtotime($fecha);
        if ($ts === false) {
            return false;
        }
        $hace30dias = strtotime('-30 days');
        return $ts >= $hace30dias;
    }

    /**
     * Mapea una fila de BD a array de salida.
     *
     * @param array<string,mixed> $row
     * @return array<string,mixed>
     */
    private static function mapearDesdeFila(array $row): array
    {
        return [
            'identificacion'     => (string)$row['identificacion'],
            'nombre'             => (string)$row['nombre'],
            'tipo_identificacion'=> $row['tipo_identificacion'] !== null ? (string)$row['tipo_identificacion'] : null,
            'situacion'          => $row['situacion'] !== null ? (string)$row['situacion'] : null,
            'regimen'            => $row['regimen'] !== null ? (string)$row['regimen'] : null,
            'es_extranjero'      => (bool)$row['es_extranjero'],
            'actividades'        => $row['actividades'] ? json_decode((string)$row['actividades'], true) : null,
        ];
    }

    /**
     * Mapea la estructura de Hacienda (fe/ae) a nuestra estructura interna.
     *
     * La documentación indica que se obtienen: nombre, tipo identificación, régimen,
     * situación tributaria y actividades económicas. :contentReference[oaicite:2]{index=2}
     *
     * @param array<string,mixed> $data
     * @return array<string,mixed>
     */
    private static function mapearDesdeHacienda(string $identificacion, array $data): array
    {
        // Los nombres de campos pueden variar; ajusta aquí según lo que veas en Postman.
        $nombre = $data['nombre'] ?? ($data['nombreCompleto'] ?? '');
        $tipo   = $data['tipoIdentificacion'] ?? ($data['tipo'] ?? null);
        $sit    = $data['situacion'] ?? ($data['estado'] ?? null);
        $reg    = $data['regimen'] ?? ($data['regimenTributario'] ?? null);

        $esExtranjero = false;
        if (isset($data['esExtranjero'])) {
            $esExtranjero = (bool)$data['esExtranjero'];
        } elseif ($tipo === 'DIMEX' || $tipo === 'NITE') {
            $esExtranjero = true;
        }

        // Actividades: puede venir como "actividades" o similar.
        $actividades = null;
        if (isset($data['actividades']) && is_array($data['actividades'])) {
            $actividades = $data['actividades'];
        } elseif (isset($data['actividadesEconomicas']) && is_array($data['actividadesEconomicas'])) {
            $actividades = $data['actividadesEconomicas'];
        }

        if ($nombre === '') {
            $nombre = 'SIN NOMBRE (' . $identificacion . ')';
        }

        return [
            'identificacion'      => $identificacion,
            'nombre'              => (string)$nombre,
            'tipo_identificacion' => $tipo !== null ? (string)$tipo : null,
            'situacion'           => $sit !== null ? (string)$sit : null,
            'regimen'             => $reg !== null ? (string)$reg : null,
            'es_extranjero'       => $esExtranjero,
            'actividades'         => $actividades,
        ];
    }

    /**
     * Guarda o actualiza un resultado en la tabla contribuyentes_ae.
     *
     * @param array<string,mixed>|null $actividades
     * @param array<string,mixed>|array<int,mixed>|null $raw
     */
    private static function guardarResultado(
        string $identificacion,
        ?string $nombre,
        ?string $tipoIdentificacion,
        ?string $situacion,
        ?string $regimen,
        bool $esExtranjero,
        ?array $actividades,
        $raw,
        ?int $httpCode,
        ?string $errorMessage
    ): void {
        $pdo = Connection::getPdo();

        $sql = "
            INSERT INTO contribuyentes_ae
              (identificacion, nombre, tipo_identificacion, situacion, regimen,
               es_extranjero, actividades, fuente, raw_response,
               ultimo_http_code, error_message, creado_en, ultimo_intento)
            VALUES
              (:identificacion, :nombre, :tipo_identificacion, :situacion, :regimen,
               :es_extranjero, :actividades, 'hacienda', :raw_response,
               :ultimo_http_code, :error_message, NOW(), NOW())
            ON DUPLICATE KEY UPDATE
              nombre             = VALUES(nombre),
              tipo_identificacion= VALUES(tipo_identificacion),
              situacion          = VALUES(situacion),
              regimen            = VALUES(regimen),
              es_extranjero      = VALUES(es_extranjero),
              actividades        = VALUES(actividades),
              raw_response       = VALUES(raw_response),
              ultimo_http_code   = VALUES(ultimo_http_code),
              error_message      = VALUES(error_message),
              ultimo_intento     = NOW(),
              actualizado_en     = NOW()
        ";

        $stmt = $pdo->prepare($sql);

        $actividadesJson = $actividades !== null ? json_encode($actividades, JSON_UNESCAPED_UNICODE) : null;
        $rawJson = $raw !== null ? json_encode($raw, JSON_UNESCAPED_UNICODE) : null;

        $stmt->execute([
            ':identificacion'      => $identificacion,
            ':nombre'              => $nombre ?? 'SIN NOMBRE (' . $identificacion . ')',
            ':tipo_identificacion' => $tipoIdentificacion,
            ':situacion'           => $situacion,
            ':regimen'             => $regimen,
            ':es_extranjero'       => $esExtranjero ? 1 : 0,
            ':actividades'         => $actividadesJson,
            ':raw_response'        => $rawJson,
            ':ultimo_http_code'    => $httpCode,
            ':error_message'       => $errorMessage,
        ]);
    }

    /**
     * Hace un GET y devuelve JSON + código HTTP.
     *
     * @param-out int $httpCode
     * @return array<string,mixed>|array<int,mixed>|null
     */
    private static function httpGetJson(string $url, ?int &$httpCode): ?array
    {
        $httpCode = 0;

        $ch = curl_init();
        if ($ch === false) {
            throw new RuntimeException('No se pudo inicializar cURL');
        }

        curl_setopt_array($ch, [
            CURLOPT_URL            => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 10,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
        ]);

        $response = curl_exec($ch);

        if ($response === false) {
            $httpCode = 0;
            curl_close($ch);
            return null;
        }

        $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        $data = json_decode($response, true);
        if (!is_array($data)) {
            return null;
        }

        return $data;
    }
}
