<?php

namespace App\Http\Controllers;

use App\Models\ConfiguracionNegocio;
use App\Models\Role;
use App\Models\User;
use App\Support\EnvFileWriter;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\View\View;
use PDO;
use PDOException;
use Throwable;

/**
 * Asistente de instalacion web (/instalar). Pensado para "una instalacion
 * por cliente": cada cliente tiene su propio servidor con su propia base de
 * datos vacia. Este controlador reemplaza el proceso manual de editar .env,
 * correr migrate y crear el primer admin por tinker.
 *
 * Cada accion vuelve a chequear el estado real (¿conecta la BD? ¿existen
 * las tablas? ¿hay un admin?) para poder retomar el asistente si el
 * navegador se cerro a la mitad, en vez de depender solo de la sesion.
 */
class InstalacionController extends Controller
{
    private const EXTENSIONES_REQUERIDAS = [
        'pdo_mysql', 'mbstring', 'openssl', 'gd', 'fileinfo', 'ctype', 'tokenizer', 'xml',
    ];

    public function requisitos(Request $request): View
    {
        if (! $this->autorizado($request)) {
            return $this->pantallaToken();
        }

        $this->autoconfigurarSiHaceFalta();

        $checks = $this->chequearRequisitos();

        return view('instalador.requisitos', [
            'checks' => $checks,
            'todoBien' => collect($checks)->every(fn ($c) => $c['ok']),
            'paso' => 1,
        ]);
    }

    public function baseDeDatos(Request $request): View|RedirectResponse
    {
        if (! $this->autorizado($request)) {
            return $this->pantallaToken();
        }

        if ($this->baseDeDatosConecta() && Schema::hasTable('roles')) {
            return redirect()->route('instalador.administrador');
        }

        return view('instalador.base_datos', ['paso' => 2]);
    }

