<?php
declare(strict_types=1);

namespace App\Services;

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

class CabysService
{
    /**
     * Busca CABYS en la tabla local cabys_items por código o descripción.
     * Si no hay resultados, intenta consultar en Hacienda por descripción (q)
     * y guarda los resultados en caché local.
     *
     * @return array<int,array<string,mixed>>
     */
    public static function buscarLocal(string $term, int $limit = 20): array
    {
        $term = trim($term);
        if ($term === '') {
            return [];
        }

        $pdo = Connection::getPdo();

        // 1) Intentar búsqueda local primero
        $soloDigitos = preg_replace('/\D+/', '', $term);
        $buscarPorCodigo = (strlen($soloDigitos) >= 6);

        if ($buscarPorCodigo) {
            $sql = "
                SELECT id, codigo, descripcion, impuesto, unidad
                FROM cabys_items
                WHERE codigo LIKE :codigo
                  AND activo = 1
                ORDER BY codigo ASC
                LIMIT :limit
            ";

            $stmt = $pdo->prepare($sql);
            $likeCodigo = $soloDigitos . '%';
            $stmt->bindValue(':codigo', $likeCodigo, PDO::PARAM_STR);
            $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
            $stmt->execute();
        } else {
            $sql = "
                SELECT id, codigo, descripcion, impuesto, unidad
                FROM cabys_items
                WHERE descripcion LIKE :term
                  AND activo = 1
                ORDER BY descripcion ASC
                LIMIT :limit
            ";

            $stmt = $pdo->prepare($sql);
            $likeTerm = '%' . $term . '%';
            $stmt->bindValue(':term', $likeTerm, PDO::PARAM_STR);
            $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
            $stmt->execute();
        }

        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

        // Si encontró algo en caché, devolver eso
        if ($rows && count($rows) > 0) {
            return array_map(static function (array $row): array {
                return [
                    'id'          => (int)$row['id'],
                    'codigo'      => (string)$row['codigo'],
                    'descripcion' => (string)$row['descripcion'],
                    'impuesto'    => $row['impuesto'] !== null ? (float)$row['impuesto'] : null,
                    'unidad'      => $row['unidad'] !== null ? (string)$row['unidad'] : null,
                ];
            }, $rows);
        }

        // 2) Si no hubo resultados locales, intentamos consultar en Hacienda por descripción
        $desdeHacienda = self::consultarCabysDescripcionHacienda($term, $limit);

        if (empty($desdeHacienda)) {
            // Nada en Hacienda para ese término
            return [];
        }

        // 3) Guardar en caché local (insert ignore por si ya existe algún código)
        $sqlInsert = "
            INSERT INTO cabys_items
              (codigo, descripcion, impuesto, unidad, activo, fuente, raw_response, creado_en)
            VALUES
              (:codigo, :descripcion, :impuesto, :unidad, 1, 'hacienda', :raw_response, NOW())
            ON DUPLICATE KEY UPDATE
              descripcion = VALUES(descripcion),
              impuesto    = VALUES(impuesto),
              unidad      = VALUES(unidad),
              raw_response= VALUES(raw_response),
              actualizado_en = NOW()
        ";

        $stmtIns = $pdo->prepare($sqlInsert);

        foreach ($desdeHacienda as $item) {
            $stmtIns->execute([
                ':codigo'       => $item['codigo'],
                ':descripcion'  => $item['descripcion'],
                ':impuesto'     => $item['impuesto'],
                ':unidad'       => $item['unidad'],
                ':raw_response' => json_encode($item['raw'] ?? [], JSON_UNESCAPED_UNICODE),
            ]);
        }

        // 4) Devolver la misma lista (sin necesidad de volver a leer BD)
        return array_map(static function (array $item): array {
            return [
                'id'          => 0, // no sabemos el id recién insertado para cada uno, pero para búsqueda no es crítico
                'codigo'      => (string)$item['codigo'],
                'descripcion' => (string)$item['descripcion'],
                'impuesto'    => $item['impuesto'],
                'unidad'      => (string)$item['unidad'],
            ];
        }, $desdeHacienda);
    }

