<?php
namespace App\Services\Invoicing;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Query\Builder;

class SatCatalogService
{
  //* Control Variables
    /** Time-to-Live (cache en segundos) */
    protected int $ttl = 60 * 60 * 6; // 6h

    protected string $connection = 'sat_catalog_db';
    protected function table(string $table): Builder
    {
        return DB::connection($this->connection)->table($table);
    }


  //* Basic Functions
    /**
     *  Obtiene lista de valores de una tabla/columna con filtros arbitrarios.
     *
     *  @param string   $table   Catalogo del SAT a consultar
     *  @param string   $column  Columna de valores a devolver
     *  @param array    $wheres  Filtros (Formato con AND implícito)
     *      - ['col' => 'valor', ...]  ()
     *      - [['col','op','valor'], ...]
     *
     *  @return array<string> Valores Filtrados del Catalogo
     */
    public function valuesWithFilters(
        string $table,
        string $column = 'clave',
        array  $wheres = []
    ): array {

        $cache_key = 'sat_catalog:'.md5(json_encode([
            't' => $table,
            'c' => $column,
            'w' => $wheres,
        ]));

        return Cache::remember($cache_key, $this->ttl, function () use ($table, $column, $wheres) {
            $q = $this->table($table);
                $this->applyWheres($q, $wheres);

            return $q->pluck($column)->map(fn($v) => (string)$v)->toArray();
        });
    }

    /**
     *  Versión corta del Extractor sin vigencia ni filtros.
     *
     *  @param string   $table   Catalogo del SAT a consultar
     *  @param string   $column  Columna de valores a devolver
     *
     *  @return array<string> Valores Filtrados del Catalogo
     */
    public function values(string $table, string $column = 'clave'): array
    {
        return $this->valuesWithFilters($table, $column);
    }

    /**
     *  Verifica que el valor proporcionado exista en el respectivo catálogo oficial del SAT
     *
     *  @param string   $table   Catalogo del SAT a consultar
     *  @param string   $column  Columna de valores a devolver
     *
     *  @return bool TRUE si el valor es válido dentro del catálogo, FALSE en caso contrario
     */
    public function existsWhere(
        string $table,
        string $column,
        $value,
        array $wheres = [],
    ): bool {
        if ($value === null || $value === '') return false;

        // Para performance, intenta resolver por consulta directa sin cache:
        $q = $this->table($table)->where($column, (string)$value);
            $this->applyWheres($q, $wheres);

        return $q->exists();
    }

  //* Acortadores de Consultas simples
    /**
     * @return array<string> Valores Filtrados del Catalogo Menor provisto
     */
    public function catalogItemsValues(
        string $catalogCode,
        string $column = 'item_key',
    ): array {
        return $this->valuesWithFilters(
            table: 'catalog_items',
            column: $column,
            wheres: ['catalog_code' => $catalogCode],
        );
    }

    /**
     *
     * @return array<string> Valores Filtrados del Catalogo de Productos/Servicios
     */
    public function prodServValues(string $column = 'prod_serv_key'): array
    {
        return $this->valuesWithFilters(
            table: 'catalog_prod_serv',
            column: $column,
            wheres: [],
        );
    }

    /**
     *
     * @return array<string> Valores Filtrados del Catalogo de Unidades de Medida
     */
    public function unitValues(string $column = 'unit_key'): array
    {
        return $this->valuesWithFilters(
            table: 'catalog_units',
            column: $column,
            wheres: [],
        );
    }


  //* Support Functions
    /**
     *  Aplica filtros mixtos al query builder.
     *
     * - Asociativos ['col' => 'valor', 'col2' => null] -> where col = 'valor' AND col2 IS NULL
     * - Arreglos:    [ ['col', 'op', 'valor'], ['otra', 'like', '%x%'] ]
     */
    protected function applyWheres(Builder $q, array $wheres): void
    {
        // Asociativos
        foreach ($wheres as $k => $v) {
            if (is_string($k)) {
                if (is_array($v)) {
                    // Permite whereIn: ['col' => ['A','B']]
                    $q->whereIn($k, $v);
                } else {
                    if (is_null($v)) {
                        $q->whereNull($k);
                    } else {
                        $q->where($k, $v);
                    }
                }
            }
        }

        // Arreglos de tripletas
        foreach ($wheres as $cond) {
            if (is_array($cond) && array_is_list($cond) && count($cond) >= 2) {
                // ['col','op','val'] o ['col','val'] (asume '=')
                $col = $cond[0];
                $op  = count($cond) === 2 ? '=' : $cond[1];
                $val = count($cond) === 2 ? $cond[1] : ($cond[2] ?? null);

                if (strtolower($op) === 'in' && is_array($val)) {
                    $q->whereIn($col, $val);
                } else {
                    if ($val === null && in_array($op, ['=','=='])) {
                        $q->whereNull($col);
                    } else {
                        $q->where($col, $op, $val);
                    }
                }
            }
        }
    }
}
