<?php

namespace Tests\Feature;

use App\Models\Song;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

final class ProFileExportTest extends TestCase
{
    use RefreshDatabase;

    private function createSongWithContent(): Song
    {
        $song = Song::create([
            'title' => 'Export Test Song',
            'ccli_id' => '54321',
            'author' => 'Test Author',
            'copyright_text' => 'Test Publisher',
            'copyright_year' => 2024,
            'publisher' => 'Test Publisher',
        ]);

        $verse = $song->groups()->create(['name' => 'Verse 1', 'color' => '#2196F3', 'order' => 0]);
        $verse->slides()->create(['order' => 0, 'text_content' => 'First line of verse']);
        $verse->slides()->create(['order' => 1, 'text_content' => 'Second line of verse']);

        $chorus = $song->groups()->create(['name' => 'Chorus', 'color' => '#F44336', 'order' => 1]);
        $chorus->slides()->create(['order' => 0, 'text_content' => 'Chorus text']);

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

        return $song;
    }

    public function test_download_pro_gibt_datei_zurueck(): void
    {
        $user = User::factory()->create();
        $song = $this->createSongWithContent();

        $response = $this->actingAs($user)->get("/api/songs/{$song->id}/download-pro");

        $response->assertOk();
        $response->assertHeader('content-disposition');
        $this->assertStringContains('Export Test Song.pro', $response->headers->get('content-disposition'));
    }

    public function test_download_pro_song_ohne_gruppen_gibt_422(): void
    {
        $user = User::factory()->create();
        $song = Song::factory()->create();

        $response = $this->actingAs($user)->get("/api/songs/{$song->id}/download-pro");

        $response->assertStatus(422);
    }

    public function test_download_pro_erfordert_authentifizierung(): void
    {
        $song = Song::factory()->create();

        $response = $this->getJson("/api/songs/{$song->id}/download-pro");

        $response->assertUnauthorized();
    }

    public function test_download_pro_roundtrip_import_export(): void
    {
        $user = User::factory()->create();

        $sourcePath = base_path('tests/fixtures/propresenter/Test.pro');
        $file = new \Illuminate\Http\UploadedFile($sourcePath, 'Test.pro', 'application/octet-stream', null, true);

        $importResponse = $this->actingAs($user)->postJson(route('api.songs.import-pro'), ['file' => $file]);
        $importResponse->assertOk();

        $songId = $importResponse->json('songs.0.id');
        $song = Song::find($songId);

        $this->assertNotNull($song);
        $this->assertGreaterThan(0, $song->groups()->count());

        $exportResponse = $this->actingAs($user)->get("/api/songs/{$songId}/download-pro");
        $exportResponse->assertOk();
    }

    public function test_download_pro_roundtrip_preserves_content(): void
    {
        $user = User::factory()->create();

        // 1. Import the reference .pro file
        $sourcePath = base_path('tests/fixtures/propresenter/Test.pro');
        $file = new \Illuminate\Http\UploadedFile($sourcePath, 'Test.pro', 'application/octet-stream', null, true);

        $importResponse = $this->actingAs($user)->postJson(route('api.songs.import-pro'), ['file' => $file]);
        $importResponse->assertOk();

        $songId = $importResponse->json('songs.0.id');
        $originalSong = Song::with(['groups.slides', 'arrangements.arrangementGroups.group'])->find($songId);
        $this->assertNotNull($originalSong);

        // Snapshot original data
        $originalGroups = $originalSong->groups->sortBy('order')->values();
        $originalArrangements = $originalSong->arrangements;

        // 2. Export as .pro
        $exportResponse = $this->actingAs($user)->get("/api/songs/{$songId}/download-pro");
        $exportResponse->assertOk();

        // Save exported content to temp file — BinaryFileResponse delivers a real file
        $tempPath = sys_get_temp_dir().'/roundtrip-test-'.uniqid().'.pro';
        /** @var \Symfony\Component\HttpFoundation\BinaryFileResponse $baseResponse */
        $baseResponse = $exportResponse->baseResponse;
        copy($baseResponse->getFile()->getPathname(), $tempPath);

        // 3. Re-import the exported file as a new song (different ccli to avoid upsert)
        // Use the ProPresenter parser directly to read and verify
        $reImported = \ProPresenter\Parser\ProFileReader::read($tempPath);
        @unlink($tempPath);

        // 4. Assert song name
        $this->assertSame($originalSong->title, $reImported->getName());

        // 5. Assert groups match (same names, same order)
        $reImportedGroups = $reImported->getGroups();
        $this->assertCount($originalGroups->count(), $reImportedGroups, 'Group count mismatch');

        foreach ($originalGroups as $index => $originalGroup) {
            $reImportedGroup = $reImportedGroups[$index];
            $this->assertSame($originalGroup->name, $reImportedGroup->getName(), "Group name mismatch at index {$index}");

            // Assert slides within group
            $originalSlides = $originalGroup->slides->sortBy('order')->values();
            $reImportedSlides = $reImported->getSlidesForGroup($reImportedGroup);
            $this->assertCount($originalSlides->count(), $reImportedSlides, "Slide count mismatch for group '{$originalGroup->name}'");

            foreach ($originalSlides as $slideIndex => $originalSlide) {
                $reImportedSlide = $reImportedSlides[$slideIndex];

                $this->assertSame(
                    $originalSlide->text_content,
                    $reImportedSlide->getPlainText(),
                    "Slide text mismatch for group '{$originalGroup->name}' slide {$slideIndex}"
                );

                // Assert translation if present
                if ($originalSlide->text_content_translated) {
                    $this->assertTrue($reImportedSlide->hasTranslation(), "Expected translation for group '{$originalGroup->name}' slide {$slideIndex}");
                    $this->assertSame(
                        $originalSlide->text_content_translated,
                        $reImportedSlide->getTranslation()?->getPlainText(),
                        "Translation text mismatch for group '{$originalGroup->name}' slide {$slideIndex}"
                    );
                }
            }
        }

        // 6. Assert arrangements match (same names, same group order)
        $reImportedArrangements = $reImported->getArrangements();
        $this->assertCount($originalArrangements->count(), $reImportedArrangements, 'Arrangement count mismatch');

        foreach ($originalArrangements as $index => $originalArrangement) {
            $reImportedArrangement = $reImported->getArrangementByName($originalArrangement->name);
            $this->assertNotNull($reImportedArrangement, "Arrangement '{$originalArrangement->name}' not found in re-import");

            $originalGroupNames = $originalArrangement->arrangementGroups
                ->sortBy('order')
                ->map(fn ($ag) => $ag->group?->name)
                ->filter()
                ->values()
                ->toArray();

            $reImportedGroupNames = array_map(
                fn ($group) => $group->getName(),
                $reImported->getGroupsForArrangement($reImportedArrangement)
            );

            $this->assertSame(
                $originalGroupNames,
                $reImportedGroupNames,
                "Arrangement '{$originalArrangement->name}' group order mismatch"
            );
        }

        // 7. Assert CCLI metadata
        if ($originalSong->ccli_id) {
            $this->assertSame((int) $originalSong->ccli_id, $reImported->getCcliSongNumber());
        }
        if ($originalSong->author) {
            $this->assertSame($originalSong->author, $reImported->getCcliAuthor());
        }
    }

    private function assertStringContains(string $needle, ?string $haystack): void
    {
        $this->assertNotNull($haystack);
        $this->assertTrue(
            str_contains($haystack, $needle),
            "Failed asserting that '{$haystack}' contains '{$needle}'"
        );
    }
}
