<?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
    {
        Schema::create('song_sections', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('song_id')->constrained()->cascadeOnDelete();
            $table->foreignId('label_id')->constrained('labels')->restrictOnDelete();
            $table->unsignedInteger('order')->default(0);
            $table->timestamps();

            $table->unique(['song_id', 'label_id']);
        });

        Schema::table('song_slides', function (Blueprint $table): void {
            $table->foreignId('song_section_id')->nullable()->after('id')->constrained('song_sections')->cascadeOnDelete();
        });

        Schema::table('song_arrangement_labels', function (Blueprint $table): void {
            $table->foreignId('song_section_id')->nullable()->after('label_id')->constrained('song_sections')->cascadeOnDelete();
        });

        $this->backfillSections();
        $this->backfillSlides();
        $this->backfillArrangementSections();

        DB::table('song_slides')->whereNull('song_section_id')->delete();

        if (DB::getDriverName() === 'sqlite') {
            $this->finalizeSqliteTables();

            return;
        }

        if (DB::getDriverName() !== 'sqlite') {
            Schema::table('song_slides', function (Blueprint $table): void {
                $table->dropForeign(['label_id']);
            });
        }

        Schema::table('song_slides', function (Blueprint $table): void {
            $table->dropColumn('label_id');
        });

        Schema::table('song_slides', function (Blueprint $table): void {
            $table->foreignId('song_section_id')->nullable(false)->change();
        });

        if (DB::getDriverName() !== 'sqlite') {
            Schema::table('song_arrangement_labels', function (Blueprint $table): void {
                $table->dropForeign(['label_id']);
            });
        }

        Schema::table('song_arrangement_labels', function (Blueprint $table): void {
            $table->dropColumn('label_id');
        });

        Schema::table('song_arrangement_labels', function (Blueprint $table): void {
            $table->foreignId('song_section_id')->nullable(false)->change();
        });
    }

    public function down(): void
    {
        throw new \RuntimeException('Destruktive Migration: kein Rollback. Backup einspielen.');
    }

    private function backfillSections(): void
    {
        DB::table('song_arrangements')
            ->select('song_arrangements.song_id')
            ->join('song_arrangement_labels', 'song_arrangement_labels.song_arrangement_id', '=', 'song_arrangements.id')
            ->whereNotNull('song_arrangement_labels.label_id')
            ->distinct()
            ->orderBy('song_arrangements.song_id')
            ->chunk(100, function ($songs): void {
                foreach ($songs as $song) {
                    $this->backfillSectionsForSong((int) $song->song_id);
                }
            });
    }

    private function backfillSectionsForSong(int $songId): void
    {
        $labelIds = DB::table('song_arrangement_labels')
            ->join('song_arrangements', 'song_arrangements.id', '=', 'song_arrangement_labels.song_arrangement_id')
            ->where('song_arrangements.song_id', $songId)
            ->whereNotNull('song_arrangement_labels.label_id')
            ->distinct()
            ->orderBy('song_arrangement_labels.label_id')
            ->pluck('song_arrangement_labels.label_id')
            ->map(fn ($labelId): int => (int) $labelId)
            ->all();

        if ($labelIds === []) {
            return;
        }

        $preferredArrangementId = DB::table('song_arrangements')
            ->where('song_id', $songId)
            ->orderByDesc('is_default')
            ->orderByRaw("lower(name) = 'normal' desc")
            ->orderBy('id')
            ->value('id');

        $preferredOrders = collect();

        if ($preferredArrangementId !== null) {
            $preferredOrders = DB::table('song_arrangement_labels')
                ->select('label_id', DB::raw('min("order") as section_order'))
                ->where('song_arrangement_id', $preferredArrangementId)
                ->whereIn('label_id', $labelIds)
                ->groupBy('label_id')
                ->pluck('section_order', 'label_id');
        }

        $fallbackOrder = ((int) $preferredOrders->max()) + 1;
        $now = now();
        $rows = [];

        foreach ($labelIds as $labelId) {
            $rows[] = [
                'song_id' => $songId,
                'label_id' => $labelId,
                'order' => $preferredOrders->has($labelId) ? (int) $preferredOrders->get($labelId) : $fallbackOrder++,
                'created_at' => $now,
                'updated_at' => $now,
            ];
        }

        DB::table('song_sections')->insertOrIgnore($rows);
    }

    private function backfillSlides(): void
    {
        DB::table('song_sections')
            ->select(['id', 'label_id'])
            ->orderBy('id')
            ->chunkById(100, function ($sections): void {
                foreach ($sections as $section) {
                    $this->backfillSlidesForSection((int) $section->id, (int) $section->label_id);
                }
            });
    }

    private function backfillSlidesForSection(int $sectionId, int $labelId): void
    {
        DB::table('song_slides')
            ->where('label_id', $labelId)
            ->whereNull('song_section_id')
            ->orderBy('order')
            ->chunkById(200, function ($slides) use ($sectionId): void {
                $now = now();
                $rows = [];

                foreach ($slides as $slide) {
                    $rows[] = [
                        'label_id' => $slide->label_id,
                        'song_section_id' => $sectionId,
                        'order' => $slide->order,
                        'text_content' => $slide->text_content,
                        'text_content_translated' => $slide->text_content_translated,
                        'notes' => $slide->notes,
                        'created_at' => $now,
                        'updated_at' => $now,
                    ];
                }

                if ($rows !== []) {
                    DB::table('song_slides')->insert($rows);
                }
            });
    }

    private function backfillArrangementSections(): void
    {
        DB::table('song_arrangement_labels')
            ->select([
                'song_arrangement_labels.id',
                'song_arrangement_labels.label_id',
                'song_arrangements.song_id',
            ])
            ->join('song_arrangements', 'song_arrangements.id', '=', 'song_arrangement_labels.song_arrangement_id')
            ->whereNotNull('song_arrangement_labels.label_id')
            ->orderBy('song_arrangement_labels.id')
            ->chunkById(500, function ($arrangementLabels): void {
                foreach ($arrangementLabels as $arrangementLabel) {
                    $sectionId = DB::table('song_sections')
                        ->where('song_id', $arrangementLabel->song_id)
                        ->where('label_id', $arrangementLabel->label_id)
                        ->value('id');

                    if ($sectionId !== null) {
                        DB::table('song_arrangement_labels')
                            ->where('id', $arrangementLabel->id)
                            ->update(['song_section_id' => $sectionId]);
                    }
                }
            }, 'song_arrangement_labels.id', 'id');
    }

    private function finalizeSqliteTables(): void
    {
        Schema::disableForeignKeyConstraints();

        Schema::create('song_slides_new', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('song_section_id')->constrained('song_sections')->cascadeOnDelete();
            $table->unsignedInteger('order');
            $table->text('text_content');
            $table->text('text_content_translated')->nullable();
            $table->text('notes')->nullable();
            $table->timestamps();
        });

        DB::statement('insert into song_slides_new (id, song_section_id, "order", text_content, text_content_translated, notes, created_at, updated_at) select id, song_section_id, "order", text_content, text_content_translated, notes, created_at, updated_at from song_slides');

        Schema::drop('song_slides');
        Schema::rename('song_slides_new', 'song_slides');

        Schema::create('song_arrangement_labels_new', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('song_arrangement_id')->constrained()->cascadeOnDelete();
            $table->foreignId('song_section_id')->constrained('song_sections')->cascadeOnDelete();
            $table->unsignedInteger('order');
            $table->timestamps();

            $table->index(['song_arrangement_id', 'order']);
        });

        DB::statement('insert into song_arrangement_labels_new (id, song_arrangement_id, song_section_id, "order", created_at, updated_at) select id, song_arrangement_id, song_section_id, "order", created_at, updated_at from song_arrangement_labels');

        Schema::drop('song_arrangement_labels');
        Schema::rename('song_arrangement_labels_new', 'song_arrangement_labels');

        Schema::enableForeignKeyConstraints();
    }
};
