<?php

namespace Tests\Feature;

use App\Models\Macro;
use App\Models\MacroAssignment;
use App\Models\Service;
use App\Models\ServiceAgendaItem;
use App\Models\ServiceSong;
use App\Models\Slide;
use App\Services\PlaylistExportService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use ProPresenter\Parser\ProPlaylistReader;
use Tests\TestCase;

final class PlaylistSermonMacroTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();

        Storage::fake('public');
    }

    /**
     * Agenda-based sermon export must attach position macros (first_slide /
     * last_slide / all_slides) for part_type='sermon' onto the sermon CONTENT
     * slides. The parser stores only one macro per slide (the app keeps
     * $macros[0]); the MOST SPECIFIC position wins (by_label > first/last >
     * all_slides, ties break by `order`), so first/last override all_slides on
     * the first/last slide while the middle slide keeps all_slides.
     */
    public function test_playlist_sermon_slides_carry_position_macros(): void
    {
        Storage::disk('public')->put('slides/sermon-1.jpg', 'sermon-one');
        Storage::disk('public')->put('slides/sermon-2.jpg', 'sermon-two');
        Storage::disk('public')->put('slides/sermon-3.jpg', 'sermon-three');

        $macroFirst = Macro::factory()->create(['name' => 'Predigt Erste']);
        $macroLast = Macro::factory()->create(['name' => 'Predigt Letzte']);
        $macroAll = Macro::factory()->create(['name' => 'Predigt Alle']);

        // Most specific position wins when several assignments match one slide
        // (first/last beat all_slides regardless of `order`); see MacroResolutionService.
        MacroAssignment::create(['part_type' => 'sermon', 'macro_id' => $macroFirst->id, 'position' => 'first_slide', 'order' => 0]);
        MacroAssignment::create(['part_type' => 'sermon', 'macro_id' => $macroLast->id, 'position' => 'last_slide', 'order' => 1]);
        MacroAssignment::create(['part_type' => 'sermon', 'macro_id' => $macroAll->id, 'position' => 'all_slides', 'order' => 2]);

        $service = Service::factory()->create([
            'title' => 'Predigt Macro Service',
            'date' => now(),
        ]);

        // A song with real (DB-driven) content guarantees the export always has at
        // least one content presentation, so it never hits the "Keine Songs mit
        // Inhalt zum Exportieren gefunden" guard regardless of test ordering or
        // slide-file resolution. `responsible => []` pins the agenda items so the
        // factory's random responsible data cannot inject stray name-tag slides.
        $song = $this->createSongWithContent('Startlied');
        $serviceSong = ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song->id,
            'cts_song_name' => 'Startlied',
            'order' => 1,
        ]);
        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Startlied',
            'service_song_id' => $serviceSong->id,
            'sort_order' => 1,
            'is_before_event' => false,
            'responsible' => [],
        ]);

        $sermonItem = ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Predigt',
            'service_song_id' => null,
            'sort_order' => 2,
            'is_before_event' => false,
            'responsible' => [],
        ]);
        $this->createSermonSlide($service, $sermonItem, 'sermon-1.jpg', 0);
        $this->createSermonSlide($service, $sermonItem, 'sermon-2.jpg', 1);
        $this->createSermonSlide($service, $sermonItem, 'sermon-3.jpg', 2);

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

        $sermon = $playlist->getEmbeddedSong('Predigt.pro');
        $this->assertNotNull($sermon, 'Embedded sermon .pro missing');

        $slides = $this->allParserSlides($sermon);
        $this->assertCount(3, $slides);

        foreach ($slides as $slide) {
            $this->assertTrue($slide->hasMacro(), 'Every sermon slide must carry a macro (all_slides guarantees at least one)');
        }

        // First slide: first_slide wins over all_slides (more specific position).
        $this->assertSame('Predigt Erste', $slides[0]->getMacroName());
        $this->assertSame($macroFirst->uuid, $slides[0]->getMacroUuid());

        // Middle slide: only all_slides matches.
        $this->assertSame('Predigt Alle', $slides[1]->getMacroName());

        // Last slide: last_slide wins over all_slides (more specific position).
        $this->assertSame('Predigt Letzte', $slides[2]->getMacroName());
        $this->assertSame($macroLast->uuid, $slides[2]->getMacroUuid());

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

    /**
     * The legacy (no-agenda) export path emits Information, Moderation and Sermon
     * presentations with their own explicit part_type. Each must attach the
     * all_slides macro registered for that part_type onto every slide.
     */
    public function test_legacy_information_moderation_sermon_slides_carry_macros(): void
    {
        Storage::disk('public')->put('slides/info-1.jpg', 'info-one');
        Storage::disk('public')->put('slides/info-2.jpg', 'info-two');
        Storage::disk('public')->put('slides/mod-1.jpg', 'mod-one');
        Storage::disk('public')->put('slides/mod-2.jpg', 'mod-two');
        Storage::disk('public')->put('slides/sermon-1.jpg', 'sermon-one');
        Storage::disk('public')->put('slides/sermon-2.jpg', 'sermon-two');

        $infoMacro = Macro::factory()->create(['name' => 'Info Macro']);
        $modMacro = Macro::factory()->create(['name' => 'Moderation Macro']);
        $sermonMacro = Macro::factory()->create(['name' => 'Predigt Macro']);

        MacroAssignment::create(['part_type' => 'information', 'macro_id' => $infoMacro->id, 'position' => 'all_slides', 'order' => 0]);
        MacroAssignment::create(['part_type' => 'moderation', 'macro_id' => $modMacro->id, 'position' => 'all_slides', 'order' => 0]);
        MacroAssignment::create(['part_type' => 'sermon', 'macro_id' => $sermonMacro->id, 'position' => 'all_slides', 'order' => 0]);

        // No agenda items => the export uses the legacy block-ordered path.
        $service = Service::factory()->create([
            'title' => 'Legacy Macro Service',
            'date' => now(),
        ]);

        $this->createInfoSlide('info-1.jpg', 0);
        $this->createInfoSlide('info-2.jpg', 1);
        $this->createServiceSlide($service, 'moderation', 'mod-1.jpg', 0);
        $this->createServiceSlide($service, 'moderation', 'mod-2.jpg', 1);
        $this->createServiceSlide($service, 'sermon', 'sermon-1.jpg', 0);
        $this->createServiceSlide($service, 'sermon', 'sermon-2.jpg', 1);

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

        $this->assertAllSlidesHaveMacro($playlist->getEmbeddedSong('Informationen.pro'), 'Info Macro');
        $this->assertAllSlidesHaveMacro($playlist->getEmbeddedSong('Moderation.pro'), 'Moderation Macro');
        $this->assertAllSlidesHaveMacro($playlist->getEmbeddedSong('Predigt.pro'), 'Predigt Macro');

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

    private function assertAllSlidesHaveMacro(?\ProPresenter\Parser\Song $song, string $macroName): void
    {
        $this->assertNotNull($song, "Embedded .pro for macro '{$macroName}' missing");

        $slides = $this->allParserSlides($song);
        $this->assertCount(2, $slides);

        foreach ($slides as $slide) {
            $this->assertTrue($slide->hasMacro(), "Slide for '{$macroName}' must carry a macro");
            $this->assertSame($macroName, $slide->getMacroName());
        }
    }

    private function createSermonSlide(Service $service, ServiceAgendaItem $agendaItem, string $filename, int $sortOrder): Slide
    {
        return Slide::factory()->create([
            'service_id' => $service->id,
            'service_agenda_item_id' => $agendaItem->id,
            'type' => 'sermon',
            'original_filename' => $filename,
            'stored_filename' => 'slides/'.$filename,
            'sort_order' => $sortOrder,
        ]);
    }

    private function createInfoSlide(string $filename, int $sortOrder): Slide
    {
        return Slide::factory()->create([
            'service_id' => null,
            'type' => 'information',
            'original_filename' => $filename,
            'stored_filename' => 'slides/'.$filename,
            'sort_order' => $sortOrder,
            'expire_date' => null,
            'uploaded_at' => now()->subDay(),
        ]);
    }

    private function createServiceSlide(Service $service, string $type, string $filename, int $sortOrder): Slide
    {
        return Slide::factory()->create([
            'service_id' => $service->id,
            'type' => $type,
            'original_filename' => $filename,
            'stored_filename' => 'slides/'.$filename,
            'sort_order' => $sortOrder,
        ]);
    }

    private function createSongWithContent(string $title): \App\Models\Song
    {
        $song = \App\Models\Song::create([
            'title' => $title,
            'ccli_id' => fake()->unique()->numerify('#####'),
            'author' => 'Test Author',
            'copyright_text' => 'Test Publisher',
        ]);

        $label = \App\Models\Label::firstOrCreate(
            ['name' => 'Verse - '.$title],
            ['color' => '#2196F3'],
        );
        $section = $song->sections()->create(['label_id' => $label->id, 'order' => 0]);
        $section->slides()->create(['order' => 0, 'text_content' => 'Erste Zeile']);

        $arrangement = $song->arrangements()->create(['name' => 'normal', 'is_default' => true]);
        $arrangement->arrangementSections()->create(['song_section_id' => $section->id, 'order' => 0]);

        return $song;
    }

    private function allParserSlides(\ProPresenter\Parser\Song $parserSong): array
    {
        $slides = [];

        foreach ($parserSong->getGroups() as $group) {
            foreach ($parserSong->getSlidesForGroup($group) as $slide) {
                $slides[] = $slide;
            }
        }

        return $slides;
    }

    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);
    }
}
