<?php

declare(strict_types=1);

namespace App\Tests\Domain\Manga\Application\QueryHandler;

use App\Domain\Manga\Application\Query\FindMangaMatchByFilename;
use App\Domain\Manga\Application\QueryHandler\FindMangaMatchByFilenameHandler;
use App\Domain\Manga\Domain\Model\Manga;
use App\Domain\Manga\Domain\Model\ValueObject\ImageUrls;
use App\Domain\Manga\Domain\Model\ValueObject\MangaId;
use App\Domain\Manga\Domain\Model\ValueObject\MangaSlug;
use App\Domain\Manga\Domain\Model\ValueObject\MangaTitle;
use App\Domain\Manga\Infrastructure\Service\FilenameAnalyzer;
use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository;
use PHPUnit\Framework\TestCase;

class FindMangaMatchByFilenameHandlerTest extends TestCase
{
    private InMemoryMangaRepository $repository;
    private FilenameAnalyzer $filenameAnalyzer;
    private FindMangaMatchByFilenameHandler $handler;

    protected function setUp(): void
    {
        $this->repository = new InMemoryMangaRepository();
        $this->filenameAnalyzer = new FilenameAnalyzer();
        $this->handler = new FindMangaMatchByFilenameHandler(
            $this->filenameAnalyzer,
            $this->repository
        );
    }

    public function test_it_finds_exact_match_by_title(): void
    {
        // Given
        $manga = $this->createManga(
            id: '123',
            title: 'One Piece',
            slug: 'one-piece'
        );
        $this->repository->save($manga);

        // When
        $query = new FindMangaMatchByFilename('one-piece_vol108_ch1094.cbz');
        $response = $this->handler->handle($query);

        // Then
        $this->assertTrue($response->hasMatches());
        $this->assertCount(1, $response->matches);

        $match = $response->matches[0];
        $this->assertEquals('123', $match->id);
        $this->assertEquals('One Piece', $match->title);
        $this->assertEquals('one-piece', $match->slug);
        $this->assertEquals(1094.0, $match->chapterNumber);
        $this->assertEquals(108, $match->volumeNumber);

        // Vérifier aussi dans la réponse globale
        $this->assertEquals(1094.0, $response->chapterNumber);
        $this->assertEquals(108, $response->volumeNumber);
        $this->assertNotEmpty($response->possibleTitles);
    }

    public function test_it_returns_empty_matches_when_no_manga_found(): void
    {
        // Given - no manga in repository

        // When
        $query = new FindMangaMatchByFilename('unknown-manga_vol1_ch1.cbz');
        $response = $this->handler->handle($query);

        // Then
        $this->assertFalse($response->hasMatches());
        $this->assertCount(0, $response->matches);
        $this->assertNull($response->getBestMatch());
    }

    public function test_it_finds_multiple_matches_and_sorts_by_score(): void
    {
        // Given
        $manga1 = $this->createManga(
            id: '1',
            title: 'One Piece',
            slug: 'one-piece'
        );
        $manga2 = $this->createManga(
            id: '2',
            title: 'One Piece Z',
            slug: 'one-piece-z'
        );
        $manga3 = $this->createManga(
            id: '3',
            title: 'The One Piece',
            slug: 'the-one-piece'
        );

        $this->repository->save($manga1);
        $this->repository->save($manga2);
        $this->repository->save($manga3);

        // When
        $query = new FindMangaMatchByFilename('one-piece_vol108_ch1094.cbz');
        $response = $this->handler->handle($query);

        // Then
        $this->assertTrue($response->hasMatches());
        $this->assertGreaterThanOrEqual(1, count($response->matches));

        // Le meilleur match devrait être "One Piece" (correspondance exacte)
        $bestMatch = $response->getBestMatch();
        $this->assertNotNull($bestMatch);
        $this->assertEquals('One Piece', $bestMatch->title);

        // Les scores doivent être triés par ordre décroissant
        $scores = array_map(fn($match) => $match->matchScore, $response->matches);
        $sortedScores = $scores;
        rsort($sortedScores);
        $this->assertEquals($sortedScores, $scores);
    }

    public function test_it_extracts_chapter_and_volume_numbers(): void
    {
        // Given
        $manga = $this->createManga(
            id: '123',
            title: 'Attack on Titan',
            slug: 'attack-on-titan'
        );
        $this->repository->save($manga);

        // When
        $query = new FindMangaMatchByFilename('attack-on-titan_vol32_ch130.cbz');
        $response = $this->handler->handle($query);

        // Then
        $this->assertEquals(130.0, $response->chapterNumber);
        $this->assertEquals(32, $response->volumeNumber);

        // Vérifier que chaque match contient aussi ces informations
        foreach ($response->matches as $match) {
            $this->assertEquals(130.0, $match->chapterNumber);
            $this->assertEquals(32, $match->volumeNumber);
        }
    }

