<?php

namespace App\Console\Commands;

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

class RegenerateAuditTriggers extends Command
{
    protected $signature = 'audit:triggers
        {--table= : 특정 테이블만 재생성}
        {--drop-only : 트리거 삭제만 (재생성 안 함)}
        {--dry-run : 변경 없이 대상 목록만 출력}';

    protected $description = '트리거 감사 로그용 MySQL 트리거 재생성 (스키마 변경 후 사용)';

    /** @var string[] 트리거 제외 테이블 */
    private array $excludeTables = [
        'audit_logs',
        'trigger_audit_logs',
        'sessions',
        'cache',
        'cache_locks',
        'jobs',
        'job_batches',
        'failed_jobs',
        'migrations',
        'personal_access_tokens',
        'api_request_logs',
    ];

    public function handle(): int
    {
        $specificTable = $this->option('table');
        $dropOnly = $this->option('drop-only');
        $dryRun = $this->option('dry-run');
        $dbName = config('database.connections.mysql.database');

        $this->info('=== 트리거 감사 로그 트리거 '.($dropOnly ? '삭제' : '재생성').' ===');
        $this->newLine();

        // 대상 테이블 목록
        $tables = $this->getTargetTables($dbName, $specificTable);
        $this->info('대상 테이블: '.count($tables).'개');

        if ($dryRun) {
            foreach ($tables as $t) {
                $this->line("  - {$t}");
            }
            $this->newLine();
            $this->info('[DRY-RUN] 실제 변경 없음');

            return self::SUCCESS;
        }

        $dropped = 0;
        $created = 0;

        foreach ($tables as $table) {
            // 기존 트리거 삭제
            foreach (['ai', 'au', 'ad'] as $suffix) {
                $triggerName = "trg_{$table}_{$suffix}";
                DB::unprepared("DROP TRIGGER IF EXISTS `{$triggerName}`");
                $dropped++;
            }

            if (! $dropOnly) {
                // 트리거 재생성
                $this->createTriggersForTable($dbName, $table);
                $created += 3;
            }

            $this->line("  {$table}: ".($dropOnly ? '삭제 완료' : '재생성 완료'));
        }

        $this->newLine();
        $this->info("결과: 삭제 {$dropped}개, 생성 {$created}개");

        return self::SUCCESS;
    }

    private function getTargetTables(string $dbName, ?string $specificTable): array
    {
        if ($specificTable) {
            if (in_array($specificTable, $this->excludeTables)) {
                $this->error("{$specificTable}은(는) 트리거 제외 테이블입니다.");

                return [];
            }

            return [$specificTable];
        }

        $rows = DB::select("
            SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
            WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'
            ORDER BY TABLE_NAME
        ", [$dbName]);

        return collect($rows)
            ->pluck('TABLE_NAME')
            ->reject(fn ($t) => in_array($t, $this->excludeTables))
            ->values()
            ->toArray();
    }

    private function createTriggersForTable(string $dbName, string $table): void
    {
        // PK 컬럼
        $pkRow = DB::selectOne("
            SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
            WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_KEY = 'PRI'
            LIMIT 1
        ", [$dbName, $table]);

        $pkCol = $pkRow?->COLUMN_NAME ?? 'id';

        // 컬럼 목록 (제외: created_at, updated_at, deleted_at, remember_token)
        $excludeCols = ['created_at', 'updated_at', 'deleted_at', 'remember_token'];
        $columns = DB::select('
            SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
            WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
            ORDER BY ORDINAL_POSITION
        ', [$dbName, $table]);

        $cols = collect($columns)
            ->pluck('COLUMN_NAME')
            ->reject(fn ($c) => in_array($c, $excludeCols))
            ->values()
            ->toArray();

        if (empty($cols)) {
            return;
        }

        // JSON_OBJECT 표현식
        $newJson = 'JSON_OBJECT('.collect($cols)->map(fn ($c) => "'{$c}', NEW.`{$c}`")->implode(', ').')';
        $oldJson = 'JSON_OBJECT('.collect($cols)->map(fn ($c) => "'{$c}', OLD.`{$c}`")->implode(', ').')';

        // changed_columns (UPDATE용)
        $changedCols = collect($cols)->map(fn ($c) => "IF(NOT (NEW.`{$c}` <=> OLD.`{$c}`), '{$c}', NULL)")->implode(', ');
        $changeCheck = collect($cols)->map(fn ($c) => "NOT (NEW.`{$c}` <=> OLD.`{$c}`)")->implode(' OR ');

        $tenantExpr = in_array('tenant_id', $cols) ? 'NEW.`tenant_id`' : 'NULL';
        $tenantExprOld = in_array('tenant_id', $cols) ? 'OLD.`tenant_id`' : 'NULL';

        $guard = 'IF @disable_audit_trigger IS NULL OR @disable_audit_trigger != 1 THEN';

        // INSERT trigger
        DB::unprepared("
            CREATE TRIGGER `trg_{$table}_ai` AFTER INSERT ON `{$table}`
            FOR EACH ROW BEGIN
                {$guard}
                    INSERT INTO trigger_audit_logs (table_name, row_id, dml_type, old_values, new_values, changed_columns, tenant_id, actor_id, session_info, db_user, created_at)
                    VALUES ('{$table}', NEW.`{$pkCol}`, 'INSERT', NULL, {$newJson}, NULL, {$tenantExpr}, @sam_actor_id, @sam_session_info, CURRENT_USER(), NOW());
                END IF;
            END
        ");

        // UPDATE trigger
        DB::unprepared("
            CREATE TRIGGER `trg_{$table}_au` AFTER UPDATE ON `{$table}`
            FOR EACH ROW BEGIN
                {$guard}
                    IF {$changeCheck} THEN
                        INSERT INTO trigger_audit_logs (table_name, row_id, dml_type, old_values, new_values, changed_columns, tenant_id, actor_id, session_info, db_user, created_at)
                        VALUES ('{$table}', NEW.`{$pkCol}`, 'UPDATE', {$oldJson}, {$newJson}, JSON_ARRAY({$changedCols}), {$tenantExpr}, @sam_actor_id, @sam_session_info, CURRENT_USER(), NOW());
                    END IF;
                END IF;
            END
        ");

        // DELETE trigger
        DB::unprepared("
            CREATE TRIGGER `trg_{$table}_ad` AFTER DELETE ON `{$table}`
            FOR EACH ROW BEGIN
                {$guard}
                    INSERT INTO trigger_audit_logs (table_name, row_id, dml_type, old_values, new_values, changed_columns, tenant_id, actor_id, session_info, db_user, created_at)
                    VALUES ('{$table}', OLD.`{$pkCol}`, 'DELETE', {$oldJson}, NULL, NULL, {$tenantExprOld}, @sam_actor_id, @sam_session_info, CURRENT_USER(), NOW());
                END IF;
            END
        ");
    }
}
