<?php

use App\Models\User;
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
    {
        $required = [
            'roles',
            'permissions',
            'role_has_permissions',
            'model_has_roles',
            'model_has_permissions',
            'permission_role',
            'role_user',
        ];

        foreach ($required as $table) {
            if (! Schema::hasTable($table)) {
                throw new \RuntimeException("Authorization bridge retirement blocked: required table {$table} is missing.");
            }
        }

        $legacyRolePermissions = $this->pairs('permission_role', 'permission_id', 'role_id');
        $canonicalRolePermissions = $this->pairs('role_has_permissions', 'permission_id', 'role_id');
        $legacyUserRoles = $this->pairs('role_user', 'role_id', 'user_id');
        $canonicalUserRoles = DB::table('model_has_roles')
            ->where('model_type', User::class)
            ->orderBy('role_id')
            ->orderBy('model_id')
            ->get(['role_id', 'model_id'])
            ->map(fn ($row): string => $row->role_id.':'.$row->model_id)
            ->all();

        if ($legacyRolePermissions !== $canonicalRolePermissions) {
            throw new \RuntimeException('Authorization bridge retirement blocked: permission_role does not exactly match role_has_permissions.');
        }

        if ($legacyUserRoles !== $canonicalUserRoles) {
            throw new \RuntimeException('Authorization bridge retirement blocked: role_user does not exactly match canonical User model_has_roles assignments.');
        }

        if (DB::table('model_has_permissions')->count() !== 0) {
            throw new \RuntimeException('Authorization bridge retirement blocked: direct model permissions exist.');
        }

        if (DB::table('model_has_roles')->where('model_type', '!=', User::class)->count() !== 0) {
            throw new \RuntimeException('Authorization bridge retirement blocked: unexpected model types exist in model_has_roles.');
        }

        if (DB::table('roles')->where(fn ($query) => $query->whereNull('guard_name')->orWhere('guard_name', '!=', 'web'))->count() !== 0) {
            throw new \RuntimeException('Authorization bridge retirement blocked: one or more roles use an invalid guard.');
        }

        if (DB::table('permissions')->where(fn ($query) => $query->whereNull('guard_name')->orWhere('guard_name', '!=', 'web'))->count() !== 0) {
            throw new \RuntimeException('Authorization bridge retirement blocked: one or more permissions use an invalid guard.');
        }

        Schema::drop('role_user');
        Schema::drop('permission_role');
    }

    public function down(): void
    {
        if (! Schema::hasTable('permission_role')) {
            Schema::create('permission_role', function (Blueprint $table): void {
                $table->foreignId('permission_id')->constrained()->cascadeOnDelete();
                $table->foreignId('role_id')->constrained()->cascadeOnDelete();
                $table->primary(['permission_id', 'role_id']);
            });
        }

        if (! Schema::hasTable('role_user')) {
            Schema::create('role_user', function (Blueprint $table): void {
                $table->foreignId('role_id')->constrained()->cascadeOnDelete();
                $table->foreignId('user_id')->constrained()->cascadeOnDelete();
                $table->primary(['role_id', 'user_id']);
            });
        }

        DB::table('role_has_permissions')
            ->orderBy('permission_id')
            ->orderBy('role_id')
            ->chunk(500, function ($rows): void {
                if ($rows->isEmpty()) {
                    return;
                }

                DB::table('permission_role')->insertOrIgnore(
                    $rows->map(fn ($row): array => [
                        'permission_id' => $row->permission_id,
                        'role_id' => $row->role_id,
                    ])->all(),
                );
            });

        DB::table('model_has_roles')
            ->where('model_type', User::class)
            ->orderBy('role_id')
            ->orderBy('model_id')
            ->chunk(500, function ($rows): void {
                if ($rows->isEmpty()) {
                    return;
                }

                DB::table('role_user')->insertOrIgnore(
                    $rows->map(fn ($row): array => [
                        'role_id' => $row->role_id,
                        'user_id' => $row->model_id,
                    ])->all(),
                );
            });
    }

    /** @return array<int, string> */
    private function pairs(string $table, string $first, string $second): array
    {
        return DB::table($table)
            ->orderBy($first)
            ->orderBy($second)
            ->get([$first, $second])
            ->map(fn ($row): string => $row->{$first}.':'.$row->{$second})
            ->all();
    }
};
