<?php

namespace Tests\Feature;

use App\Models\Label;
use App\Models\Service;
use App\Models\ServiceAgendaItem;
use App\Models\ServiceSong;
use App\Models\Setting;
use App\Models\Slide;
use App\Models\Song;
use App\Models\User;
use App\Services\PlaylistExportService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

final class PlaylistExportTest extends TestCase
{
    use RefreshDatabase;

    private User $user;

    protected function setUp(): void
    {
        parent::setUp();
        $this->user = User::factory()->create();
    }

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

        $verse = Label::firstOrCreate(
            ['name' => 'Verse 1 - '.$title],
            ['color' => '#2196F3'],
        );
        $verse->songSlides()->create(['order' => 0, 'text_content' => 'First line of verse']);

        $chorus = Label::firstOrCreate(
            ['name' => 'Chorus - '.$title],
            ['color' => '#F44336'],
        );
        $chorus->songSlides()->create(['order' => 0, 'text_content' => 'Chorus text']);

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

        return $song;
    }

    private function createSlideFile(string $storedFilename): void
    {
        Storage::fake('public');
        $image = imagecreatetruecolor(1920, 1080);
        ob_start();
        imagejpeg($image);
        $contents = ob_get_clean();
        imagedestroy($image);
        Storage::disk('public')->put('slides/'.$storedFilename, $contents);
    }

    private function createSlide(array $attributes): Slide
    {
        return Slide::create(array_merge([
            'thumbnail_filename' => 'thumb_'.($attributes['stored_filename'] ?? 'default.jpg'),
            'uploaded_at' => now()->subDay(),
        ], $attributes));
    }

    private function createTestableExportService(): PlaylistExportService
    {
        return new class extends PlaylistExportService
        {
            protected function writeProFile(string $path, string $name, array $groups, array $arrangements): void
            {
                file_put_contents($path, 'mock-pro-file:'.$name);
            }

            protected function writePlaylistFile(string $path, string $name, array $items, array $embeddedFiles): void
            {
                $content = 'mock-playlist:'.$name;
                foreach ($items as $item) {
                    $content .= "\n".$item['name'];
                }
                file_put_contents($path, $content);
            }
        };
    }

    public function test_download_finalisierter_service_mit_songs_gibt_proplaylist_datei(): void
    {
        $service = Service::factory()->create(['finalized_at' => now(), 'title' => 'Gottesdienst']);
        $song = $this->createSongWithContent('Lobe den Herrn');

        ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song->id,
            'cts_song_name' => 'Lobe den Herrn',
            'order' => 1,
        ]);

        $response = $this->actingAs($this->user)->get(route('services.download', $service));

        $response->assertOk();
        $contentType = $response->headers->get('content-type');
        $this->assertTrue(
            in_array($contentType, ['application/octet-stream', 'application/zip']),
            "Expected octet-stream or zip content-type, got: {$contentType}"
        );
        $disposition = $response->headers->get('content-disposition');
        $this->assertNotNull($disposition);
        $this->assertTrue(
            str_contains($disposition, '.proplaylist'),
            "Expected .proplaylist in content-disposition, got: {$disposition}"
        );
    }

    public function test_download_nicht_finalisierter_service_gibt_403(): void
    {
        $service = Service::factory()->create(['finalized_at' => null]);

        $response = $this->actingAs($this->user)->getJson(route('services.download', $service));

        $response->assertForbidden();
    }

    public function test_download_finalisierter_service_ohne_songs_gibt_422(): void
    {
        $service = Service::factory()->create(['finalized_at' => now()]);

        $response = $this->actingAs($this->user)->getJson(route('services.download', $service));

        $response->assertUnprocessable()
            ->assertJson(['message' => 'Keine Songs mit Inhalt zum Exportieren gefunden.']);
    }

    public function test_download_mit_ungematchten_songs_setzt_skipped_header(): void
    {
        $service = Service::factory()->create(['finalized_at' => now()]);
        $song = $this->createSongWithContent('Matched Song');

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

        ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => null,
            'cts_song_name' => 'Unmatched Song',
            'order' => 2,
        ]);

        $response = $this->actingAs($this->user)->get(route('services.download', $service));

        $response->assertOk();
        $this->assertEquals('1', $response->headers->get('X-Skipped-Songs'));
    }

    public function test_download_erfordert_authentifizierung(): void
    {
        $service = Service::factory()->create(['finalized_at' => now()]);

        $response = $this->getJson(route('services.download', $service));

        $response->assertUnauthorized();
    }

    public function test_legacy_fallback_wenn_keine_agenda_items(): void
    {

        $service = Service::factory()->create(['finalized_at' => now(), 'title' => 'Legacy Service']);
        $song = $this->createSongWithContent('Legacy Song');

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

        $exportService = $this->createTestableExportService();
        $result = $exportService->generatePlaylist($service);

        $this->assertStringContainsString('.proplaylist', $result['filename']);
        $this->assertFileExists($result['path']);
        $this->assertEquals(0, $result['skipped']);

        $content = file_get_contents($result['path']);
        $this->assertStringContainsString('Legacy Song', $content);

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

    public function test_agenda_export_folgt_agenda_reihenfolge(): void
    {

        $service = Service::factory()->create(['finalized_at' => now(), 'title' => 'Agenda Service']);

        $song1 = $this->createSongWithContent('Erstes Lied');
        $song2 = $this->createSongWithContent('Zweites Lied');

        $serviceSong1 = ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song1->id,
            'cts_song_name' => 'Erstes Lied',
            'order' => 1,
        ]);

        $serviceSong2 = ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song2->id,
            'cts_song_name' => 'Zweites Lied',
            'order' => 2,
        ]);

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Zweites Lied',
            'service_song_id' => $serviceSong2->id,
            'sort_order' => 1,
            'is_before_event' => false,
        ]);

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Erstes Lied',
            'service_song_id' => $serviceSong1->id,
            'sort_order' => 2,
            'is_before_event' => false,
        ]);

        $exportService = $this->createTestableExportService();
        $result = $exportService->generatePlaylist($service);

        $this->assertFileExists($result['path']);
        $this->assertEquals(0, $result['skipped']);

        $playlistContent = file_get_contents($result['path']);
        $pos1 = strpos($playlistContent, 'Zweites Lied');
        $pos2 = strpos($playlistContent, 'Erstes Lied');
        $this->assertNotFalse($pos1);
        $this->assertNotFalse($pos2);
        $this->assertLessThan($pos2, $pos1, 'Zweites Lied should appear before Erstes Lied in agenda order');

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

    public function test_agenda_export_informationen_an_gematchter_position(): void
    {

        $service = Service::factory()->create([
            'finalized_at' => now(),
            'title' => 'Service mit Infos',
            'date' => now(),
        ]);

        Setting::set('agenda_announcement_position', 'Hinweise*,Information*');

        $this->createSlideFile('info1.jpg');
        $this->createSlide([
            'type' => 'information',
            'service_id' => null,
            'original_filename' => 'info1.jpg',
            'stored_filename' => 'slides/info1.jpg',
            'sort_order' => 1,
        ]);

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

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Lobpreissong',
            'service_song_id' => $serviceSong->id,
            'sort_order' => 1,
            'is_before_event' => false,
        ]);

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Hinweise und Informationen',
            'service_song_id' => null,
            'sort_order' => 2,
            'is_before_event' => false,
        ]);

        $exportService = $this->createTestableExportService();
        $result = $exportService->generatePlaylist($service);

        $this->assertFileExists($result['path']);

        $playlistContent = file_get_contents($result['path']);
        $songPos = strpos($playlistContent, 'Lobpreissong');
        $infoPos = strpos($playlistContent, 'Informationen');
        $this->assertNotFalse($songPos);
        $this->assertNotFalse($infoPos);
        $this->assertLessThan($infoPos, $songPos, 'Song should appear before announcements at matched position');

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

    public function test_agenda_export_informationen_am_anfang_als_fallback(): void
    {

        $service = Service::factory()->create([
            'finalized_at' => now(),
            'title' => 'Service Fallback Infos',
            'date' => now(),
        ]);

        Setting::set('agenda_announcement_position', 'Hinweise*');

        $this->createSlideFile('info_fallback.jpg');
        $this->createSlide([
            'type' => 'information',
            'service_id' => null,
            'original_filename' => 'info_fallback.jpg',
            'stored_filename' => 'slides/info_fallback.jpg',
            'sort_order' => 1,
        ]);

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

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Predigt',
            'service_song_id' => null,
            'sort_order' => 1,
            'is_before_event' => false,
        ]);

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Fallback Song',
            'service_song_id' => $serviceSong->id,
            'sort_order' => 2,
            'is_before_event' => false,
        ]);

        $exportService = $this->createTestableExportService();
        $result = $exportService->generatePlaylist($service);

        $this->assertFileExists($result['path']);

        $playlistContent = file_get_contents($result['path']);
        $infoPos = strpos($playlistContent, 'Informationen');
        $songPos = strpos($playlistContent, 'Fallback Song');
        $this->assertNotFalse($infoPos);
        $this->assertNotFalse($songPos);
        $this->assertLessThan($songPos, $infoPos, 'Announcements should be at beginning when no pattern matches');

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

    public function test_agenda_export_ueberspringt_items_ohne_slides_oder_songs(): void
    {

        $service = Service::factory()->create(['finalized_at' => now(), 'title' => 'Skip Service']);

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

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Begrüßung',
            'service_song_id' => null,
            'sort_order' => 1,
            'is_before_event' => false,
        ]);

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Einziger Song',
            'service_song_id' => $serviceSong->id,
            'sort_order' => 2,
            'is_before_event' => false,
        ]);

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Gebet',
            'service_song_id' => null,
            'sort_order' => 3,
            'is_before_event' => false,
        ]);

        $exportService = $this->createTestableExportService();
        $result = $exportService->generatePlaylist($service);

        $this->assertFileExists($result['path']);
        $this->assertEquals(0, $result['skipped']);

        $playlistContent = file_get_contents($result['path']);
        $this->assertStringContainsString('Einziger Song', $playlistContent);
        $this->assertStringNotContainsString('Begrüßung', $playlistContent);
        $this->assertStringNotContainsString('Gebet', $playlistContent);

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

    public function test_agenda_export_zaehlt_ungematchte_songs_als_skipped(): void
    {

        $service = Service::factory()->create(['finalized_at' => now(), 'title' => 'Skipped Service']);

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

        $unmatchedServiceSong = ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => null,
            'cts_song_name' => 'Unbekannter Song',
            'order' => 2,
        ]);

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Gematchter Song',
            'service_song_id' => $matchedServiceSong->id,
            'sort_order' => 1,
            'is_before_event' => false,
        ]);

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Unbekannter Song',
            'service_song_id' => $unmatchedServiceSong->id,
            'sort_order' => 2,
            'is_before_event' => false,
        ]);

        $exportService = $this->createTestableExportService();
        $result = $exportService->generatePlaylist($service);

        $this->assertEquals(1, $result['skipped']);

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

    public function test_agenda_export_mit_slides_auf_agenda_item(): void
    {

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

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

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Worship Song',
            'service_song_id' => $serviceSong->id,
            'sort_order' => 1,
            'is_before_event' => false,
        ]);

        $sermonItem = ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Predigtblock',
            'service_song_id' => null,
            'sort_order' => 2,
            'is_before_event' => false,
        ]);

        $this->createSlideFile('predigt1.jpg');
        $this->createSlide([
            'type' => 'sermon',
            'service_id' => $service->id,
            'service_agenda_item_id' => $sermonItem->id,
            'original_filename' => 'predigt1.jpg',
            'stored_filename' => 'slides/predigt1.jpg',
            'sort_order' => 1,
        ]);

        $exportService = $this->createTestableExportService();
        $result = $exportService->generatePlaylist($service);

        $this->assertFileExists($result['path']);

        $playlistContent = file_get_contents($result['path']);
        $songPos = strpos($playlistContent, 'Worship Song');
        $sermonPos = strpos($playlistContent, 'Predigtblock');
        $this->assertNotFalse($songPos);
        $this->assertNotFalse($sermonPos);
        $this->assertLessThan($sermonPos, $songPos, 'Song should appear before sermon slides');

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

    public function test_agenda_export_before_event_items_ausgeschlossen(): void
    {

        $service = Service::factory()->create(['finalized_at' => now(), 'title' => 'Before Event']);

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

        $hiddenSong = $this->createSongWithContent('Hidden Song');
        $hiddenServiceSong = ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $hiddenSong->id,
            'cts_song_name' => 'Hidden Song',
            'order' => 2,
        ]);

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Hidden Song',
            'service_song_id' => $hiddenServiceSong->id,
            'sort_order' => 1,
            'is_before_event' => true,
        ]);

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Visible Song',
            'service_song_id' => $serviceSong->id,
            'sort_order' => 2,
            'is_before_event' => false,
        ]);

        $exportService = $this->createTestableExportService();
        $result = $exportService->generatePlaylist($service);

        $this->assertFileExists($result['path']);

        $playlistContent = file_get_contents($result['path']);
        $this->assertStringContainsString('Visible Song', $playlistContent);
        $this->assertStringNotContainsString('Hidden Song', $playlistContent);

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

    public function test_finalize_und_download_flow_mit_agenda_items(): void
    {
        $this->app->instance(PlaylistExportService::class, $this->createTestableExportService());

        $service = Service::factory()->create([
            'finalized_at' => null,
            'title' => 'Integrations Service',
            'date' => now(),
        ]);

        // Create songs with content
        $song1 = $this->createSongWithContent('Lobpreis Lied');
        $song2 = $this->createSongWithContent('Abschluss Lied');
        $arrangement1 = $song1->arrangements()->first();
        $arrangement2 = $song2->arrangements()->first();

        $serviceSong1 = ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song1->id,
            'song_arrangement_id' => $arrangement1->id,
            'cts_song_name' => 'Lobpreis Lied',
            'order' => 1,
        ]);

        $serviceSong2 = ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song2->id,
            'song_arrangement_id' => $arrangement2->id,
            'cts_song_name' => 'Abschluss Lied',
            'order' => 2,
        ]);

        // Agenda: Song → Sermon (non-song with slides) → Song
        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Lobpreis Lied',
            'service_song_id' => $serviceSong1->id,
            'sort_order' => 1,
            'is_before_event' => false,
        ]);

        $sermonItem = ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Predigt',
            'service_song_id' => null,
            'sort_order' => 2,
            'is_before_event' => false,
        ]);

        ServiceAgendaItem::factory()->create([
            'service_id' => $service->id,
            'title' => 'Abschluss Lied',
            'service_song_id' => $serviceSong2->id,
            'sort_order' => 3,
            'is_before_event' => false,
        ]);

        // Upload slides to sermon agenda item
        $this->createSlideFile('sermon_slide1.jpg');
        $this->createSlide([
            'type' => 'sermon',
            'service_id' => $service->id,
            'service_agenda_item_id' => $sermonItem->id,
            'original_filename' => 'sermon_slide1.jpg',
            'stored_filename' => 'slides/sermon_slide1.jpg',
            'sort_order' => 1,
        ]);

        // Add sermon slides for finalization check (legacy)
        Slide::factory()->create([
            'service_id' => $service->id,
            'type' => 'sermon',
        ]);

        // Step 1: Finalize the service
        $finalizeResponse = $this->actingAs($this->user)
            ->postJson(route('services.finalize', $service), ['confirmed' => true]);

        $finalizeResponse->assertOk()
            ->assertJson([
                'needs_confirmation' => false,
                'success' => 'Service wurde abgeschlossen.',
            ]);

        $service->refresh();
        $this->assertNotNull($service->finalized_at);

        // Step 2: Download the finalized service
        $downloadResponse = $this->actingAs($this->user)
            ->get(route('services.download', $service));

        $downloadResponse->assertOk();

        $disposition = $downloadResponse->headers->get('content-disposition');
        $this->assertNotNull($disposition);
        $this->assertStringContainsString('.proplaylist', $disposition);

        // Verify playlist content includes songs and sermon in correct order
        $tempPath = null;
        foreach (glob(sys_get_temp_dir().'/playlist-export-*/*.proplaylist') as $file) {
            $content = file_get_contents($file);
            if (str_contains($content, 'Integrations Service')) {
                $tempPath = dirname($file);
                // Verify agenda ordering: song1 → sermon → song2
                $pos1 = strpos($content, 'Lobpreis Lied');
                $posSer = strpos($content, 'Predigt');
                $pos2 = strpos($content, 'Abschluss Lied');
                $this->assertNotFalse($pos1);
                $this->assertNotFalse($posSer);
                $this->assertNotFalse($pos2);
                $this->assertLessThan($posSer, $pos1, 'Song 1 should be before sermon');
                $this->assertLessThan($pos2, $posSer, 'Sermon should be before Song 2');
                break;
            }
        }

        if ($tempPath) {
            $this->cleanupTempDir($tempPath);
        }
    }

    public function test_finalize_und_download_flow_legacy_ohne_agenda(): void
    {
        $this->app->instance(PlaylistExportService::class, $this->createTestableExportService());

        $service = Service::factory()->create([
            'finalized_at' => null,
            'title' => 'Legacy Ablauf',
            'date' => now(),
        ]);

        $song = $this->createSongWithContent('Legacy Worship');
        $arrangement = $song->arrangements()->first();

        ServiceSong::create([
            'service_id' => $service->id,
            'song_id' => $song->id,
            'song_arrangement_id' => $arrangement->id,
            'cts_song_name' => 'Legacy Worship',
            'order' => 1,
        ]);

        Slide::factory()->create([
            'service_id' => $service->id,
            'type' => 'sermon',
        ]);

        // No ServiceAgendaItems → will use legacy path

        // Step 1: Finalize
        $finalizeResponse = $this->actingAs($this->user)
            ->postJson(route('services.finalize', $service), ['confirmed' => false]);

        $finalizeResponse->assertOk()
            ->assertJson([
                'needs_confirmation' => false,
                'success' => 'Service wurde abgeschlossen.',
            ]);

        $service->refresh();
        $this->assertNotNull($service->finalized_at);

        // Step 2: Download — uses legacy path (no agenda items)
        $downloadResponse = $this->actingAs($this->user)
            ->get(route('services.download', $service));

        $downloadResponse->assertOk();

        $disposition = $downloadResponse->headers->get('content-disposition');
        $this->assertNotNull($disposition);
        $this->assertStringContainsString('.proplaylist', $disposition);
        $this->assertStringContainsString('Legacy Ablauf', $disposition);

        // Cleanup temp files
        foreach (glob(sys_get_temp_dir().'/playlist-export-*') as $dir) {
            if (is_dir($dir)) {
                $this->cleanupTempDir($dir);
            }
        }
    }

    private function cleanupTempDir(string $dir): void
    {
        if (is_dir($dir)) {
            $items = scandir($dir);
            foreach ($items as $item) {
                if ($item === '.' || $item === '..') {
                    continue;
                }
                $path = $dir.'/'.$item;
                is_dir($path) ? $this->cleanupTempDir($path) : unlink($path);
            }
            rmdir($dir);
        }
    }
}
