<?php

namespace App\Console\Commands;

use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;
use Throwable;

use function Laravel\Prompts\confirm;
use function Laravel\Prompts\password;
use function Laravel\Prompts\text;

/**
 * Instalação guiada.
 *
 * Faz na ordem certa o que, feito fora de ordem, gera erro difícil de
 * diagnosticar: chave da aplicação antes das migrações (a senha do
 * certificado é criptografada com ela), migrações antes do master, cache
 * de configuração só no fim.
 */
class InstalarCommand extends Command
{
    protected $signature = 'xml:instalar
                            {--somente-master : Cria apenas o usuário master}
                            {--nome= : Nome do master (modo não interativo)}
                            {--email= : E-mail do master (modo não interativo)}
                            {--senha= : Senha do master (modo não interativo)}
                            {--force : Não pede confirmação em nenhuma etapa}';

    protected $description = 'Instalação guiada: chave, banco, usuário master e cache';

    public function handle(): int
    {
        $this->newLine();
        $this->line('  <options=bold>Instalação do XML Monitor</>');
        $this->newLine();

        if ($this->option('somente-master')) {
            return $this->criarMaster() ? self::SUCCESS : self::FAILURE;
        }

        if (! $this->verificarPreRequisitos()) {
            return self::FAILURE;
        }

        if (! $this->gerarChave()) {
            return self::FAILURE;
        }

        if (! $this->migrar()) {
            return self::FAILURE;
        }

        $this->criarMaster();
        $this->otimizar();
        $this->instrucoesFinais();

        return self::SUCCESS;
    }

    /* ------------------------------------------------------------------ Etapas */

    private function verificarPreRequisitos(): bool
    {
        $this->components->task('Verificando extensões do PHP', function () {
            $faltando = array_values(array_filter(
                ['openssl', 'soap', 'curl', 'dom', 'simplexml', 'zlib', 'zip', 'gd', 'mbstring', 'intl', 'fileinfo', 'bcmath'],
                fn ($ext) => ! extension_loaded($ext),
            ));

            if ($faltando !== []) {
                $this->faltando = $faltando;

                return false;
            }

            return true;
        });

        if (isset($this->faltando)) {
            $this->newLine();
            $this->components->error('Faltam extensões: '.implode(', ', $this->faltando));
            $this->line('  Ative em "Select PHP Version → Extensions" no cPanel e rode de novo.');

            return false;
        }

        if (! file_exists(base_path('.env'))) {
            $this->components->error('Arquivo .env não encontrado.');
            $this->line('  Copie o modelo: cp .env.example .env');

            return false;
        }

        return true;
    }

    private array $faltando;

    private function gerarChave(): bool
    {
        if (filled(config('app.key'))) {
            $this->components->twoColumnDetail('Chave da aplicação', '<fg=green>já definida</>');

            return true;
        }

        Artisan::call('key:generate', ['--force' => true]);
        $this->components->twoColumnDetail('Chave da aplicação', '<fg=green>gerada</>');

        $this->components->warn('Guarde a APP_KEY do .env. Perdê-la torna as senhas de certificado ilegíveis.');

        return true;
    }

    private function migrar(): bool
    {
        try {
            DB::connection()->getPdo();
        } catch (Throwable $e) {
            $this->components->error('Não foi possível conectar ao banco: '.$e->getMessage());
            $this->line('  Confira DB_DATABASE, DB_USERNAME e DB_PASSWORD no .env.');

            return false;
        }

        $this->components->twoColumnDetail(
            'Banco de dados',
            '<fg=green>'.DB::connection()->getDriverName().' · '.DB::connection()->getDatabaseName().'</>',
        );

        $this->newLine();
        Artisan::call('migrate', ['--force' => true], $this->output);

        return true;
    }

