<?php

namespace App\Console\Commands;

use App\Models\AuditLog;
use App\Models\EmployeeSnapshot;
use App\Models\RgpdRequest;
use App\Models\Submission;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;

class EncryptExistingData extends Command
{
    protected $signature = 'app:encrypt-existing-data
        {--table= : Tabela especifica (submissions, employees_snapshot, rgpd_requests, audit_logs)}
        {--dry-run : Simular sem alterar dados}
        {--chunk=100 : Tamanho do chunk}';

    protected $description = 'Encripta dados PII existentes em plaintext na base de dados';

    private int $encrypted = 0;
    private int $skipped = 0;
    private int $failed = 0;

    public function handle(): int
    {
        $dryRun = $this->option('dry-run');
        $table = $this->option('table');
        $chunk = (int) $this->option('chunk');

        if ($dryRun) {
            $this->warn('MODO DRY-RUN — nenhum dado sera alterado.');
        }

        $this->info('A iniciar encriptacao de dados existentes...');
        $this->newLine();

        $tables = $table
            ? [$table]
            : ['submissions', 'employees_snapshot', 'rgpd_requests', 'audit_logs'];

        foreach ($tables as $t) {
            match ($t) {
                'submissions' => $this->encryptSubmissions($chunk, $dryRun),
                'employees_snapshot' => $this->encryptSnapshots($chunk, $dryRun),
                'rgpd_requests' => $this->encryptRgpdRequests($chunk, $dryRun),
                'audit_logs' => $this->encryptAuditLogs($chunk, $dryRun),
                default => $this->error("Tabela desconhecida: {$t}"),
            };
        }

        $this->newLine();
        $this->info("Concluido: {$this->encrypted} encriptados, {$this->skipped} ja encriptados, {$this->failed} falhados.");

        return self::SUCCESS;
    }

    private function encryptSubmissions(int $chunk, bool $dryRun): void
    {
        $this->info('== submissions ==');
        $fields = [
            'nome_completo', 'nif', 'email', 'telemovel',
            'morada_original', 'morada_corrigida',
            'contacto_emergencia_nome', 'contacto_emergencia_relacao',
            'contacto_emergencia_telefone', 'ip_address',
        ];

        DB::table('submissions')->orderBy('id')->chunk($chunk, function ($rows) use ($fields, $dryRun) {
            foreach ($rows as $row) {
                if ($this->isAlreadyEncrypted($row->nome_completo)) {
                    $this->skipped++;
                    continue;
                }

                $updates = [];
                foreach ($fields as $field) {
                    $value = $row->$field;
                    if ($value !== null) {
                        $updates[$field] = Crypt::encryptString($value);
                    }
                }

                // Gerar nif_hash
                if ($row->nif !== null) {
                    $updates['nif_hash'] = hash('sha256', $row->nif);
                }

                if (!$dryRun && !empty($updates)) {
                    DB::table('submissions')->where('id', $row->id)->update($updates);
                }

                $this->encrypted++;
            }
        });

        $this->line("  Processados: {$this->encrypted} encriptados, {$this->skipped} skipped");
    }

    private function encryptSnapshots(int $chunk, bool $dryRun): void
    {
        $this->info('== employees_snapshot ==');
        $countBefore = $this->encrypted;
        $skipBefore = $this->skipped;
        $fields = [
            'nome_completo', 'nif', 'email', 'telemovel', 'morada',
            'contacto_emergencia_nome', 'contacto_emergencia_relacao',
            'contacto_emergencia_telefone',
        ];

        DB::table('employees_snapshot')->orderBy('id')->chunk($chunk, function ($rows) use ($fields, $dryRun) {
            foreach ($rows as $row) {
                if ($this->isAlreadyEncrypted($row->nome_completo)) {
                    $this->skipped++;
                    continue;
                }

                $updates = [];
                foreach ($fields as $field) {
                    $value = $row->$field;
                    if ($value !== null) {
                        $updates[$field] = Crypt::encryptString($value);
                    }
                }

                if ($row->nif !== null) {
                    $updates['nif_hash'] = hash('sha256', $row->nif);
                }

                if (!$dryRun && !empty($updates)) {
                    DB::table('employees_snapshot')->where('id', $row->id)->update($updates);
                }

                $this->encrypted++;
            }
        });

        $this->line("  Processados: " . ($this->encrypted - $countBefore) . " encriptados, " . ($this->skipped - $skipBefore) . " skipped");
    }

    private function encryptRgpdRequests(int $chunk, bool $dryRun): void
    {
        $this->info('== rgpd_requests ==');
        $countBefore = $this->encrypted;
        $skipBefore = $this->skipped;
        $fields = ['nome_completo', 'email', 'nif'];

        DB::table('rgpd_requests')->orderBy('id')->chunk($chunk, function ($rows) use ($fields, $dryRun) {
            foreach ($rows as $row) {
                if ($this->isAlreadyEncrypted($row->nome_completo)) {
                    $this->skipped++;
                    continue;
                }

                $updates = [];
                foreach ($fields as $field) {
                    $value = $row->$field;
                    if ($value !== null) {
                        $updates[$field] = Crypt::encryptString($value);
                    }
                }

                if (!$dryRun && !empty($updates)) {
                    DB::table('rgpd_requests')->where('id', $row->id)->update($updates);
                }

                $this->encrypted++;
            }
        });

        $this->line("  Processados: " . ($this->encrypted - $countBefore) . " encriptados, " . ($this->skipped - $skipBefore) . " skipped");
    }

    private function encryptAuditLogs(int $chunk, bool $dryRun): void
    {
        $this->info('== audit_logs ==');
        $countBefore = $this->encrypted;
        $skipBefore = $this->skipped;

        DB::table('audit_logs')->orderBy('id')->chunk($chunk, function ($rows) use ($dryRun) {
            foreach ($rows as $row) {
                // Verificar se ja esta encriptado (old_values nao e JSON valido)
                if ($row->old_values !== null && $this->isAlreadyEncrypted($row->old_values)) {
                    $this->skipped++;
                    continue;
                }

                $updates = [];

                if ($row->old_values !== null) {
                    $updates['old_values'] = Crypt::encryptString($row->old_values);
                }
                if ($row->new_values !== null) {
                    $updates['new_values'] = Crypt::encryptString($row->new_values);
                }
                if ($row->ip_address !== null) {
                    $updates['ip_address'] = Crypt::encryptString($row->ip_address);
                }

                if (!$dryRun && !empty($updates)) {
                    DB::table('audit_logs')->where('id', $row->id)->update($updates);
                }

                $this->encrypted++;
            }
        });

        $this->line("  Processados: " . ($this->encrypted - $countBefore) . " encriptados, " . ($this->skipped - $skipBefore) . " skipped");
    }

    /**
     * Verificar se um valor ja esta encriptado pelo Laravel.
     * Valores encriptados pelo Laravel sao strings base64 longas que, ao decodificar,
     * contem JSON com as chaves 'iv', 'value', 'mac'.
     */
    private function isAlreadyEncrypted(?string $value): bool
    {
        if ($value === null || strlen($value) < 50) {
            return false;
        }

        $decoded = base64_decode($value, true);
        if ($decoded === false) {
            return false;
        }

        $json = json_decode($decoded, true);
        return is_array($json) && isset($json['iv'], $json['value'], $json['mac']);
    }
}
