<?php

namespace App\Services\Audit;

use App\Models\Audit\TriggerAuditLog;
use App\Services\Service;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;

class AuditRollbackService extends Service
{
    /**
     * 역방향 SQL 생성 (실행하지 않음, 미리보기용)
     */
    public function generateRollbackSQL(int $auditId): string
    {
        $log = TriggerAuditLog::findOrFail($auditId);

        return match ($log->dml_type) {
            'INSERT' => $this->buildDeleteSQL($log),
            'UPDATE' => $this->buildRevertUpdateSQL($log),
            'DELETE' => $this->buildReinsertSQL($log),
        };
    }

    /**
     * 복구 실행 (트랜잭션)
     */
    public function executeRollback(int $auditId): array
    {
        $log = TriggerAuditLog::findOrFail($auditId);
        $sql = $this->generateRollbackSQL($auditId);

        DB::statement('SET @disable_audit_trigger = 1');

        try {
            DB::transaction(function () use ($sql) {
                DB::statement($sql);
            });

            return [
                'success' => true,
                'audit_id' => $auditId,
                'table_name' => $log->table_name,
                'row_id' => $log->row_id,
                'original_dml' => $log->dml_type,
                'rollback_sql' => $sql,
            ];
        } catch (\Throwable $e) {
            return [
                'success' => false,
                'audit_id' => $auditId,
                'error' => $e->getMessage(),
                'rollback_sql' => $sql,
            ];
        } finally {
            DB::statement('SET @disable_audit_trigger = NULL');
        }
    }

    /**
     * 특정 레코드의 특정 시점 상태 조회
     */
    public function getRecordStateAt(string $table, string $rowId, Carbon $at): ?array
    {
        $log = TriggerAuditLog::where('table_name', $table)
            ->where('row_id', $rowId)
            ->where('created_at', '<=', $at)
            ->orderByDesc('created_at')
            ->first();

        if (! $log) {
            return null;
        }

        return match ($log->dml_type) {
            'INSERT', 'UPDATE' => $log->new_values,
            'DELETE' => null,
        };
    }

    /**
     * INSERT 복구: 삽입된 레코드 삭제
     */
    private function buildDeleteSQL(TriggerAuditLog $log): string
    {
        $rowId = DB::getPdo()->quote($log->row_id);

        return "DELETE FROM `{$log->table_name}` WHERE `id` = {$rowId} LIMIT 1";
    }

    /**
     * UPDATE 복구: old_values로 되돌림
     */
    private function buildRevertUpdateSQL(TriggerAuditLog $log): string
    {
        if (empty($log->old_values)) {
            throw new \RuntimeException("No old_values for audit #{$log->id}");
        }

        $pdo = DB::getPdo();
        $sets = collect($log->old_values)
            ->map(fn ($val, $col) => "`{$col}` = ".($val === null ? 'NULL' : $pdo->quote((string) $val)))
            ->implode(', ');

        $rowId = $pdo->quote($log->row_id);

        return "UPDATE `{$log->table_name}` SET {$sets} WHERE `id` = {$rowId} LIMIT 1";
    }

    /**
     * DELETE 복구: old_values로 재삽입
     */
    private function buildReinsertSQL(TriggerAuditLog $log): string
    {
        if (empty($log->old_values)) {
            throw new \RuntimeException("No old_values for audit #{$log->id}");
        }

        $pdo = DB::getPdo();
        $cols = collect($log->old_values)->keys()->map(fn ($c) => "`{$c}`")->implode(', ');
        $vals = collect($log->old_values)->values()
            ->map(fn ($v) => $v === null ? 'NULL' : $pdo->quote((string) $v))
            ->implode(', ');

        return "INSERT INTO `{$log->table_name}` ({$cols}) VALUES ({$vals})";
    }
}