    private function criarMaster(): bool
    {
        if (! Schema::hasTable('users')) {
            $this->components->error('As tabelas ainda não existem. Rode a instalação completa antes.');

            return false;
        }

        $existentes = User::where('role', User::ROLE_MASTER)->count();

        if ($existentes > 0 && ! $this->option('somente-master')) {
            $this->components->twoColumnDetail('Usuário master', "<fg=green>{$existentes} já cadastrado(s)</>");

            // Sem terminal não há como perguntar: mantém o que já existe.
            if ($this->naoInterativo() && blank($this->option('email'))) {
                return true;
            }

            if (! $this->option('force') && ! $this->naoInterativo()
                && ! confirm('Criar outro usuário master?', default: false)) {
                return true;
            }
        }

        $this->newLine();
        $this->line('  <options=bold>Usuário master</> <fg=gray>— acesso total à plataforma</>');
        $this->newLine();

        // Modo não interativo: exigido por terminais sem TTY e por scripts de
        // implantação. Sem isso o comando ficaria travado esperando digitação.
        if ($this->naoInterativo()) {
            return $this->criarMasterPorParametros();
        }

        $nome = text('Nome', required: true, default: 'Administrador Master');

        $email = text('E-mail de acesso', required: true, validate: fn (string $valor) => $this->validarEmail($valor));

        $senha = password('Senha', required: true, validate: fn (string $valor) => $this->validarSenha($valor));

        User::create([
            'name' => $nome,
            'email' => $email,
            'password' => $senha,
            'role' => User::ROLE_MASTER,
            'company_id' => null,
        ]);

        $this->newLine();
        $this->components->info("Master criado: {$email}");

        return true;
    }

    /**
     * Verdadeiro quando não há como perguntar nada: `--no-interaction`,
     * parâmetros já informados, ou entrada sem terminal (pipe, cron, CI).
     */
    private function naoInterativo(): bool
    {
        return $this->option('no-interaction')
            || filled($this->option('email'))
            || ! $this->input->isInteractive();
    }

    private function criarMasterPorParametros(): bool
    {
        $dados = [
            'name' => $this->option('nome') ?: 'Administrador Master',
            'email' => (string) $this->option('email'),
            'password' => (string) $this->option('senha'),
        ];

        if (blank($dados['email']) || blank($dados['password'])) {
            $this->components->error('Terminal não interativo: informe --email e --senha.');
            $this->line('  Exemplo:');
            $this->line('      <options=bold>php artisan xml:instalar --somente-master --email=voce@dominio.com.br --senha="SuaSenha123"</>');

            return false;
        }

        if ($erro = $this->validarEmail($dados['email'])) {
            $this->components->error($erro);

            return false;
        }

        if ($erro = $this->validarSenha($dados['password'])) {
            $this->components->error($erro);

            return false;
        }

        User::create([...$dados, 'role' => User::ROLE_MASTER, 'company_id' => null]);

        $this->components->info("Master criado: {$dados['email']}");

        return true;
    }

    private function validarEmail(string $valor): ?string
    {
        if (! filter_var($valor, FILTER_VALIDATE_EMAIL)) {
            return 'Informe um e-mail válido.';
        }

        return User::withTrashed()->where('email', $valor)->exists()
            ? 'Já existe um usuário com este e-mail.'
            : null;
    }

    private function validarSenha(string $valor): ?string
    {
        $validador = Validator::make(
            ['password' => $valor],
            ['password' => [Password::min(8)->letters()->numbers()]],
        );

        return $validador->fails() ? $validador->errors()->first('password') : null;
    }

    private function otimizar(): void
    {
        if (! app()->environment('production')) {
            $this->components->twoColumnDetail('Cache de configuração', '<fg=gray>ignorado fora de produção</>');

            return;
        }

        foreach (['config:cache' => 'Configuração', 'route:cache' => 'Rotas', 'view:cache' => 'Views'] as $comando => $rotulo) {
            Artisan::call($comando);
            $this->components->twoColumnDetail("Cache · {$rotulo}", '<fg=green>gerado</>');
        }
    }

    private function instrucoesFinais(): void
    {
        $php = PHP_BINARY;
        $artisan = base_path('artisan');

        $this->newLine();
        $this->line('  <options=bold;fg=cyan>Falta apenas o cron</>');
        $this->newLine();
        $this->line('  No cPanel, em "Cron Jobs", adicione UMA entrada a cada minuto:');
        $this->newLine();
        $this->line("      <options=bold>* * * * * {$php} {$artisan} schedule:run >/dev/null 2>&1</>");
        $this->newLine();
        $this->line('  <fg=gray>Ela cuida do monitor de XML, da fila e da limpeza. Sem ela nada é baixado.</>');
        $this->newLine();
        $this->line('  Depois, confira tudo com:');
        $this->line('      <options=bold>php artisan xml:diagnostico</>');
        $this->newLine();
    }
}