    public function test_it_handles_decimal_chapter_numbers(): void
    {
        // Given
        $manga = $this->createManga(
            id: '123',
            title: 'Naruto',
            slug: 'naruto'
        );
        $this->repository->save($manga);

        // When
        $query = new FindMangaMatchByFilename('naruto_vol50_ch456.5.cbz');
        $response = $this->handler->handle($query);

        // Then
        $this->assertEquals(456.5, $response->chapterNumber);
        $this->assertEquals(50, $response->volumeNumber);

        // Vérifier que chaque match contient aussi ces informations
        foreach ($response->matches as $match) {
            $this->assertEquals(456.5, $match->chapterNumber);
            $this->assertEquals(50, $match->volumeNumber);
        }
    }

    public function test_it_finds_matches_with_alternative_slugs(): void
    {
        // Given
        $manga = $this->createManga(
            id: '123',
            title: 'Shingeki no Kyojin',
            slug: 'shingeki-no-kyojin',
            alternativeSlugs: ['attack-on-titan', 'aot']
        );
        $this->repository->save($manga);

        // When
        $query = new FindMangaMatchByFilename('attack-on-titan_vol1_ch1.cbz');
        $response = $this->handler->handle($query);

        // Then
        $this->assertTrue($response->hasMatches());
        $bestMatch = $response->getBestMatch();
        $this->assertNotNull($bestMatch);
        $this->assertEquals('123', $bestMatch->id);
        $this->assertEquals('Shingeki no Kyojin', $bestMatch->title);
    }

    public function test_it_handles_filename_without_chapter_or_volume(): void
    {
        // Given
        $manga = $this->createManga(
            id: '123',
            title: 'One Piece',
            slug: 'one-piece'
        );
        $this->repository->save($manga);

        // When
        $query = new FindMangaMatchByFilename('one-piece.cbz');
        $response = $this->handler->handle($query);

        // Then
        $this->assertTrue($response->hasMatches());
        $this->assertNull($response->chapterNumber);
        $this->assertNull($response->volumeNumber);

        $bestMatch = $response->getBestMatch();
        $this->assertEquals('123', $bestMatch->id);
        $this->assertNull($bestMatch->chapterNumber);
        $this->assertNull($bestMatch->volumeNumber);
    }

    public function test_it_finds_single_match_with_unique_id(): void
    {
        // Given
        $manga = $this->createManga(
            id: '123',
            title: 'One Piece',
            slug: 'one-piece'
        );
        $this->repository->save($manga);

        // When
        $query = new FindMangaMatchByFilename('one-piece_vol1_ch1.cbz');
        $response = $this->handler->handle($query);

        // Then - Vérifier qu'on a bien un seul résultat avec l'ID correct
        $this->assertCount(1, $response->matches);
        $this->assertEquals('123', $response->matches[0]->id);
    }

    public function test_it_provides_possible_titles_in_response(): void
    {
        // Given
        $manga = $this->createManga(
            id: '123',
            title: 'Dragon Ball',
            slug: 'dragon-ball'
        );
        $this->repository->save($manga);

        // When
        $query = new FindMangaMatchByFilename('dragon-ball_vol1_ch5.cbz');
        $response = $this->handler->handle($query);

        // Then
        $this->assertNotEmpty($response->possibleTitles);
        $this->assertEquals(['dragon-ball'], $response->possibleTitles);
    }

    public function test_it_handles_filename_with_only_volume(): void
    {
        // Given
        $manga = $this->createManga(
            id: '123',
            title: 'One Piece',
            slug: 'one-piece'
        );
        $this->repository->save($manga);

        // When - Fichier avec seulement un volume, sans chapitre
        $query = new FindMangaMatchByFilename('one-piece_vol108.cbz');
        $response = $this->handler->handle($query);

        // Then
        $this->assertTrue($response->hasMatches());
        $this->assertEquals(108, $response->volumeNumber);
        $this->assertNull($response->chapterNumber);

        $bestMatch = $response->getBestMatch();
        $this->assertNotNull($bestMatch);
        $this->assertEquals('123', $bestMatch->id);
        $this->assertEquals(108, $bestMatch->volumeNumber);
        $this->assertNull($bestMatch->chapterNumber);
    }

