<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;

class LegacyGenerateModelsCommand extends Command
{
    protected $signature = 'legacy:generate-models {--force : Sobrescribe modelos existentes}';

    protected $description = 'Genera modelos Eloquent base para todas las tablas existentes en veltrix_pos.';

    private const TABLE_CLASS_MAP = [
        'ai_help_messages' => 'AiHelpMessage',
        'ai_help_sessions' => 'AiHelpSession',
        'ajustes_inventario' => 'AjusteInventario',
        'aperturas_caja' => 'AperturaCaja',
        'avisos_cobro_suscripcion' => 'AvisoCobroSuscripcion',
        'cajas' => 'Caja',
        'categorias' => 'Categoria',
        'cierres_caja' => 'CierreCaja',
        'ciudades' => 'Ciudad',
        'clientes' => 'Cliente',
        'compras' => 'Compra',
        'compra_detalles' => 'CompraDetalle',
        'configuracion_empresa' => 'ConfiguracionEmpresa',
        'configuracion_global' => 'ConfiguracionGlobal',
        'devoluciones' => 'Devolucion',
        'empresas' => 'Empresa',
        'facturas' => 'Factura',
        'factura_detalles' => 'FacturaDetalle',
        'impresoras' => 'Impresora',
        'impuestos' => 'Impuesto',
        'inventario' => 'Inventario',
        'logs_actividad' => 'LogActividad',
        'marcas' => 'Marca',
        'metodos_pago' => 'MetodoPago',
        'monedas' => 'Moneda',
        'movimientos_caja' => 'MovimientoCaja',
        'movimientos_inventario' => 'MovimientoInventario',
        'notificacion_vistas' => 'NotificacionVista',
        'numeraciones_documentos' => 'NumeracionDocumento',
        'ordenes_compra' => 'OrdenCompra',
        'orden_compra_detalles' => 'OrdenCompraDetalle',
        'pagos_compra' => 'PagoCompra',
        'pagos_suscripcion' => 'PagoSuscripcion',
        'paises' => 'Pais',
        'permisos' => 'Permiso',
        'planes' => 'Plan',
        'productos' => 'Producto',
        'producto_variantes' => 'ProductoVariante',
        'proveedores' => 'Proveedor',
        'recepciones_compra' => 'RecepcionCompra',
        'recepcion_compra_detalles' => 'RecepcionCompraDetalle',
        'resoluciones_dian' => 'ResolucionDian',
        'roles' => 'Rol',
        'rol_permiso' => 'RolPermiso',
        'saas_checkout_intents' => 'SaasCheckoutIntent',
        'soporte_tickets' => 'SoporteTicket',
        'soporte_ticket_eventos' => 'SoporteTicketEvento',
        'sucursales' => 'Sucursal',
        'suscripciones' => 'Suscripcion',
        'traspasos_caja' => 'TraspasoCaja',
        'unidades_medida' => 'UnidadMedida',
        'usuarios' => 'User',
        'usuario_empresa' => 'UsuarioEmpresa',
        'ventas' => 'Venta',
        'venta_detalles' => 'VentaDetalle',
        'venta_pagos' => 'VentaPago',
        'whatsapp_meta_templates' => 'WhatsappMetaTemplate',
    ];

    /**
     * @var array<string, string>
     */
    private array $classToTable = [];

    public function handle(): int
    {
        $database = (string) config('database.connections.mysql.database');

        if ($database === '') {
            $this->error('No hay base de datos configurada en la conexion mysql.');

            return self::FAILURE;
        }

        $this->classToTable = array_flip(self::TABLE_CLASS_MAP);

        $tables = DB::select(
            'SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? ORDER BY TABLE_NAME',
            [$database]
        );

        if ($tables === []) {
            $this->warn('No se detectaron tablas para generar modelos.');

            return self::SUCCESS;
        }

        $force = (bool) $this->option('force');
        $created = 0;
        $skipped = 0;

        foreach ($tables as $tableRow) {
            $table = (string) $tableRow->TABLE_NAME;
            $class = $this->resolveClassName($table);

            if ($class === 'User') {
                $this->line(" - {$table}: omitida (modelo User gestionado manualmente).");
                $skipped++;

                continue;
            }

            $path = app_path("Models/{$class}.php");

            if (File::exists($path) && ! $force) {
                $this->line(" - {$class}: ya existe, omitido.");
                $skipped++;

                continue;
            }

            $columns = DB::select(
                'SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, COLUMN_KEY, IS_NULLABLE, COLUMN_DEFAULT, EXTRA
                 FROM information_schema.COLUMNS
                 WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
                 ORDER BY ORDINAL_POSITION',
                [$database, $table]
            );

            $foreignKeys = DB::select(
                'SELECT COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
                 FROM information_schema.KEY_COLUMN_USAGE
                 WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND REFERENCED_TABLE_NAME IS NOT NULL
                 ORDER BY ORDINAL_POSITION',
                [$database, $table]
            );

            File::put($path, $this->buildModelSource($class, $table, $columns, $foreignKeys));
            $created++;
        }

        $this->newLine();
        $this->info("Modelos generados: {$created}");
        $this->info("Modelos omitidos: {$skipped}");

        return self::SUCCESS;
    }