    /**
     * Obtiene un CABYS por código. Si no existe localmente, intenta
     * consultarlo en la API pública de Hacienda y lo guarda en caché.
     *
     * @return array<string,mixed>|null
     */
    public static function obtenerPorCodigo(string $codigo): ?array
    {
        $codigo = preg_replace('/\D+/', '', $codigo);
        if ($codigo === '') {
            return null;
        }

        $pdo = Connection::getPdo();

        // 1) Buscar en caché local
        $sql = "
            SELECT id, codigo, descripcion, impuesto, unidad, raw_response
            FROM cabys_items
            WHERE codigo = :codigo
              AND activo = 1
            LIMIT 1
        ";

        $stmt = $pdo->prepare($sql);
        $stmt->execute([':codigo' => $codigo]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($row) {
            return [
                'id'          => (int)$row['id'],
                'codigo'      => (string)$row['codigo'],
                'descripcion' => (string)$row['descripcion'],
                'impuesto'    => $row['impuesto'] !== null ? (float)$row['impuesto'] : null,
                'unidad'      => $row['unidad'] !== null ? (string)$row['unidad'] : null,
                'raw'         => $row['raw_response'],
            ];
        }

        // 2) Si no existe, consultamos la API de Hacienda por código
        $fromHacienda = self::consultarCabysCodigoHacienda($codigo);

        if ($fromHacienda === null) {
            return null;
        }

        // 3) Guardar en caché local
        $sqlInsert = "
            INSERT INTO cabys_items
              (codigo, descripcion, impuesto, unidad, activo, fuente, raw_response, creado_en)
            VALUES
              (:codigo, :descripcion, :impuesto, :unidad, 1, 'hacienda', :raw_response, NOW())
            ON DUPLICATE KEY UPDATE
              descripcion = VALUES(descripcion),
              impuesto    = VALUES(impuesto),
              unidad      = VALUES(unidad),
              raw_response= VALUES(raw_response),
              actualizado_en = NOW()
        ";

        $stmtIns = $pdo->prepare($sqlInsert);
        $stmtIns->execute([
            ':codigo'       => $fromHacienda['codigo'],
            ':descripcion'  => $fromHacienda['descripcion'],
            ':impuesto'     => $fromHacienda['impuesto'],
            ':unidad'       => $fromHacienda['unidad'],
            ':raw_response' => json_encode($fromHacienda['raw'] ?? [], JSON_UNESCAPED_UNICODE),
        ]);

        $id = (int)$pdo->lastInsertId();

        return [
            'id'          => $id,
            'codigo'      => $fromHacienda['codigo'],
            'descripcion' => $fromHacienda['descripcion'],
            'impuesto'    => $fromHacienda['impuesto'],
            'unidad'      => $fromHacienda['unidad'],
            'raw'         => json_encode($fromHacienda['raw'] ?? [], JSON_UNESCAPED_UNICODE),
        ];
    }

    /**
     * Consulta en Hacienda por CÓDIGO:
     *   https://api.hacienda.go.cr/fe/cabys?codigo=...
     *
     * @return array<string,mixed>|null
     */
    private static function consultarCabysCodigoHacienda(string $codigo): ?array
    {
        $url = 'https://api.hacienda.go.cr/fe/cabys?codigo=' . urlencode($codigo);

        $data = self::httpGetJson($url);
        if (!is_array($data)) {
            return null;
        }

        return self::mapearCabysDesdeHacienda($data, $codigo);
    }

    /**
     * Consulta en Hacienda por DESCRIPCIÓN:
     *   https://api.hacienda.go.cr/fe/cabys?q=...&top=...
     *
     * Devuelve lista de ítems.
     *
     * @return array<int,array<string,mixed>>
     */
    private static function consultarCabysDescripcionHacienda(string $term, int $limit = 20): array
    {
        $top = $limit > 0 && $limit <= 50 ? $limit : 20;

        $url = 'https://api.hacienda.go.cr/fe/cabys?q=' . urlencode($term) . '&top=' . $top;

        $data = self::httpGetJson($url);
        if ($data === null) {
            return [];
        }

        // La respuesta puede ser un array de ítems o un objeto con campo "results", "items", etc.
        $items = [];

        if (isset($data[0]) && is_array($data[0])) {
            // Es un array directo
            $items = $data;
        } elseif (isset($data['items']) && is_array($data['items'])) {
            $items = $data['items'];
        } elseif (isset($data['results']) && is_array($data['results'])) {
            $items = $data['results'];
        }

        $salida = [];

        foreach ($items as $raw) {
            if (!is_array($raw)) {
                continue;
            }

            $mapeado = self::mapearCabysDesdeHacienda($raw);
            if ($mapeado === null) {
                continue;
            }

            $salida[] = [
                'codigo'      => $mapeado['codigo'],
                'descripcion' => $mapeado['descripcion'],
                'impuesto'    => $mapeado['impuesto'],
                'unidad'      => $mapeado['unidad'],
                'raw'         => $raw,
            ];
        }

        return $salida;
    }

    /**
     * Hace un GET simple y devuelve JSON decodificado o null si falla.
     *
     * @return array<string,mixed>|array<int,mixed>|null
     */
    private static function httpGetJson(string $url): array|null
    {
        $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) {
            curl_close($ch);
            return null;
        }

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

        if ($httpCode !== 200) {
            return null;
        }

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

        return $data;
    }

    /**
     * Mapea la estructura de Hacienda a nuestra estructura interna.
     * Ajusta aquí los nombres de campos según lo que devuelva realmente la API.
     *
     * @param array<string,mixed> $data
     */
    private static function mapearCabysDesdeHacienda(array $data, ?string $codigoFallback = null): ?array
    {
        // Basado en documentación y ejemplos: puede variar según implementación real.
        $codigo = $data['codigo'] ?? $codigoFallback;
        if (!$codigo) {
            return null;
        }

        $descripcion = $data['descripcion'] ?? ($data['descripcionCabys'] ?? '');
        $impuesto    = isset($data['impuesto']) ? (float)$data['impuesto'] : null;
        $unidad      = $data['unidadMedida'] ?? ($data['unidad'] ?? 'Sp');

        if ($descripcion === '') {
            $descripcion = 'CABYS ' . $codigo;
        }

        return [
            'codigo'      => (string)$codigo,
            'descripcion' => (string)$descripcion,
            'impuesto'    => $impuesto,
            'unidad'      => (string)$unidad,
            'raw'         => $data,
        ];
    }
}
