<?php

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('operation_runs')) {
            return;
        }

        $driver = Schema::getConnection()->getDriverName();

        if ($driver === 'sqlite') {
            Schema::disableForeignKeyConstraints();

            Schema::rename('operation_runs', 'operation_runs_old');

            foreach ([
                'operation_runs_active_unique',
                'operation_runs_managed_environment_id_type_created_at_index',
                'operation_runs_managed_environment_id_created_at_index',
                'operation_runs_tenant_id_type_created_at_index',
                'operation_runs_tenant_id_created_at_index',
            ] as $indexName) {
                DB::statement("DROP INDEX IF EXISTS {$indexName}");
            }

            Schema::create('operation_runs', function (Blueprint $table) {
                $table->id();
                $table->foreignId('workspace_id')->constrained()->cascadeOnDelete();
                $table->foreignId('managed_environment_id')->nullable()->constrained()->cascadeOnDelete();
                $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
                $table->string('initiator_name');
                $table->string('type');
                $table->string('status');
                $table->string('outcome')->default('pending');
                $table->string('run_identity_hash');
                $table->json('summary_counts')->default('{}');
                $table->json('failure_summary')->default('[]');
                $table->json('context')->default('{}');
                $table->timestamp('started_at')->nullable();
                $table->timestamp('completed_at')->nullable();
                $table->timestamps();

                $table->index(['workspace_id', 'type', 'created_at']);
                $table->index(['workspace_id', 'created_at']);
                $table->index(['managed_environment_id', 'type', 'created_at']);
                $table->index(['managed_environment_id', 'created_at']);
            });

            DB::table('operation_runs_old')
                ->orderBy('id')
                ->chunkById(500, function ($rows): void {
                    foreach ($rows as $row) {
                        $workspaceId = DB::table('managed_environments')
                            ->where('id', (int) $row->managed_environment_id)
                            ->value('workspace_id');

                        DB::table('operation_runs')->insert([
                            'id' => (int) $row->id,
                            'workspace_id' => (int) $workspaceId,
                            'managed_environment_id' => $row->managed_environment_id,
                            'user_id' => $row->user_id,
                            'initiator_name' => $row->initiator_name,
                            'type' => $row->type,
                            'status' => $row->status,
                            'outcome' => $row->outcome,
                            'run_identity_hash' => $row->run_identity_hash,
                            'summary_counts' => $row->summary_counts,
                            'failure_summary' => $row->failure_summary,
                            'context' => $row->context,
                            'started_at' => $row->started_at,
                            'completed_at' => $row->completed_at,
                            'created_at' => $row->created_at,
                            'updated_at' => $row->updated_at,
                        ]);
                    }
                }, 'id');

            Schema::drop('operation_runs_old');

            DB::statement("CREATE UNIQUE INDEX operation_runs_active_unique_tenant ON operation_runs (managed_environment_id, run_identity_hash) WHERE managed_environment_id IS NOT NULL AND status IN ('queued', 'running')");
            DB::statement("CREATE UNIQUE INDEX operation_runs_active_unique_workspace ON operation_runs (workspace_id, run_identity_hash) WHERE managed_environment_id IS NULL AND status IN ('queued', 'running')");

            Schema::enableForeignKeyConstraints();

            return;
        }

        if (! Schema::hasColumn('operation_runs', 'workspace_id')) {
            Schema::table('operation_runs', function (Blueprint $table) use ($driver): void {
                $column = $table->foreignId('workspace_id')->nullable();

                if ($driver !== 'sqlite') {
                    $column->after('id')->constrained()->cascadeOnDelete();
                }

                $table->index(['workspace_id', 'type', 'created_at']);
                $table->index(['workspace_id', 'created_at']);
            });
        }

        $this->backfillWorkspaceId($driver);

        if ($driver === 'pgsql') {
            DB::statement('ALTER TABLE operation_runs ALTER COLUMN managed_environment_id DROP NOT NULL');
            DB::statement('ALTER TABLE operation_runs ALTER COLUMN workspace_id SET NOT NULL');
        }

        if ($driver === 'mysql') {
            DB::statement('ALTER TABLE operation_runs MODIFY managed_environment_id BIGINT UNSIGNED NULL');
            DB::statement('ALTER TABLE operation_runs MODIFY workspace_id BIGINT UNSIGNED NOT NULL');
        }

        DB::statement('DROP INDEX IF EXISTS operation_runs_active_unique');

        DB::statement("CREATE UNIQUE INDEX operation_runs_active_unique_tenant ON operation_runs (managed_environment_id, run_identity_hash) WHERE managed_environment_id IS NOT NULL AND status IN ('queued', 'running')");
        DB::statement("CREATE UNIQUE INDEX operation_runs_active_unique_workspace ON operation_runs (workspace_id, run_identity_hash) WHERE managed_environment_id IS NULL AND status IN ('queued', 'running')");
    }

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

        $driver = Schema::getConnection()->getDriverName();

        if ($driver === 'sqlite') {
            Schema::disableForeignKeyConstraints();

            Schema::rename('operation_runs', 'operation_runs_with_workspace');

            foreach ([
                'operation_runs_active_unique_tenant',
                'operation_runs_active_unique_workspace',
                'operation_runs_workspace_id_type_created_at_index',
                'operation_runs_workspace_id_created_at_index',
                'operation_runs_managed_environment_id_type_created_at_index',
                'operation_runs_managed_environment_id_created_at_index',
                'operation_runs_tenant_id_type_created_at_index',
                'operation_runs_tenant_id_created_at_index',
            ] as $indexName) {
                DB::statement("DROP INDEX IF EXISTS {$indexName}");
            }

            Schema::create('operation_runs', function (Blueprint $table) {
                $table->id();
                $table->foreignId('managed_environment_id')->constrained()->cascadeOnDelete();
                $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
                $table->string('initiator_name');
                $table->string('type');
                $table->string('status');
                $table->string('outcome')->default('pending');
                $table->string('run_identity_hash');
                $table->json('summary_counts')->default('{}');
                $table->json('failure_summary')->default('[]');
                $table->json('context')->default('{}');
                $table->timestamp('started_at')->nullable();
                $table->timestamp('completed_at')->nullable();
                $table->timestamps();

                $table->index(['managed_environment_id', 'type', 'created_at']);
                $table->index(['managed_environment_id', 'created_at']);
            });

            DB::table('operation_runs_with_workspace')
                ->whereNotNull('managed_environment_id')
                ->orderBy('id')
                ->chunkById(500, function ($rows): void {
                    foreach ($rows as $row) {
                        DB::table('operation_runs')->insert([
                            'id' => (int) $row->id,
                            'managed_environment_id' => (int) $row->managed_environment_id,
                            'user_id' => $row->user_id,
                            'initiator_name' => $row->initiator_name,
                            'type' => $row->type,
                            'status' => $row->status,
                            'outcome' => $row->outcome,
                            'run_identity_hash' => $row->run_identity_hash,
                            'summary_counts' => $row->summary_counts,
                            'failure_summary' => $row->failure_summary,
                            'context' => $row->context,
                            'started_at' => $row->started_at,
                            'completed_at' => $row->completed_at,
                            'created_at' => $row->created_at,
                            'updated_at' => $row->updated_at,
                        ]);
                    }
                }, 'id');

            Schema::drop('operation_runs_with_workspace');

            DB::statement("CREATE UNIQUE INDEX operation_runs_active_unique ON operation_runs (managed_environment_id, run_identity_hash) WHERE status IN ('queued', 'running')");

            Schema::enableForeignKeyConstraints();

            return;
        }

        DB::statement('DROP INDEX IF EXISTS operation_runs_active_unique_tenant');
        DB::statement('DROP INDEX IF EXISTS operation_runs_active_unique_workspace');
        DB::statement('DROP INDEX IF EXISTS operation_runs_active_unique');

        DB::statement("CREATE UNIQUE INDEX operation_runs_active_unique ON operation_runs (managed_environment_id, run_identity_hash) WHERE status IN ('queued', 'running')");

        if ($driver === 'pgsql') {
            DB::statement('ALTER TABLE operation_runs ALTER COLUMN managed_environment_id SET NOT NULL');
            DB::statement('ALTER TABLE operation_runs ALTER COLUMN workspace_id DROP NOT NULL');
        }

        if ($driver === 'mysql') {
            DB::statement('ALTER TABLE operation_runs MODIFY managed_environment_id BIGINT UNSIGNED NOT NULL');
            DB::statement('ALTER TABLE operation_runs MODIFY workspace_id BIGINT UNSIGNED NULL');
        }

        Schema::table('operation_runs', function (Blueprint $table): void {
            $table->dropIndex(['workspace_id', 'type', 'created_at']);
            $table->dropIndex(['workspace_id', 'created_at']);
            $table->dropConstrainedForeignId('workspace_id');
        });
    }

    private function backfillWorkspaceId(string $driver): void
    {
        if ($driver === 'pgsql') {
            DB::statement(<<<'SQL'
                UPDATE operation_runs
                SET workspace_id = managed_environments.workspace_id
                FROM managed_environments
                WHERE operation_runs.workspace_id IS NULL
                  AND operation_runs.managed_environment_id = managed_environments.id
            SQL);

            return;
        }

        if ($driver === 'mysql') {
            DB::statement(<<<'SQL'
                UPDATE operation_runs
                JOIN managed_environments ON managed_environments.id = operation_runs.managed_environment_id
                SET operation_runs.workspace_id = managed_environments.workspace_id
                WHERE operation_runs.workspace_id IS NULL
            SQL);

            return;
        }

        DB::table('operation_runs')
            ->whereNull('workspace_id')
            ->orderBy('id')
            ->chunkById(500, function ($rows): void {
                foreach ($rows as $row) {
                    $workspaceId = DB::table('managed_environments')
                        ->where('id', (int) $row->managed_environment_id)
                        ->value('workspace_id');

                    if ($workspaceId !== null) {
                        DB::table('operation_runs')
                            ->where('id', (int) $row->id)
                            ->update(['workspace_id' => (int) $workspaceId]);
                    }
                }
            }, 'id');
    }
};