    private function resolveClassName(string $table): string
    {
        if (isset(self::TABLE_CLASS_MAP[$table])) {
            return self::TABLE_CLASS_MAP[$table];
        }

        return Str::studly(Str::singular($table));
    }

    /**
     * @param  array<int, object>  $columns
     * @param  array<int, object>  $foreignKeys
     */
    private function buildModelSource(string $class, string $table, array $columns, array $foreignKeys): string
    {
        $fillable = [];
        $casts = [];
        $hasCreatedAt = false;
        $hasUpdatedAt = false;

        foreach ($columns as $column) {
            $name = (string) $column->COLUMN_NAME;
            $dataType = strtolower((string) $column->DATA_TYPE);
            $columnType = strtolower((string) $column->COLUMN_TYPE);

            if ($name === 'created_at') {
                $hasCreatedAt = true;
            }

            if ($name === 'updated_at') {
                $hasUpdatedAt = true;
            }

            if (! in_array($name, ['id', 'created_at', 'updated_at'], true)) {
                $fillable[] = $name;
            }

            $cast = $this->castForColumn($dataType, $columnType);

            if ($cast) {
                $casts[$name] = $cast;
            }
        }

        $fillableExport = $this->exportList($fillable, 8);
        $castsExport = $this->exportAssoc($casts, 12);
        $timestampsLine = ($hasCreatedAt && $hasUpdatedAt) ? '' : "\n    public \$timestamps = false;\n";

        $relationshipMethods = [];
        foreach ($foreignKeys as $foreignKey) {
            $column = (string) $foreignKey->COLUMN_NAME;
            $referencedTable = (string) $foreignKey->REFERENCED_TABLE_NAME;
            $relatedClass = $this->resolveClassName($referencedTable);

            $methodName = Str::camel(Str::replaceLast('_id', '', $column));
            if ($methodName === '') {
                $methodName = Str::camel($relatedClass);
            }

            $relationshipMethods[] = <<<PHP
    public function {$methodName}(): BelongsTo
    {
        return \$this->belongsTo({$relatedClass}::class, '{$column}');
    }

PHP;
        }

        $relationships = implode('', array_unique($relationshipMethods));
        $belongsToImport = $relationships !== '' ? "use Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\n" : '';

        return <<<PHP
<?php

namespace App\Models;

{$belongsToImport}use Illuminate\Database\Eloquent\Model;

class {$class} extends Model
{
    protected \$table = '{$table}';
{$timestampsLine}
    protected \$fillable = {$fillableExport};

    protected function casts(): array
    {
        return {$castsExport};
    }

{$relationships}}

PHP;
    }

    private function castForColumn(string $dataType, string $columnType): ?string
    {
        return match (true) {
            $dataType === 'tinyint' && $columnType === 'tinyint(1)' => 'boolean',
            $dataType === 'date' => 'date',
            in_array($dataType, ['datetime', 'timestamp'], true) => 'datetime',
            in_array($dataType, ['decimal', 'float', 'double'], true) => $this->decimalCast($columnType),
            $dataType === 'json' => 'array',
            default => null,
        };
    }

    private function decimalCast(string $columnType): string
    {
        if (preg_match('/\((\d+),\s*(\d+)\)/', $columnType, $matches) === 1) {
            return 'decimal:'.$matches[2];
        }

        return 'float';
    }

    /**
     * @param  array<int, string>  $values
     */
    private function exportList(array $values, int $indentSpaces): string
    {
        if ($values === []) {
            return '[]';
        }

        $indent = str_repeat(' ', $indentSpaces);
        $lines = array_map(static fn (string $value): string => "{$indent}'{$value}',", $values);

        return "[\n".implode("\n", $lines)."\n".str_repeat(' ', $indentSpaces - 4).']';
    }

    /**
     * @param  array<string, string>  $values
     */
    private function exportAssoc(array $values, int $indentSpaces): string
    {
        if ($values === []) {
            return '[]';
        }

        $indent = str_repeat(' ', $indentSpaces);
        $lines = [];

        foreach ($values as $key => $value) {
            $lines[] = "{$indent}'{$key}' => '{$value}',";
        }

        return "[\n".implode("\n", $lines)."\n".str_repeat(' ', $indentSpaces - 4).']';
    }
}