    public function procesarBaseDeDatos(Request $request): View|RedirectResponse
    {
        if (! $this->autorizado($request)) {
            return $this->pantallaToken();
        }

        $data = $request->validate([
            'db_host' => ['required', 'string', 'max:255'],
            'db_port' => ['required', 'integer', 'between:1,65535'],
            'db_database' => ['required', 'string', 'max:64'],
            'db_username' => ['required', 'string', 'max:64'],
            'db_password' => ['nullable', 'string', 'max:255'],
        ]);

        try {
            $conexion = new PDO(
                "mysql:host={$data['db_host']};port={$data['db_port']};dbname={$data['db_database']};charset=utf8mb4",
                $data['db_username'],
                $data['db_password'] ?? '',
                [PDO::ATTR_TIMEOUT => 5, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
            );
            $conexion = null;
        } catch (PDOException $e) {
            report($e);

            return back()->withInput($request->except('db_password'))
                ->withErrors(['db_host' => 'No se pudo conectar con esos datos. Revisá el servidor, el nombre de la base de datos y las credenciales.']);
        }

        (new EnvFileWriter)->set([
            'DB_HOST' => $data['db_host'],
            'DB_PORT' => (string) $data['db_port'],
            'DB_DATABASE' => $data['db_database'],
            'DB_USERNAME' => $data['db_username'],
            'DB_PASSWORD' => $data['db_password'] ?? '',
            'APP_URL' => $request->getSchemeAndHttpHost(),
        ]);

        return redirect()->route('instalador.migrando');
    }

    public function migrando(Request $request): View|RedirectResponse
    {
        if (! $this->autorizado($request)) {
            return $this->pantallaToken();
        }

        if (! $this->baseDeDatosConecta()) {
            return redirect()->route('instalador.baseDeDatos');
        }

        if (Schema::hasTable('roles')) {
            return redirect()->route('instalador.administrador');
        }

        return view('instalador.migrando_confirmar', ['paso' => 3]);
    }

    public function procesarMigracion(Request $request): View|RedirectResponse
    {
        if (! $this->autorizado($request)) {
            return $this->pantallaToken();
        }

        if (! $this->baseDeDatosConecta()) {
            return redirect()->route('instalador.baseDeDatos')
                ->withErrors(['db_host' => 'La conexión a la base de datos se perdió. Volvé a ingresar los datos.']);
        }

        try {
            Artisan::call('migrate', ['--force' => true]);
        } catch (Throwable $e) {
            report($e);

            return back()->withErrors(['migracion' => 'Ocurrió un error al migrar la base de datos. Ya quedó registrado para revisión.']);
        }

        return redirect()->route('instalador.administrador');
    }

    public function administrador(Request $request): View|RedirectResponse
    {
        if (! $this->autorizado($request)) {
            return $this->pantallaToken();
        }

        if (! Schema::hasTable('roles')) {
            return redirect()->route('instalador.migrando');
        }

        if (User::query()->exists()) {
            return redirect()->route('instalador.finalizar');
        }

        return view('instalador.administrador', ['paso' => 4]);
    }

    public function procesarAdministrador(Request $request): View|RedirectResponse
    {
        if (! $this->autorizado($request)) {
            return $this->pantallaToken();
        }

        $data = $request->validate([
            'cedula' => ['required', 'string', 'max:20', 'unique:users,cedula'],
            'name' => ['required', 'string', 'max:255'],
            'apellido' => ['nullable', 'string', 'max:255'],
            'telefono' => ['nullable', 'string', 'max:30'],
            'email' => ['required', 'email', 'max:255', 'unique:users,email'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
            'negocio_nombre' => ['nullable', 'string', 'max:255'],
        ]);

        $rolAdmin = Role::where('nombre', 'Administrador')->first();

        abort_unless($rolAdmin, 500, 'No se encontró el rol Administrador. Revisá que las migraciones hayan corrido correctamente.');

        User::create([
            'cedula' => $data['cedula'],
            'name' => $data['name'],
            'apellido' => $data['apellido'] ?? null,
            'telefono' => $data['telefono'] ?? null,
            'email' => $data['email'],
            'password' => $data['password'],
            'role_id' => $rolAdmin->id,
            'active' => true,
        ]);

        if (! empty($data['negocio_nombre'])) {
            ConfiguracionNegocio::actual()->update(['nombre' => $data['negocio_nombre']]);
        }

        return redirect()->route('instalador.finalizar');
    }

    public function finalizar(Request $request): View|RedirectResponse
    {
        if (! $this->autorizado($request)) {
            return $this->pantallaToken();
        }

        if (! User::query()->exists()) {
            return redirect()->route('instalador.administrador');
        }

        if (! file_exists(public_path('storage'))) {
            Artisan::call('storage:link');
        }

        file_put_contents(storage_path('installed.lock'), 'Instalado el '.now()->toDateTimeString()."\n");

        if (file_exists(storage_path('install.token'))) {
            unlink(storage_path('install.token'));
        }

        $request->session()->forget('instalador_autorizado');

        return view('instalador.finalizado', ['paso' => 5]);
    }

    private function autorizado(Request $request): bool
    {
        if (session('instalador_autorizado') === true) {
            return true;
        }

        $rutaToken = storage_path('install.token');

        if (! file_exists($rutaToken)) {
            return false;
        }

        $tokenGuardado = file_get_contents($rutaToken);
        $tokenEnviado = (string) $request->input('token');

        if ($tokenEnviado !== '' && hash_equals($tokenGuardado, $tokenEnviado)) {
            session(['instalador_autorizado' => true]);

            return true;
        }

        return false;
    }

    private function pantallaToken(): View
    {
        return view('instalador.token', ['tokenExiste' => file_exists(storage_path('install.token'))]);
    }

    private function autoconfigurarSiHaceFalta(): void
    {
        // APP_KEY ya se resuelve en el comando "instalador:token" (por SSH,
        // antes de la primera request web) — no se puede generar acá porque
        // la sesión ya la necesita para arrancar.
        if (! file_exists(public_path('storage'))) {
            Artisan::call('storage:link');
        }
    }

    /**
     * @return array<string, array{ok: bool, detalle: string}>
     */
    private function chequearRequisitos(): array
    {
        $checks = [
            'PHP 8.3 o superior' => [
                'ok' => version_compare(PHP_VERSION, '8.3.0', '>='),
                'detalle' => 'Versión detectada: '.PHP_VERSION,
            ],
            'Clave de aplicación (APP_KEY)' => [
                'ok' => ! empty(config('app.key')),
                'detalle' => empty(config('app.key')) ? 'Corré "php artisan instalador:token" por SSH primero' : 'Generada correctamente',
            ],
        ];

        foreach (self::EXTENSIONES_REQUERIDAS as $extension) {
            $checks["Extensión PHP: {$extension}"] = [
                'ok' => extension_loaded($extension),
                'detalle' => $extension,
            ];
        }

        $checks['Carpeta storage/ escribible'] = [
            'ok' => is_writable(storage_path()),
            'detalle' => storage_path(),
        ];

        $checks['Carpeta bootstrap/cache/ escribible'] = [
            'ok' => is_writable(base_path('bootstrap/cache')),
            'detalle' => base_path('bootstrap/cache'),
        ];

        return $checks;
    }

    private function baseDeDatosConecta(): bool
    {
        try {
            DB::connection()->getPdo();

            return true;
        } catch (Throwable) {
            return false;
        }
    }
}
