<?php

namespace Koneko\SatCatalogs\Services;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class SatCatalogService
{
    protected $catalogs = [
        'banco' => [
            'table' => 'sat_banco',
            'key' => 'c_banco',
            'value' => 'descripcion AS item',
            'order_by' => 'descripcion',
            'search_columns' => ['descripcion'],
            'use_status' => true,
            'limit' => 10
        ],
        'clave_prod_serv' => [
            'table' => 'sat_clave_prod_serv',
            'key' => 'c_clave_prod_serv',
            'value' => "CONCAT_WS(' - ', c_clave_prod_serv , descripcion) as item",
            'search_type' => 'MATCH',
            'search_columns' => ['c_clave_prod_serv_text', 'descripcion'],
        ],
        'clave_unidad' => [
            'table' => 'sat_clave_unidad',
            'key' => 'c_clave_unidad',
            'value' => "CONCAT_WS(' - ', c_clave_unidad , nombre) as item",
            'search_columns' => ['c_clave_unidad', 'nombre'],
            'limit' => 20
        ],
        'deduccion' => [
            'table' => 'sat_deduccion',
            'key' => 'c_deduccion',
            'value' => "CONCAT_WS(' - ', c_deduccion , descripcion) as item",
            'search_columns' => ['c_deduccion', 'descripcion'],
            'limit' => 20
        ],
        'forma_pago' => [
            'table' => 'sat_forma_pago',
            'key' => 'c_forma_pago',
            'value' => "CONCAT_WS(' - ', c_forma_pago , descripcion) as item",
            'search_columns' => ['c_forma_pago', 'descripcion'],
            'limit' => 10
        ],
        'moneda' => [
            'table' => 'sat_moneda',
            'key' => 'c_moneda',
            'value' => "CONCAT_WS(' - ', c_moneda , descripcion) as item",
            'order_by' => 'descripcion',
            'search_columns' => ['c_moneda', 'descripcion'],
            'limit' => 10
        ],
        'pais' => [
            'table' => 'sat_pais',
            'key' => 'c_pais',
            'value' => 'descripcion AS item',
            'order_by' => 'descripcion',
            'search_columns' => ['c_pais', 'descripcion'],
            'limit' => 10
        ],

        'estado' => [
            'table' => 'sat_estado',
            'key' => 'c_estado',
            'value' => 'nombre_del_estado AS item',
            'order_by' => 'nombre_del_estado',
            'extra_conditions' => ['c_pais'],
            'search_columns' => ['nombre_del_estado'],
        ],
        'municipio' => [
            'table' => 'sat_municipio',
            'key' => 'c_municipio',
            'value' => 'descripcion AS item',
            'order_by' => 'descripcion',
            'extra_conditions' => ['c_estado'],
            'search_columns' => ['descripcion'],
        ],
        'localidad' => [
            'table' => 'sat_localidad',
            'key' => 'c_localidad',
            'value' => 'descripcion AS item',
            'order_by' => 'descripcion',
            'extra_conditions' => ['c_estado'],
            'search_columns' => ['descripcion'],
        ],
        'colonia' => [
            'table' => 'sat_colonia',
            'joins' => [
                [
                    'table' => 'sat_codigo_postal',
                    'first' => 'sat_colonia.c_codigo_postal',
                    'second' => 'sat_codigo_postal.c_codigo_postal',
                ],
            ],
            'key' => 'sat_colonia.c_colonia',
            'value' => 'sat_colonia.nombre_del_asentamiento AS item',
            'columns' => [
                'sat_colonia.c_codigo_postal',
            ],
            'order_by' => 'sat_colonia.nombre_del_asentamiento',
            'extra_conditions' => ['sat_codigo_postal.c_codigo_postal', 'sat_codigo_postal.c_estado', 'sat_codigo_postal.c_municipio', 'sat_colonia.c_colonia'],
            'search_columns' => ['sat_colonia.nombre_del_asentamiento'],
        ],
        'codigo_postal' => [
            'table' => 'sat_codigo_postal',
            'joins' => [
                [
                    'table' => 'sat_localidad',
                    'first' => 'sat_codigo_postal.c_localidad',
                    'second' => 'sat_localidad.c_localidad',
                    'and' => ['sat_codigo_postal.c_estado = sat_localidad.c_estado'],
                    'type' => 'leftJoin',
                ],
                [
                    'table' => 'sat_municipio',
                    'first' => 'sat_codigo_postal.c_municipio',
                    'second' => 'sat_municipio.c_municipio',
                    'and' => ['sat_codigo_postal.c_estado = sat_municipio.c_estado'],
                    'type' => 'leftJoin',
                ],
                [
                    'table' => 'sat_estado',
                    'first' => 'sat_codigo_postal.c_estado',
                    'second' => 'sat_estado.c_estado',
                    'type' => 'leftJoin',
                ],
            ],
            'key'   => 'sat_codigo_postal.c_codigo_postal',
            'columns' => [
                'sat_codigo_postal.c_estado',
                'sat_estado.nombre_del_estado as estado',
                'sat_codigo_postal.c_localidad',
                'sat_localidad.descripcion as localidad',
                'sat_codigo_postal.c_municipio',
                'sat_municipio.descripcion as municipio',
            ],
            'search_columns' => ['sat_codigo_postal.c_codigo_postal'],
        ],
    ];

