<?php

namespace Tests\Feature;

use App\Models\Label;
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\Models\Song;
use App\Services\PlaylistExportService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use ProPresenter\Parser\ProPlaylistReader;
use Rv\Data\Cue\CompletionActionType;
use Rv\Data\Cue\CompletionTargetType;
use Tests\Support\InspectsSlideFillMedia;
use Tests\TestCase;

final class AbspannExportTest extends TestCase
{
    use InspectsSlideFillMedia;
    use RefreshDatabase;

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

        Storage::fake('public');
    }

    public function test_abspann_is_last_item_with_keyvisual_first_then_information_slides(): void
    {
        Storage::disk('public')->put('slides/kv.jpg', 'keyvisual-image');
        Storage::disk('public')->put('slides/info1.jpg', 'info-image-1');
        Storage::disk('public')->put('slides/info2.jpg', 'info-image-2');

        $service = $this->serviceWithSong('Abspann Service', 'slides/kv.jpg');
        $this->createInfoSlide('info1.jpg', 'slides/info1.jpg', 0);
        $this->createInfoSlide('info2.jpg', 'slides/info2.jpg', 1);

        $result = app(PlaylistExportService::class)->generatePlaylist($service);
        $playlist = ProPlaylistReader::read($result['path']);
        $entries = $playlist->getEntries();
        $names = array_map(fn ($entry) => $entry->getName(), $entries);

        // Abspann is the very last playlist item and appears exactly once.
        $last = end($entries);
        $this->assertSame('Abspann', $last->getName(), 'Abspann must be the last playlist item');
        $this->assertSame(1, count(array_filter($names, fn ($n) => $n === 'Abspann')));

        // The embedded Abspann .pro has 1 (keyvisual) + 2 (information) = 3 cues.
        $abspann = $playlist->getEmbeddedSong($last->getDocumentFilename());
        $this->assertNotNull($abspann);
        $slides = $this->allParserSlides($abspann);
        $this->assertCount(3, $slides);

        // First cue is the key-visual as a BACKGROUND media action (no content element).
        $this->assertTrue($slides[0]->hasBackgroundMedia(), 'First Abspann cue must be the key-visual');
        $this->assertSame('KEY_VISUAL.jpg', $slides[0]->getBackgroundMediaUrl());
        $this->assertSame([], $this->fillMediaUrls($slides[0]), 'Key-visual cue has no content image element');
        $this->assertFalse($this->hasForegroundImageMediaAction($slides[0]), 'No foreground media action may remain');
        $this->assertArrayHasKey('KEY_VISUAL.jpg', $playlist->getEmbeddedMediaFiles());

        // Remaining cues are the information slides as content image elements, labelled by filename.
        $this->assertNotEmpty($this->fillMediaUrls($slides[1]));
        $this->assertNotContains('KEY_VISUAL.jpg', $this->fillMediaUrls($slides[1]));
        $this->assertFalse($slides[1]->hasBackgroundMedia());
        $this->assertFalse($this->hasImageMediaAction($slides[1]));
        $this->assertSame('info1.jpg', $slides[1]->getLabel());
        $this->assertNotEmpty($this->fillMediaUrls($slides[2]));
        $this->assertSame('info2.jpg', $slides[2]->getLabel());

        // The Abspann is a self-advancing loop on the live parser round-trip: every cue
        // auto-advances after 10s and the last cue jumps back to the first ("auto repeat all").
        $presentation = $abspann->getPresentation();
        $cues = $presentation->getCues();
        $this->assertCount(3, $cues);

        foreach ($cues as $cue) {
            $this->assertSame(
                CompletionActionType::COMPLETION_ACTION_TYPE_AFTER_TIME,
                $cue->getCompletionActionType(),
                'Every Abspann cue must auto-advance after a fixed time',
            );
            $this->assertSame(10.0, $cue->getCompletionTime(), 'Auto-advance must fire after 10 seconds');
        }

        // Leading cues step to the next slide; the final cue loops back to the first.
        $this->assertSame(CompletionTargetType::COMPLETION_TARGET_TYPE_NEXT, $cues[0]->getCompletionTargetType());
        $this->assertSame(CompletionTargetType::COMPLETION_TARGET_TYPE_NEXT, $cues[1]->getCompletionTargetType());
        $this->assertSame(
            CompletionTargetType::COMPLETION_TARGET_TYPE_FIRST,
            $cues[count($cues) - 1]->getCompletionTargetType(),
            'The final Abspann cue must loop back to the first cue',
        );

        // The presentation carries the Dissolve transition (soft cross-fade between slides).
        $this->assertTrue($presentation->hasTransition(), 'Abspann must use a slide transition');
        $effect = $presentation->getTransition()->getEffect();
        $this->assertNotNull($effect);
        $this->assertSame('EC52A828-AD85-4602-B70C-1DEE7C904DB6', $effect->getRenderId());
        $this->assertSame('Dissolve', $effect->getName());
        $this->assertSame('Dissolves', $effect->getCategory());

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

    /**
     * Auto-advance (10s), loop-to-first and the Dissolve transition survive the disk
     * round-trip and are asserted in
     * test_abspann_is_last_item_with_keyvisual_first_then_information_slides. This test
     * stays focused on the per-cue Abspann macro assignment.
     */
    public function test_abspann_cues_carry_configured_abspann_macro(): void
    {
        Storage::disk('public')->put('slides/kv.jpg', 'keyvisual-image');
        Storage::disk('public')->put('slides/info1.jpg', 'info-image-1');

        $macro = Macro::factory()->create(['name' => 'Abspann Macro']);
        MacroAssignment::create([
            'part_type' => 'abspann',
            'macro_id' => $macro->id,
            'position' => 'all_slides',
            'order' => 0,
        ]);

        $service = $this->serviceWithSong('Abspann Macro Service', 'slides/kv.jpg');
        $this->createInfoSlide('info1.jpg', 'slides/info1.jpg', 0);

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

        $abspann = $playlist->getEmbeddedSong($last->getDocumentFilename());
        $slides = $this->allParserSlides($abspann);
        $this->assertCount(2, $slides);

        foreach ($slides as $slide) {
            $this->assertTrue($slide->hasMacro(), 'Every Abspann cue must carry the abspann macro');
            $this->assertSame('Abspann Macro', $slide->getMacroName());
        }

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

    public function test_no_abspann_when_no_keyvisual_and_no_information_slides(): void
    {
        // A service with a song but no key-visual and no information slides.
        $service = $this->serviceWithSong('Ohne Abspann', null);

        $result = app(PlaylistExportService::class)->generatePlaylist($service);
        $playlist = ProPlaylistReader::read($result['path']);
        $names = array_map(fn ($entry) => $entry->getName(), $playlist->getEntries());

        $this->assertNotContains('Abspann', $names, 'No Abspann item without key-visual and information slides');

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

    private function serviceWithSong(string $title, ?string $keyVisual): Service
    {
        $service = Service::factory()->create([
            'title' => $title,
            'date' => now(),
            'key_visual_filename' => $keyVisual,
        ]);

        $song = $this->createSongWithContent($title.' Lied');
        $serviceSong = ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song->id,
            'cts_song_name' => $title.' Lied',
            'order' => 1,
        ]);
        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => $title.' Lied',
            'service_song_id' => $serviceSong->id,
            'sort_order' => 1,
            'is_before_event' => false,
        ]);

        return $service;
    }

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

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

        $label = Label::firstOrCreate(
            ['name' => 'Verse 1 - '.$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);
    }
}
