<?php

namespace Tests\Feature;

use App\Models\Label;
use App\Models\Service;
use App\Models\ServiceAgendaItem;
use App\Models\ServiceSong;
use App\Models\Song;
use App\Models\SongArrangement;
use App\Services\PlaylistExportService;
use App\Services\ProBundleExportService;
use App\Services\ProExportService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use ProPresenter\Parser\ProBundleReader;
use ProPresenter\Parser\ProPlaylistReader;
use Tests\TestCase;

/**
 * Export must honour the per-service selected arrangement:
 *  - .pro file: selected_arrangement (protobuf field 10) set to the chosen arrangement by name.
 *  - .proplaylist: presentation item arrangement_name (field 5) set to the chosen arrangement name.
 * ALL arrangements stay in the .pro; only the pre-selection changes.
 */
final class ArrangementSelectionExportTest extends TestCase
{
    use RefreshDatabase;

    /**
     * Song with three sections and two arrangements whose section sets DIFFER:
     *   - "Normal" (default): Strophe → Refrain
     *   - "Bene"   (chosen):  Refrain → Bridge
     * Union of both arrangements = {Strophe, Refrain, Bridge}.
     *
     * @return array{0: Song, 1: SongArrangement, 2: SongArrangement}
     */
    private function createSongWithTwoArrangements(string $title = 'Union Test'): array
    {
        $song = Song::create([
            'title' => $title,
            'ccli_id' => (string) fake()->unique()->numerify('#####'),
            'author' => 'Test Author',
            'copyright_text' => 'Test Publisher',
        ]);

        $verseLabel = Label::firstOrCreate(['name' => 'Strophe 1 - '.$title], ['color' => '#2196F3']);
        $chorusLabel = Label::firstOrCreate(['name' => 'Refrain - '.$title], ['color' => '#F44336']);
        $bridgeLabel = Label::firstOrCreate(['name' => 'Bridge - '.$title], ['color' => '#4CAF50']);

        $verse = $song->sections()->create(['label_id' => $verseLabel->id, 'order' => 0]);
        $verse->slides()->create(['order' => 0, 'text_content' => 'Strophe Zeile']);

        $chorus = $song->sections()->create(['label_id' => $chorusLabel->id, 'order' => 1]);
        $chorus->slides()->create(['order' => 0, 'text_content' => 'Refrain Zeile']);

        $bridge = $song->sections()->create(['label_id' => $bridgeLabel->id, 'order' => 2]);
        $bridge->slides()->create(['order' => 0, 'text_content' => 'Bridge Zeile']);

        $normal = $song->arrangements()->create(['name' => 'Normal', 'is_default' => true]);
        $normal->arrangementSections()->create(['song_section_id' => $verse->id, 'order' => 0]);
        $normal->arrangementSections()->create(['song_section_id' => $chorus->id, 'order' => 1]);

        $bene = $song->arrangements()->create(['name' => 'Bene', 'is_default' => false]);
        $bene->arrangementSections()->create(['song_section_id' => $chorus->id, 'order' => 0]);
        $bene->arrangementSections()->create(['song_section_id' => $bridge->id, 'order' => 1]);

        return [$song->fresh(), $normal, $bene];
    }

    public function test_generate_parser_song_mit_gewaehltem_arrangement_setzt_selected_und_behaelt_alle(): void
    {
        [$song, $normal, $bene] = $this->createSongWithTwoArrangements();

        $parserSong = app(ProExportService::class)->generateParserSong($song, null, $bene);

        $beneParser = $parserSong->getArrangementByName('Bene');
        $this->assertNotNull($beneParser, 'Gewähltes Arrangement "Bene" muss im .pro vorhanden sein');
        $this->assertSame(
            $beneParser->getUuid(),
            $parserSong->getSelectedArrangementUuid(),
            'selected_arrangement muss auf das gewählte Arrangement "Bene" zeigen',
        );

        $names = array_map(fn ($arr) => $arr->getName(), $parserSong->getArrangements());
        $this->assertContains('Normal', $names, 'Default-Arrangement muss erhalten bleiben');
        $this->assertContains('Bene', $names, 'Gewähltes Arrangement muss erhalten bleiben');
    }

    public function test_generate_parser_song_ohne_auswahl_nutzt_default_arrangement(): void
    {
        [$song, $normal] = $this->createSongWithTwoArrangements();

        $parserSong = app(ProExportService::class)->generateParserSong($song, null, null);

        $normalParser = $parserSong->getArrangementByName('Normal');
        $this->assertNotNull($normalParser);
        $this->assertSame(
            $normalParser->getUuid(),
            $parserSong->getSelectedArrangementUuid(),
            'Ohne explizite Auswahl muss das Default-Arrangement vorausgewählt sein',
        );
    }