    protected $fixedCatalogs = [
        'exportacion' => [
            1 => 'No aplica',
            2 => 'Definitiva con clave A1',
            3 => 'Temporal',
            4 => 'Definitiva con clave distinta a A1 o cuando no existe enajenación en términos del CFF'
        ],
        'horas_extra' => [
            '01' => 'Dobles',
            '02' => 'Triples',
            '03' => 'Simples'
        ],
        'impuestos' => [
            1 => 'ISR',
            2 => 'IVA',
            3 => 'IEPS'
        ],
        'incapacidad' => [
            1 => 'Riesgo de trabajo',
            2 => 'Enfermedad en general',
            3 => 'Maternidad',
            4 => 'Licencia por cuidados médicos de hijos diagnosticados con cáncer'
        ],
        'jornada' => [
            '01' => 'Diurna',
            '02' => 'Nocturna',
            '03' => 'Mixta',
            '04' => 'Por hora',
            '05' => 'Reducida',
            '06' => 'Continuada',
            '07' => 'Partida',
            '08' => 'Por turnos',
            '99' => 'Otra Jornada'
        ],
        'metodo_pago' => [
            'PUE' => 'Pago en una sola exhibición',
            'PPD' => 'Pago en parcialidades o diferido'
        ],
        'nomina' => [
            'O' => 'Nómina ordinaria',
            'E' => 'Nómina extraordinaria'
        ],
        'objeto_imp' => [
            1 => 'No objeto de impuesto.',
            2 => 'Sí objeto de impuesto.',
            3 => 'Sí objeto del impuesto y no obligado al desglose.',
            4 => 'Sí objeto del impuesto y no causa impuesto.',
            5 => 'Sí objeto del impuesto, IVA crédito PODEBI.'
        ],
        'origen_recurso' => [
            'IP' => 'Ingresos propios',
            'IF' => 'Ingresos federales',
            'IM' => 'Ingresos mixtos'
        ],
        'otro_pago' => [
            1 => 'Reintegro de ISR pagado en exceso',
            2 => 'Subsidio para el empleo',
            3 => 'Viáticos',
            4 => 'Aplicación de saldo a favor por compensación anual',
            5 => 'Reintegro de ISR retenido en exceso de ejercicio anterior',
            6 => 'Alimentos en bienes',
            7 => 'ISR ajustado por subsidio',
            8 => 'Subsidio efectivamente entregado que no correspondía',
            999 => 'Pagos distintos a los listados'
        ],
        'periodicidad' => [
            1 => 'Diario',
            2 => 'Semanal',
            3 => 'Quincenal',
            4 => 'Mensual',
            5 => 'Bimestral'
        ],
        'periodicidad_pago' => [
            1 => 'Diario',
            2 => 'Semanal',
            3 => 'Catorcenal',
            4 => 'Quincenal',
            5 => 'Mensual',
            6 => 'Bimestral',
            7 => 'Unidad obra',
            8 => 'Comisión',
            9 => 'Precio alzado',
            10 => 'Decenal',
            99 => 'Otra Periodicidad'
        ],
        'riesgo_puesto' => [
            '1' => 'Clase I',
            '2' => 'Clase II',
            '3' => 'Clase III',
            '4' => 'Clase IV',
            '5' => 'Clase V'
        ],
        'tipo_comprobante' => [
            'I' => 'Ingreso',
            'E' => 'Egreso',
            'T' => 'Traslado',
            'N' => 'Nómina',
            'P' => 'Pago'
        ],
        'tipo_factor' => [
            1 => 'Tasa',
            2 => 'Cuota',
            3 => 'Exento'
        ],
        'tipo_relacion' => [
            1 => 'Nota de crédito de los documentos relacionados',
            2 => 'Nota de débito de los documentos relacionados',
            3 => 'Devolución de mercancía sobre facturas o traslados previos',
            4 => 'Sustitución de los CFDI previos',
            5 => 'Traslados de mercancías facturados previamente',
            6 => 'Factura generada por los traslados previos',
            7 => 'CFDI por aplicación de anticipo'
        ]
    ];


