<?php

namespace Tests\Feature;

use App\Models\ExportProFile;
use App\Models\Label;
use App\Models\Service;
use App\Models\ServiceSong;
use App\Models\Song;
use App\Services\PlaylistExportService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use ProPresenter\Parser\ProFileGenerator;
use ProPresenter\Parser\ProPlaylistReader;
use Tests\TestCase;
use ZipArchive;

/**
 * Regression cover for the "empty keyword/prefix show" bug: a .probundle uploaded
 * as a keyword/prefix/postfix export is stored by Laravel under a MIME-guessed
 * .zip extension. Branching on the stored path then misses the probundle branch
 * and embeds the raw ZIP bytes as a broken .pro (ProPresenter shows 0 slides).
 *
 * These tests use REAL, slide-bearing .pro bytes (generated by the parser) wrapped
 * in a real ZIP — dummy string bytes are exactly what hid the original bug.
 */
final class ExportProFileProbundleEmbedTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        Storage::fake('local');
        Storage::fake('public');
    }

    /** Build real, parseable .pro bytes with the given groups/arrangements. */
    private function realProBytes(string $name, array $groups, array $arrangements): string
    {
        $tmp = tempnam(sys_get_temp_dir(), 'fixture-pro-').'.pro';
        ProFileGenerator::generateAndWrite($tmp, $name, $groups, $arrangements);
        $bytes = file_get_contents($tmp);
        @unlink($tmp);

        return $bytes;
    }

    /** Wrap real .pro bytes (+ optional media) into a real .probundle ZIP, return the archive bytes. */
    private function probundleBytes(string $proBytes, array $mediaFiles = []): string
    {
        $tmp = tempnam(sys_get_temp_dir(), 'fixture-bundle-').'.probundle';
        $zip = new ZipArchive;
        $zip->open($tmp, ZipArchive::CREATE | ZipArchive::OVERWRITE);
        $zip->addFromString('presentation.pro', $proBytes);
        foreach ($mediaFiles as $name => $content) {
            $zip->addFromString($name, $content);
        }
        $zip->close();

        $bytes = file_get_contents($tmp);
        @unlink($tmp);

        return $bytes;
    }

    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 - '.$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);
    }

    public function test_probundle_mis_stored_as_zip_wird_mit_folien_injiziert(): void
    {
        $service = Service::factory()->create(['title' => 'Bundle Service', 'date' => now()]);

        $song = $this->createSongWithContent('Lied');
        ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song->id,
            'cts_song_name' => 'Lied',
            'order' => 1,
        ]);

        // Real inner .pro with a group of 2 content slides.
        $proBytes = $this->realProBytes('intro', [
            ['name' => 'Verse', 'color' => [0.5, 0.5, 0.5, 1.0], 'slides' => [
                ['text' => 'Folie eins'],
                ['text' => 'Folie zwei'],
            ]],
        ], [['name' => 'normal', 'groupNames' => ['Verse']]]);
        $bundleBytes = $this->probundleBytes($proBytes, ['hintergrund.jpg' => 'jpg-bytes']);

        // Reproduce the mis-stored state: original name .probundle, stored path .zip.
        $storedPath = 'export-pro-files/prefix/'.\Illuminate\Support\Str::random(40).'.zip';
        Storage::disk('local')->put($storedPath, $bundleBytes);

        ExportProFile::create([
            'type' => 'prefix',
            'original_name' => 'intro.probundle',
            'stored_path' => $storedPath,
            'order' => 1,
        ]);

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

        $entryNames = array_map(fn ($e) => $e->getName(), $playlist->getEntries());
        $this->assertContains('intro', $entryNames, 'Prefix bundle presentation must be injected');

        $embeddedSong = $playlist->getEmbeddedSong('PREFIX_1_intro.pro');
        $this->assertNotNull($embeddedSong, 'Inner .pro must be embedded, not the raw ZIP');
        $this->assertCount(2, $this->allParserSlides($embeddedSong), 'Inner .pro must keep its 2 content slides');

        $embeddedFiles = $playlist->getEmbeddedFiles();
        $this->assertArrayHasKey('hintergrund.jpg', $embeddedFiles, 'Bundle media must be embedded');
        $this->assertSame('jpg-bytes', $embeddedFiles['hintergrund.jpg']);

        $this->assertSame([], $result['warnings'], 'A slide-bearing bundle must not be skipped');

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

    public function test_probundle_ohne_folien_wird_uebersprungen_mit_warnung(): void
    {
        $service = Service::factory()->create(['title' => 'Leer Service', 'date' => now()]);

        $song = $this->createSongWithContent('Lied');
        ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song->id,
            'cts_song_name' => 'Lied',
            'order' => 1,
        ]);

        // Real inner .pro that parses fine but has a group with ZERO slides.
        $proBytes = $this->realProBytes('leer', [
            ['name' => 'Verse', 'color' => [0.5, 0.5, 0.5, 1.0], 'slides' => []],
        ], [['name' => 'normal', 'groupNames' => ['Verse']]]);
        $bundleBytes = $this->probundleBytes($proBytes);

        $storedPath = 'export-pro-files/prefix/'.\Illuminate\Support\Str::random(40).'.zip';
        Storage::disk('local')->put($storedPath, $bundleBytes);

        ExportProFile::create([
            'type' => 'prefix',
            'original_name' => 'leer.probundle',
            'stored_path' => $storedPath,
            'order' => 1,
        ]);

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

        $entryNames = array_map(fn ($e) => $e->getName(), $playlist->getEntries());
        $this->assertNotContains('leer', $entryNames, 'A contentless bundle must not be injected');
        $this->assertNull($playlist->getEmbeddedSong('PREFIX_1_leer.pro'), 'A contentless bundle must not be embedded');

        $this->assertContains(
            "Export-Datei 'leer.probundle' übersprungen: keine Folien gefunden.",
            $result['warnings'],
            'A contentless bundle must be skipped with a German warning',
        );

        // The valid song is still exported.
        $this->assertContains('Lied', $entryNames);

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