<?php

declare(strict_types=1);

use App\Support\Onboarding\OnboardingCheckpoint;
use App\Support\Onboarding\OnboardingLifecycleState;
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('managed_environment_onboarding_sessions')) {
            return;
        }

        $needsVersion = ! Schema::hasColumn('managed_environment_onboarding_sessions', 'version');
        $needsLifecycleState = ! Schema::hasColumn('managed_environment_onboarding_sessions', 'lifecycle_state');
        $needsCurrentCheckpoint = ! Schema::hasColumn('managed_environment_onboarding_sessions', 'current_checkpoint');
        $needsLastCompletedCheckpoint = ! Schema::hasColumn('managed_environment_onboarding_sessions', 'last_completed_checkpoint');
        $needsReasonCode = ! Schema::hasColumn('managed_environment_onboarding_sessions', 'reason_code');
        $needsBlockingReasonCode = ! Schema::hasColumn('managed_environment_onboarding_sessions', 'blocking_reason_code');

        if ($needsVersion || $needsLifecycleState || $needsCurrentCheckpoint || $needsLastCompletedCheckpoint || $needsReasonCode || $needsBlockingReasonCode) {
            Schema::table('managed_environment_onboarding_sessions', function (Blueprint $table) use ($needsVersion, $needsLifecycleState, $needsCurrentCheckpoint, $needsLastCompletedCheckpoint, $needsReasonCode, $needsBlockingReasonCode): void {
                if ($needsVersion) {
                    $table->unsignedBigInteger('version')->default(1);
                }

                if ($needsLifecycleState) {
                    $table->string('lifecycle_state')->default(OnboardingLifecycleState::Draft->value);
                }

                if ($needsCurrentCheckpoint) {
                    $table->string('current_checkpoint')->nullable();
                }

                if ($needsLastCompletedCheckpoint) {
                    $table->string('last_completed_checkpoint')->nullable();
                }

                if ($needsReasonCode) {
                    $table->string('reason_code')->nullable();
                }

                if ($needsBlockingReasonCode) {
                    $table->string('blocking_reason_code')->nullable();
                }
            });
        }

        DB::table('managed_environment_onboarding_sessions')
            ->orderBy('id')
            ->chunkById(500, function ($rows): void {
                foreach ($rows as $row) {
                    $state = is_string($row->state) ? json_decode($row->state, true) : null;
                    $state = is_array($state) ? $state : [];

                    $currentCheckpoint = $this->currentCheckpoint($row->current_step, $state, $row->completed_at, $row->cancelled_at);
                    $lastCompletedCheckpoint = $this->lastCompletedCheckpoint($currentCheckpoint, $state, $row->completed_at);
                    $lifecycleState = $this->lifecycleState($row->completed_at, $row->cancelled_at, $currentCheckpoint, $state);

                    DB::table('managed_environment_onboarding_sessions')
                        ->where('id', $row->id)
                        ->update([
                            'version' => max(1, (int) ($row->version ?? 1)),
                            'lifecycle_state' => $lifecycleState,
                            'current_checkpoint' => $currentCheckpoint,
                            'last_completed_checkpoint' => $lastCompletedCheckpoint,
                            'reason_code' => null,
                            'blocking_reason_code' => null,
                        ]);
                }
            }, 'id');

        if (! $this->hasIndex('managed_environment_onboarding_sessions_lifecycle_state_index')) {
            Schema::table('managed_environment_onboarding_sessions', function (Blueprint $table): void {
                $table->index('lifecycle_state', 'managed_environment_onboarding_sessions_lifecycle_state_index');
            });
        }
    }

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

        if ($this->hasIndex('managed_environment_onboarding_sessions_lifecycle_state_index')) {
            Schema::table('managed_environment_onboarding_sessions', function (Blueprint $table): void {
                $table->dropIndex('managed_environment_onboarding_sessions_lifecycle_state_index');
            });
        }

        Schema::table('managed_environment_onboarding_sessions', function (Blueprint $table): void {
            foreach (['version', 'lifecycle_state', 'current_checkpoint', 'last_completed_checkpoint', 'reason_code', 'blocking_reason_code'] as $column) {
                if (Schema::hasColumn('managed_environment_onboarding_sessions', $column)) {
                    $table->dropColumn($column);
                }
            }
        });
    }

    /**
     * @param  array<string, mixed>  $state
     */
    private function currentCheckpoint(
        ?string $currentStep,
        array $state,
        mixed $completedAt,
        mixed $cancelledAt,
    ): ?string {
        if ($completedAt !== null) {
            return OnboardingCheckpoint::CompleteActivate->value;
        }

        if (is_string($currentStep) && OnboardingCheckpoint::fromCurrentStep($currentStep) instanceof OnboardingCheckpoint) {
            return OnboardingCheckpoint::fromCurrentStep($currentStep)?->value;
        }

        if ($cancelledAt !== null) {
            return OnboardingCheckpoint::Identify->value;
        }

        if ($this->normalizeInteger($state['provider_connection_id'] ?? null) !== null) {
            return OnboardingCheckpoint::VerifyAccess->value;
        }

        $entraTenantId = $state['entra_tenant_id'] ?? null;

        if (is_string($entraTenantId) && trim($entraTenantId) !== '') {
            return OnboardingCheckpoint::ConnectProvider->value;
        }

        return OnboardingCheckpoint::Identify->value;
    }

    /**
     * @param  array<string, mixed>  $state
     */
    private function lastCompletedCheckpoint(?string $currentCheckpoint, array $state, mixed $completedAt): ?string
    {
        if ($completedAt !== null) {
            return OnboardingCheckpoint::CompleteActivate->value;
        }

        return match ($currentCheckpoint) {
            OnboardingCheckpoint::ConnectProvider->value => OnboardingCheckpoint::Identify->value,
            OnboardingCheckpoint::VerifyAccess->value => OnboardingCheckpoint::ConnectProvider->value,
            OnboardingCheckpoint::Bootstrap->value => OnboardingCheckpoint::VerifyAccess->value,
            OnboardingCheckpoint::CompleteActivate->value => $this->hasBootstrapSelection($state)
                ? OnboardingCheckpoint::Bootstrap->value
                : OnboardingCheckpoint::VerifyAccess->value,
            default => null,
        };
    }

    /**
     * @param  array<string, mixed>  $state
     */
    private function lifecycleState(
        mixed $completedAt,
        mixed $cancelledAt,
        ?string $currentCheckpoint,
        array $state,
    ): string {
        if ($completedAt !== null) {
            return OnboardingLifecycleState::Completed->value;
        }

        if ($cancelledAt !== null) {
            return OnboardingLifecycleState::Cancelled->value;
        }

        if ($currentCheckpoint === OnboardingCheckpoint::CompleteActivate->value) {
            return OnboardingLifecycleState::ReadyForActivation->value;
        }

        if ($currentCheckpoint === OnboardingCheckpoint::Bootstrap->value && $this->hasBootstrapSelection($state)) {
            return OnboardingLifecycleState::Draft->value;
        }

        return OnboardingLifecycleState::Draft->value;
    }

    /**
     * @param  array<string, mixed>  $state
     */
    private function hasBootstrapSelection(array $state): bool
    {
        $types = $state['bootstrap_operation_types'] ?? null;

        return is_array($types) && $types !== [];
    }

    private function normalizeInteger(mixed $value): ?int
    {
        if (is_int($value) && $value > 0) {
            return $value;
        }

        if (is_string($value) && ctype_digit(trim($value))) {
            $normalized = (int) trim($value);

            return $normalized > 0 ? $normalized : null;
        }

        return null;
    }

    private function hasIndex(string $indexName): bool
    {
        $driver = Schema::getConnection()->getDriverName();

        return match ($driver) {
            'pgsql' => DB::table('pg_indexes')
                ->where('schemaname', 'public')
                ->where('indexname', $indexName)
                ->exists(),
            'sqlite' => collect(DB::select("PRAGMA index_list('managed_environment_onboarding_sessions')"))
                ->contains(fn (object $index): bool => ($index->name ?? null) === $indexName),
            default => false,
        };
    }
};