    public function test_union_der_sektionen_laesst_jedes_arrangement_gruppen_rendern(): void
    {
        [$song, $normal, $bene] = $this->createSongWithTwoArrangements();

        // Auswahl "Bene" (Refrain, Bridge) — ohne Union würde "Normal" (Strophe) leer/unvollständig rendern.
        $parserSong = app(ProExportService::class)->generateParserSong($song, null, $bene);

        // Alle drei Sektionen (Strophe, Refrain, Bridge) als Gruppen emittiert.
        $this->assertCount(3, $parserSong->getGroups(), 'Union muss alle referenzierten Sektionen als Gruppen emittieren');

        foreach ($parserSong->getArrangements() as $arrangement) {
            $resolved = $parserSong->getGroupsForArrangement($arrangement);
            $this->assertCount(
                2,
                $resolved,
                "Arrangement '{$arrangement->getName()}' muss alle 2 Gruppen-Referenzen auflösen können",
            );
        }
    }

    public function test_playlist_export_setzt_arrangement_name_auf_gewaehltes_arrangement(): void
    {
        Storage::fake('public');

        $service = Service::factory()->create(['title' => 'Arrangement Service', 'date' => now()]);
        [$song, $normal, $bene] = $this->createSongWithTwoArrangements('Playlist Song');

        $serviceSong = ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song->id,
            'song_arrangement_id' => $bene->id,
            'cts_song_name' => 'Playlist Song',
            'order' => 1,
        ]);
        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Playlist Song',
            'service_song_id' => $serviceSong->id,
            'sort_order' => 1,
            'is_before_event' => false,
        ]);

        $result = app(PlaylistExportService::class)->generatePlaylist($service);
        $playlist = ProPlaylistReader::read($result['path']);

        $songEntry = null;
        foreach ($playlist->getEntries() as $entry) {
            if ($entry->getName() === 'Playlist Song') {
                $songEntry = $entry;
                break;
            }
        }

        $this->assertNotNull($songEntry, 'Song-Eintrag muss in der Playlist vorhanden sein');
        $this->assertSame('Bene', $songEntry->getArrangementName(), 'arrangement_name muss das gewählte Arrangement sein');

        // Und der eingebettete .pro trägt selected_arrangement = "Bene".
        $embedded = $playlist->getEmbeddedSong('Playlist Song.pro');
        $this->assertNotNull($embedded);
        $this->assertSame(
            $embedded->getArrangementByName('Bene')->getUuid(),
            $embedded->getSelectedArrangementUuid(),
        );

        $this->cleanupTempDir($result['temp_dir']);
    }

    public function test_agenda_bundle_export_selektiert_gewaehltes_arrangement(): void
    {
        Storage::fake('public');

        $service = Service::factory()->create(['title' => 'Bundle Service', 'date' => now()]);
        [$song, $normal, $bene] = $this->createSongWithTwoArrangements('Bundle Song');

        $serviceSong = ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song->id,
            'song_arrangement_id' => $bene->id,
            'cts_song_name' => 'Bundle Song',
            'order' => 1,
        ]);
        $agendaItem = ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Bundle Song',
            'service_song_id' => $serviceSong->id,
            'sort_order' => 1,
            'is_before_event' => false,
        ]);

        $bundlePath = app(ProBundleExportService::class)->generateAgendaItemBundle($agendaItem);
        $bundleSong = ProBundleReader::read($bundlePath)->getSong();

        $beneParser = $bundleSong->getArrangementByName('Bene');
        $this->assertNotNull($beneParser, 'Bundle .pro muss das Arrangement "Bene" enthalten');
        $this->assertSame(
            $beneParser->getUuid(),
            $bundleSong->getSelectedArrangementUuid(),
            'Bundle .pro selected_arrangement muss "Bene" sein',
        );
        $this->assertNotNull($bundleSong->getArrangementByName('Normal'), 'Alle Arrangements bleiben erhalten');

        @unlink($bundlePath);
    }

    private function cleanupTempDir(string $dir): void
    {
        if (! is_dir($dir)) {
            return;
        }

        $items = scandir($dir);
        if ($items === false) {
            return;
        }

        foreach ($items as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }

            $path = $dir.'/'.$item;
            is_dir($path) ? $this->cleanupTempDir($path) : unlink($path);
        }

        rmdir($dir);
    }
}