    public function test_it_handles_filename_with_only_chapter(): void
    {
        // Given
        $manga = $this->createManga(
            id: '123',
            title: 'Naruto',
            slug: 'naruto'
        );
        $this->repository->save($manga);

        // When - Fichier avec seulement un chapitre, sans volume
        $query = new FindMangaMatchByFilename('naruto_ch456.cbz');
        $response = $this->handler->handle($query);

        // Then
        $this->assertTrue($response->hasMatches());
        $this->assertEquals(456.0, $response->chapterNumber);
        $this->assertNull($response->volumeNumber);

        $bestMatch = $response->getBestMatch();
        $this->assertNotNull($bestMatch);
        $this->assertEquals('123', $bestMatch->id);
        $this->assertEquals(456.0, $bestMatch->chapterNumber);
        $this->assertNull($bestMatch->volumeNumber);
    }

    public function test_it_handles_various_volume_only_formats(): void
    {
        // Given
        $manga = $this->createManga(
            id: '123',
            title: 'Attack on Titan',
            slug: 'attack-on-titan'
        );
        $this->repository->save($manga);

        $testCases = [
            ['filename' => 'attack-on-titan vol 32.cbz', 'expectedVolume' => 32],
            ['filename' => 'attack-on-titan-tome-15.cbz', 'expectedVolume' => 15],
            ['filename' => 'attack-on-titan_t10.cbz', 'expectedVolume' => 10],
            ['filename' => 'attack-on-titan Volume 5.cbr', 'expectedVolume' => 5],
        ];

        foreach ($testCases as $case) {
            // When
            $query = new FindMangaMatchByFilename($case['filename']);
            $response = $this->handler->handle($query);

            // Then
            $this->assertTrue($response->hasMatches(), "Should find match for {$case['filename']}");
            $this->assertEquals($case['expectedVolume'], $response->volumeNumber,
                "Failed volume extraction for: {$case['filename']}");
            $this->assertNull($response->chapterNumber,
                "Should not have chapter for: {$case['filename']}");

            $bestMatch = $response->getBestMatch();
            $this->assertEquals($case['expectedVolume'], $bestMatch->volumeNumber,
                "Match should have correct volume for: {$case['filename']}");
            $this->assertNull($bestMatch->chapterNumber,
                "Match should not have chapter for: {$case['filename']}");
        }
    }

    public function test_it_handles_various_chapter_only_formats(): void
    {
        // Given
        $manga = $this->createManga(
            id: '123',
            title: 'My Hero Academia',
            slug: 'my-hero-academia'
        );
        $this->repository->save($manga);

        $testCases = [
            ['filename' => 'my-hero-academia ch 150.cbz', 'expectedChapter' => 150.0],
            ['filename' => 'my-hero-academia-chap-200.cbz', 'expectedChapter' => 200.0],
            ['filename' => 'my-hero-academia_chapter_75.cbz', 'expectedChapter' => 75.0],
            ['filename' => 'my-hero-academia chapitre 100.cbr', 'expectedChapter' => 100.0],
            ['filename' => 'my-hero-academia_ch99.5.cbz', 'expectedChapter' => 99.5],
        ];

        foreach ($testCases as $case) {
            // When
            $query = new FindMangaMatchByFilename($case['filename']);
            $response = $this->handler->handle($query);

            // Then
            $this->assertTrue($response->hasMatches(), "Should find match for {$case['filename']}");
            $this->assertEquals($case['expectedChapter'], $response->chapterNumber,
                "Failed chapter extraction for: {$case['filename']}");
            $this->assertNull($response->volumeNumber,
                "Should not have volume for: {$case['filename']}");

            $bestMatch = $response->getBestMatch();
            $this->assertEquals($case['expectedChapter'], $bestMatch->chapterNumber,
                "Match should have correct chapter for: {$case['filename']}");
            $this->assertNull($bestMatch->volumeNumber,
                "Match should not have volume for: {$case['filename']}");
        }
    }

    private function createManga(
        string $id,
        string $title,
        string $slug,
        array $alternativeSlugs = [],
        ?string $thumbnailUrl = null
    ): Manga {
        return new Manga(
            id: new MangaId($id),
            title: new MangaTitle($title),
            slug: new MangaSlug($slug),
            description: 'Test description',
            author: 'Test Author',
            publicationYear: 2000,
            genres: ['action'],
            status: 'ongoing',
            imageUrls: new ImageUrls(
                'http://example.com/full.jpg',
                $thumbnailUrl ?? 'http://example.com/thumbnail.jpg'
            ),
            alternativeSlugs: $alternativeSlugs
        );
    }

    protected function tearDown(): void
    {
        $this->repository->clear();
    }
}