    public function searchCatalog(string $catalog, string $searchTerm = '', array $options = []): array
    {
        // 1. Validar si es un catálogo fijo o uno definido en $this->catalogs
        if (isset($this->fixedCatalogs[$catalog])) {
            return $this->fixedCatalogs[$catalog];
        }

        if (!isset($this->catalogs[$catalog])) {
            return [];
        }

        $config = $this->catalogs[$catalog];

        // 2. Construye Query Builder base
        $query = DB::table($config['table']);

        // 3. Aplica joins
        if (!empty($config['joins'])) {
            foreach ($config['joins'] as $join) {
                $type = $join['type'] ?? 'join';
                $query->{$type}($join['table'], function($joinObj) use ($join) {
                    $joinObj->on($join['first'], '=', $join['second']);
                    // Soporte para AND en ON, si está definidio
                    if (!empty($join['and'])) {
                        foreach ((array) $join['and'] as $andCondition) {
                            // 'sat_codigo_postal.c_estado = sat_localidad.c_estado'
                            $parts = explode('=', $andCondition);
                            if (count($parts) === 2) {
                                $left = trim($parts[0]);
                                $right = trim($parts[1]);
                                $joinObj->whereRaw("$left = $right");
                            }
                        }
                    }
                });
            }
        }

        // 4. Construir la lista de columnas a seleccionar
        $selectFields = [];

        // - Si hay "columns", añádelas
        if (!empty($config['columns'])) {
            foreach ($config['columns'] as $col) {
                $selectFields[] = DB::raw($col);
            }
        }

        // - Si también tienes "key" y "value" (por ejemplo para select2),
        //   añádelos (si no están ya en columns).
        if (!empty($config['key'])) {
            $selectFields[] = DB::raw($config['key']);
        }

        if (!empty($config['value'])) {
            $selectFields[] = DB::raw($config['value']);
        }

        // - Si al final no hay nada, por fallback selecciona todo (o lanza un error)
        if (empty($selectFields)) {
            $query->select('*');

        } else {
            $query->select($selectFields);
        }

        // 5. Filtrado por status si aplica
        if (($config['use_status'] ?? false) === true) {
            $status = isset($options['status'])
                ? $options['status']
                : (isset($config['status']) ? $config['status']: null);

            if ($status !== null) {
                $query->where('status', $status);
            }
        }

        // 6. Filtrar según extra_conditions (ahora puede incluir columnas de la tabla unida)
        if (isset($config['extra_conditions'])) {
            foreach ($config['extra_conditions'] as $field) {
                if (array_key_exists($field, $options)) {
                    $query->where($field, $options[$field]);
                }
            }
        }

        // 7. Búsqueda
        if (!empty($searchTerm) && !empty($config['search_columns'])) {
            if (($config['search_type'] ?? 'LIKE') === 'MATCH') {
                // Ejemplo: MATCH..AGAINST
                $cols = implode(',', $config['search_columns']);

                $query->whereRaw("MATCH ($cols) AGAINST (? IN BOOLEAN MODE)", [$searchTerm]);

            } else {
                // Búsqueda por LIKE
                $query->where(function ($subQ) use ($config, $searchTerm) {
                    foreach ($config['search_columns'] as $col) {
                        $subQ->orWhere($col, 'LIKE', "%{$searchTerm}%");
                    }
                });
            }
        }

        // 8. Ordenar resultados
        $orderBy  = $options['order_by']  ?? $config['order_by']  ?? $config['key'];
        $orderDir = $options['order_dir'] ?? $config['order_dir'] ?? 'asc';
        $query->orderBy($orderBy, $orderDir);

        // 9. Limitar
        $limit = array_key_exists('limit', $options) ? $options['limit'] : ($config['limit'] ?? 50);

        if ($limit !== null) {
            $query->limit($limit);
        }


        // Para ver la sentencia SQL (con placeholders ?)
        // dump($query->toSql()); dd($query->getBindings());


        // 10. Revisar modo de respuesta
        $rawMode  = ($config['rawMode'] ?? false) || ($options['rawMode'] ?? false);
        $firstRow = ($config['firstRow'] ?? false) || ($options['firstRow'] ?? false);
        $select2Mode = $options['select2Mode'] ?? false;

        // (a) Modo raw -> devolvemos tal cual
        if ($rawMode) {
            if($firstRow){
                return (array) $query->first();
            }

            return $query->get()->toArray();
        }

        $shortKey = Str::afterLast($config['key'], '.');

        // (b) Devuelve en formato "select2" o en un array
        if ($select2Mode) {
            $response = [];

            foreach ($query->get() as $row) {
                $response[] = [
                    'id'   => $row->{$shortKey},
                    'text' => $row->item,
                ];
            }

            return $response;
        }


        // (c) Por defecto, regresa "pluck" id => texto
        return $query->pluck('item', $shortKey)->toArray();
    }

}
