<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

/**
 * Seeder responsável por identificar e remover automaticamente
 * todos os registros órfãos em todo o banco de dados.
 *
 * Este seeder:
 * - Lê dinamicamente o esquema do banco de dados MySQL
 * - Identifica todas as foreign keys existentes
 * - Gera e executa queries SQL para remover registros órfãos
 * - Registra no log cada limpeza realizada
 *
 * @package Database\Seeders
 */
class CleanOrphansSeeder extends Seeder
{
    /**
     * Nome do banco de dados atual
     *
     * @var string
     */
    protected string $databaseName;

    /**
     * Lista de foreign keys encontradas
     *
     * @var array
     */
    protected array $foreignKeys = [];

    /**
     * Total de registros removidos
     *
     * @var int
     */
    protected int $totalRemoved = 0;

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run(): void
    {
        $this->info('Iniciando limpeza de registros órfãos...');

        try {
            // Obtém o nome do banco de dados atual
            $this->databaseName = DB::connection()->getDatabaseName();

            // Busca todas as foreign keys
            $this->fetchForeignKeys();

            if (empty($this->foreignKeys)) {
                $this->info('Nenhuma foreign key encontrada no banco de dados.');
                return;
            }

            $this->info("Encontradas " . count($this->foreignKeys) . " foreign key(s) para verificar.");

            // Ordena as foreign keys por dependência (tabelas filhas primeiro)
            $this->sortForeignKeysByDependency();

            // Executa a limpeza para cada foreign key
            foreach ($this->foreignKeys as $fk) {
                $this->cleanOrphanRecords($fk);
            }

            $this->info("Limpeza concluída! Total de registros removidos: {$this->totalRemoved}");

        } catch (\Exception $e) {
            $this->error("Erro durante a limpeza de registros órfãos: " . $e->getMessage());
            Log::error('CleanOrphansSeeder: Erro ao executar limpeza', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            throw $e;
        }
    }

    /**
     * Busca todas as foreign keys do banco de dados
     *
     * @return void
     */
    protected function fetchForeignKeys(): void
    {
        $query = "
            SELECT
                kcu.TABLE_SCHEMA,
                kcu.TABLE_NAME AS child_table,
                kcu.COLUMN_NAME AS child_column,
                kcu.CONSTRAINT_NAME,
                kcu.REFERENCED_TABLE_NAME AS parent_table,
                kcu.REFERENCED_COLUMN_NAME AS parent_column
            FROM
                INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
            WHERE
                kcu.TABLE_SCHEMA = ?
                AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
                AND kcu.REFERENCED_COLUMN_NAME IS NOT NULL
            ORDER BY
                kcu.TABLE_NAME, kcu.CONSTRAINT_NAME
        ";

        $this->foreignKeys = DB::select($query, [$this->databaseName]);
    }

    /**
     * Ordena as foreign keys por dependência
     * Tabelas que são referenciadas por outras devem ser processadas por último
     *
     * @return void
     */
    protected function sortForeignKeysByDependency(): void
    {
        // Cria um mapa de dependências
        $dependencyMap = [];
        $tableOrder = [];

        foreach ($this->foreignKeys as $fk) {
            $childTable = $fk->child_table;
            $parentTable = $fk->parent_table;

            if (!isset($dependencyMap[$childTable])) {
                $dependencyMap[$childTable] = [];
                $tableOrder[$childTable] = 0;
            }

            $dependencyMap[$childTable][] = $parentTable;
        }

        // Ordena usando topological sort simples
        // Tabelas que não são referenciadas por outras vêm primeiro
        usort($this->foreignKeys, function ($a, $b) use ($dependencyMap) {
            $aIsReferenced = $this->isTableReferenced($a->child_table);
            $bIsReferenced = $this->isTableReferenced($b->child_table);

            // Se uma tabela é referenciada e a outra não, a não referenciada vem primeiro
            if ($aIsReferenced && !$bIsReferenced) {
                return 1;
            }
            if (!$aIsReferenced && $bIsReferenced) {
                return -1;
            }

            // Caso contrário, mantém a ordem original
            return 0;
        });
    }

    /**
     * Verifica se uma tabela é referenciada por alguma foreign key
     *
     * @param string $tableName
     * @return bool
     */
    protected function isTableReferenced(string $tableName): bool
    {
        foreach ($this->foreignKeys as $fk) {
            if ($fk->parent_table === $tableName) {
                return true;
            }
        }
        return false;
    }

    /**
     * Limpa registros órfãos para uma foreign key específica
     *
     * @param object $foreignKey
     * @return void
     */
    protected function cleanOrphanRecords(object $foreignKey): void
    {
        $childTable = $foreignKey->child_table;
        $childColumn = $foreignKey->child_column;
        $parentTable = $foreignKey->parent_table;
        $parentColumn = $foreignKey->parent_column;

        try {
            // Gera a query SQL para deletar registros órfãos
            $sql = "
                DELETE child
                FROM `{$childTable}` AS child
                LEFT JOIN `{$parentTable}` AS parent
                    ON child.`{$childColumn}` = parent.`{$parentColumn}`
                WHERE parent.`{$parentColumn}` IS NULL
            ";

            // Executa a query e obtém o número de linhas afetadas
            // Usando affectingStatement para queries DELETE com JOIN
            $affectedRows = DB::affectingStatement($sql);

            if ($affectedRows > 0) {
                $this->totalRemoved += $affectedRows;

                $message = sprintf(
                    "Removidos %d registro(s) órfão(s) da tabela '%s' (FK: %s -> %s.%s)",
                    $affectedRows,
                    $childTable,
                    $childColumn,
                    $parentTable,
                    $parentColumn
                );

                $this->info($message);

                // Registra no log do Laravel
                Log::info('CleanOrphansSeeder: Registros órfãos removidos', [
                    'child_table' => $childTable,
                    'child_column' => $childColumn,
                    'parent_table' => $parentTable,
                    'parent_column' => $parentColumn,
                    'removed_count' => $affectedRows
                ]);
            } else {
                $this->line("Nenhum registro órfão encontrado em '{$childTable}' (FK: {$childColumn} -> {$parentTable}.{$parentColumn})");
            }

        } catch (\Exception $e) {
            $errorMessage = sprintf(
                "Erro ao limpar registros órfãos da tabela '%s' (FK: %s -> %s.%s): %s",
                $childTable,
                $childColumn,
                $parentTable,
                $parentColumn,
                $e->getMessage()
            );

            $this->error($errorMessage);

            Log::error('CleanOrphansSeeder: Erro ao limpar registros órfãos', [
                'child_table' => $childTable,
                'child_column' => $childColumn,
                'parent_table' => $parentTable,
                'parent_column' => $parentColumn,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);

            // Continua processando outras foreign keys mesmo se uma falhar
        }
    }

    /**
     * Exibe mensagem informativa no console
     *
     * @param string $message
     * @return void
     */
    protected function info(string $message): void
    {
        if (isset($this->command)) {
            $this->command->info($message);
        } else {
            echo $message . PHP_EOL;
        }
    }

    /**
     * Exibe mensagem de erro no console
     *
     * @param string $message
     * @return void
     */
    protected function error(string $message): void
    {
        if (isset($this->command)) {
            $this->command->error($message);
        } else {
            echo "ERRO: " . $message . PHP_EOL;
        }
    }

    /**
     * Exibe mensagem de linha no console
     *
     * @param string $message
     * @return void
     */
    protected function line(string $message): void
    {
        if (isset($this->command)) {
            $this->command->line($message);
        } else {
            echo $message . PHP_EOL;
        }
    }
}
