<?php

namespace App\Services;

use Illuminate\Support\Facades\DB;

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

    /** 트리거 제외 컬럼 */
    private array $excludeColumns = [
        'created_at',
        'updated_at',
        'deleted_at',
        'remember_token',
    ];

    /**
     * 전체 테이블 + 트리거 존재 여부 매핑
     */
    public function getTableTriggerStatus(): array
    {
        $dbName = config('database.connections.mysql.database');

        // 모든 BASE TABLE 조회
        $allTables = collect(DB::select("
            SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
            WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'
            ORDER BY TABLE_NAME
        ", [$dbName]))->pluck('TABLE_NAME')->toArray();

        // 추적 대상 테이블
        $trackedTables = array_values(array_diff($allTables, $this->excludeTables));

        // 현재 존재하는 트리거 조회
        $triggers = collect(DB::select("
            SELECT TRIGGER_NAME, EVENT_OBJECT_TABLE, EVENT_MANIPULATION
            FROM INFORMATION_SCHEMA.TRIGGERS
            WHERE TRIGGER_SCHEMA = ? AND TRIGGER_NAME LIKE 'trg_%'
            ORDER BY EVENT_OBJECT_TABLE, EVENT_MANIPULATION
        ", [$dbName]));

        // 테이블별 트리거 상태 매핑
        $tableStatus = [];
        foreach ($trackedTables as $table) {
            // 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]);

            // 컬럼 수 조회
            $colCount = DB::selectOne('
                SELECT COUNT(*) as cnt FROM INFORMATION_SCHEMA.COLUMNS
                WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
            ', [$dbName, $table])->cnt;

            $tableTriggers = $triggers->where('EVENT_OBJECT_TABLE', $table);

            $tableStatus[] = [
                'table_name' => $table,
                'pk_column' => $pkRow?->COLUMN_NAME ?? '-',
                'column_count' => $colCount,
                'has_insert' => $tableTriggers->contains('EVENT_MANIPULATION', 'INSERT'),
                'has_update' => $tableTriggers->contains('EVENT_MANIPULATION', 'UPDATE'),
                'has_delete' => $tableTriggers->contains('EVENT_MANIPULATION', 'DELETE'),
            ];
        }

        return [
            'total_tables' => count($allTables),
            'tracked_tables' => count($trackedTables),
            'excluded_tables' => count($this->excludeTables),
            'active_triggers' => $triggers->count(),
            'table_status' => $tableStatus,
            'exclude_tables' => $this->excludeTables,
        ];
    }

    /**
     * 트리거 재생성 (단일 테이블 또는 전체)
     */
    public function regenerate(?string $tableName = null): array
    {
        $dbName = config('database.connections.mysql.database');
        $tables = $this->getTargetTables($dbName, $tableName);

        if (empty($tables)) {
            return ['success' => false, 'message' => '대상 테이블이 없습니다.'];
        }

        $dropped = 0;
        $created = 0;

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

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

        $label = $tableName ?? '전체';

        return [
            'success' => true,
            'message' => "[{$label}] 트리거 재생성 완료 (삭제: {$dropped}, 생성: {$created})",
        ];
    }

    /**
     * 트리거 삭제 (단일 테이블 또는 전체)
     */
    public function drop(?string $tableName = null): array
    {
        $dbName = config('database.connections.mysql.database');
        $tables = $this->getTargetTables($dbName, $tableName);

        if (empty($tables)) {
            return ['success' => false, 'message' => '대상 테이블이 없습니다.'];
        }

        $dropped = 0;

        foreach ($tables as $table) {
            foreach (['ai', 'au', 'ad'] as $suffix) {
                DB::unprepared("DROP TRIGGER IF EXISTS `trg_{$table}_{$suffix}`");
                $dropped++;
            }
        }

        $label = $tableName ?? '전체';

        return [
            'success' => true,
            'message' => "[{$label}] 트리거 삭제 완료 ({$dropped}개)",
        ];
    }

    private function getTargetTables(string $dbName, ?string $tableName): array
    {
        if ($tableName) {
            if (in_array($tableName, $this->excludeTables)) {
                return [];
            }

            return [$tableName];
        }

        $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
    {
        $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';

        $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, $this->excludeColumns))
            ->values()
            ->toArray();

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

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

        $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';

        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, operation_id, db_user, created_at)
                    VALUES ('{$table}', NEW.`{$pkCol}`, 'INSERT', NULL, {$newJson}, NULL, {$tenantExpr}, @sam_actor_id, @sam_session_info, @sam_operation_id, CURRENT_USER(), NOW());
                END IF;
            END
        ");

        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, operation_id, db_user, created_at)
                        VALUES ('{$table}', NEW.`{$pkCol}`, 'UPDATE', {$oldJson}, {$newJson}, JSON_ARRAY({$changedCols}), {$tenantExpr}, @sam_actor_id, @sam_session_info, @sam_operation_id, CURRENT_USER(), NOW());
                    END IF;
                END IF;
            END
        ");

        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, operation_id, db_user, created_at)
                    VALUES ('{$table}', OLD.`{$pkCol}`, 'DELETE', {$oldJson}, NULL, NULL, {$tenantExprOld}, @sam_actor_id, @sam_session_info, @sam_operation_id, CURRENT_USER(), NOW());
                END IF;
            END
        ");
    }
}
