<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        if (! Schema::hasTable('audit_logs')) {
            return;
        }

        Schema::table('audit_logs', function (Blueprint $table): void {
            if (! Schema::hasColumn('audit_logs', 'summary')) {
                $table->text('summary')->nullable()->after('action');
            }

            if (! Schema::hasColumn('audit_logs', 'outcome')) {
                $table->string('outcome')->nullable()->after('status');
            }

            if (! Schema::hasColumn('audit_logs', 'actor_type')) {
                $table->string('actor_type')->nullable()->after('actor_name');
            }

            if (! Schema::hasColumn('audit_logs', 'actor_label')) {
                $table->string('actor_label')->nullable()->after('actor_type');
            }

            if (! Schema::hasColumn('audit_logs', 'target_label')) {
                $table->string('target_label')->nullable()->after('resource_id');
            }

            if (! Schema::hasColumn('audit_logs', 'operation_run_id')) {
                $table->foreignId('operation_run_id')->nullable()->after('workspace_id')->constrained()->nullOnDelete();
            }
        });

        $this->backfillDerivedColumns();

        Schema::table('audit_logs', function (Blueprint $table): void {
            $table->index(['workspace_id', 'recorded_at'], 'audit_logs_workspace_recorded_at_index');
            $table->index(['managed_environment_id', 'recorded_at'], 'audit_logs_tenant_recorded_at_index');
            $table->index(['action', 'recorded_at'], 'audit_logs_action_recorded_at_index');
            $table->index(['outcome', 'recorded_at'], 'audit_logs_outcome_recorded_at_index');
        });
    }

    public function down(): void
    {
        if (! Schema::hasTable('audit_logs')) {
            return;
        }

        Schema::table('audit_logs', function (Blueprint $table): void {
            foreach ([
                'audit_logs_workspace_recorded_at_index',
                'audit_logs_tenant_recorded_at_index',
                'audit_logs_action_recorded_at_index',
                'audit_logs_outcome_recorded_at_index',
            ] as $indexName) {
                $table->dropIndex($indexName);
            }

            if (Schema::hasColumn('audit_logs', 'operation_run_id')) {
                $table->dropConstrainedForeignId('operation_run_id');
            }

            foreach (['target_label', 'actor_label', 'actor_type', 'outcome', 'summary'] as $column) {
                if (Schema::hasColumn('audit_logs', $column)) {
                    $table->dropColumn($column);
                }
            }
        });
    }

    private function backfillDerivedColumns(): void
    {
        DB::table('audit_logs')
            ->orderBy('id')
            ->chunkById(500, function ($rows): void {
                foreach ($rows as $row) {
                    $metadata = $this->decodeJson($row->metadata ?? null);

                    $resourceType = is_string($row->resource_type ?? null) ? $row->resource_type : null;
                    $resourceId = $row->resource_id ?? null;
                    $action = is_string($row->action ?? null) ? $row->action : 'audit.event';
                    $status = is_string($row->status ?? null) ? strtolower(trim($row->status)) : 'info';

                    $outcome = match ($status) {
                        'success', 'succeeded', 'completed', 'complete' => 'success',
                        'partial', 'partially_succeeded', 'partial_success', 'partial_failure' => 'partial',
                        'blocked', 'skipped', 'deferred', 'cancelled', 'canceled' => 'blocked',
                        'failure', 'failed', 'error', 'errored' => 'failed',
                        default => 'info',
                    };

                    $actorType = match (true) {
                        filled($row->actor_id ?? null), filled($row->actor_email ?? null), filled($row->actor_name ?? null) => 'human',
                        str_starts_with(strtolower($action), 'platform.') => 'platform',
                        str_contains(strtolower($action), 'schedule') || array_key_exists('backup_schedule_id', $metadata) => 'scheduled',
                        default => 'system',
                    };

                    $actorLabel = is_string($row->actor_name ?? null) && trim($row->actor_name) !== ''
                        ? trim($row->actor_name)
                        : ((is_string($row->actor_email ?? null) && trim($row->actor_email) !== '')
                            ? trim($row->actor_email)
                            : ucfirst($actorType));

                    $targetLabel = $this->fallbackTargetLabel($resourceType, $resourceId);

                    $operationRunCandidate = is_numeric($metadata['operation_run_id'] ?? null)
                        ? (int) $metadata['operation_run_id']
                        : (($resourceType === 'operation_run' && is_numeric($resourceId)) ? (int) $resourceId : null);

                    $operationRunId = $this->resolveExistingOperationRunId($operationRunCandidate);

                    $summary = $this->fallbackSummary($action, $targetLabel);

                    DB::table('audit_logs')
                        ->where('id', (int) $row->id)
                        ->update([
                            'summary' => $summary,
                            'outcome' => $outcome,
                            'actor_type' => $actorType,
                            'actor_label' => $actorLabel,
                            'target_label' => $targetLabel,
                            'operation_run_id' => $operationRunId,
                        ]);
                }
            }, 'id');
    }

    /**
     * @return array<string, mixed>
     */
    private function decodeJson(mixed $value): array
    {
        if (is_array($value)) {
            return $value;
        }

        if (! is_string($value) || trim($value) === '') {
            return [];
        }

        $decoded = json_decode($value, true);

        return is_array($decoded) ? $decoded : [];
    }

    private function fallbackTargetLabel(?string $type, mixed $id): ?string
    {
        if (! filled($type) && ! filled($id)) {
            return null;
        }

        $subject = is_string($type) && trim($type) !== ''
            ? ucfirst(str_replace(['_', '.'], ' ', trim($type)))
            : 'Record';

        if (filled($id)) {
            return sprintf('%s #%s', $subject, (string) $id);
        }

        return $subject;
    }

    private function fallbackSummary(string $action, ?string $targetLabel): string
    {
        $segments = preg_split('/[._]+/', strtolower(trim($action))) ?: ['audit', 'event'];
        $segments = array_values(array_filter($segments, static fn (string $segment): bool => $segment !== ''));

        $verb = array_pop($segments) ?? 'recorded';
        $subject = $segments === [] ? 'Audit event' : ucfirst(implode(' ', $segments));
        $verb = match ($verb) {
            'add' => 'added',
            'remove' => 'removed',
            'start' => 'started',
            'started' => 'started',
            'finish', 'finished' => 'finished',
            'complete', 'completed' => 'completed',
            'fail', 'failed' => 'failed',
            'update', 'updated' => 'updated',
            'create', 'created' => 'created',
            'delete', 'deleted' => 'deleted',
            'enable', 'enabled' => 'enabled',
            'disable', 'disabled' => 'disabled',
            default => str_replace('-', ' ', $verb),
        };

        $summary = trim(sprintf('%s %s', $subject, $verb));

        if ($targetLabel !== null && $targetLabel !== '' && ! str_contains($summary, $targetLabel)) {
            $summary .= ' for '.$targetLabel;
        }

        return $summary;
    }

    private function resolveExistingOperationRunId(?int $operationRunId): ?int
    {
        if ($operationRunId === null || $operationRunId <= 0) {
            return null;
        }

        return DB::table('operation_runs')
            ->where('id', $operationRunId)
            ->exists()
            ? $operationRunId
            : null;
    }
};
