<?php

declare(strict_types=1);

namespace App\Tests\Domain\Manga\Infrastructure\Service;

use App\Domain\Manga\Domain\Model\Chapter;
use App\Domain\Manga\Domain\Model\Manga;
use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
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\MangadxChapterSynchronizationService;
use App\Tests\Domain\Manga\Adapter\InMemoryMangadexClient;
use App\Tests\Domain\Manga\Adapter\InMemoryMangaRepository;
use PHPUnit\Framework\TestCase;

class MangadxChapterSynchronizationServiceTest extends TestCase
{
    private InMemoryMangadexClient $client;
    private InMemoryMangaRepository $repository;
    private MangadxChapterSynchronizationService $service;

    protected function setUp(): void
    {
        $this->client = new InMemoryMangadexClient();
        $this->repository = new InMemoryMangaRepository();
        $this->service = new MangadxChapterSynchronizationService($this->client, $this->repository);
    }

    private function makeManga(string $externalId = 'manga-123'): Manga
    {
        return new Manga(
            new MangaId('manga-id'),
            new MangaTitle('Test'),
            new MangaSlug('test'),
            'Desc',
            'Author',
            2024,
            [],
            'ongoing',
            new ExternalId($externalId)
        );
    }

    private function chapterEntry(string $number, string $lang, ?string $volume): array
    {
        return [
            'attributes' => [
                'chapter' => $number,
                'translatedLanguage' => $lang,
                'title' => "Chapter $number",
                'volume' => $volume,
            ],
        ];
    }

    /**
     * Chapitres sans volume entre deux volumes différents → assignés au volume précédent
     *
     * Ch1→Vol1, Ch2→null, Ch3→null, Ch4→Vol2
     * Après sync : Ch2 et Ch3 doivent avoir Vol1
     */
    public function testVolumeTransitionIsAssignedToPreviousVolume(): void
    {
        $manga = $this->makeManga();
        $this->client->addFeed('manga-123', [
            $this->chapterEntry('1', 'en', '1'),
            $this->chapterEntry('2', 'en', null),
            $this->chapterEntry('3', 'en', null),
            $this->chapterEntry('4', 'en', '2'),
        ]);

        $this->service->synchronizeChapters($manga);

        $chapters = $this->indexedByNumber($manga->pullNewChapters());

        $this->assertSame(1, $chapters[1.0]->getVolume(), 'Ch1 doit rester Vol1');
        $this->assertSame(1, $chapters[2.0]->getVolume(), 'Ch2 (transition) doit être assigné à Vol1');
        $this->assertSame(1, $chapters[3.0]->getVolume(), 'Ch3 (transition) doit être assigné à Vol1');
        $this->assertSame(2, $chapters[4.0]->getVolume(), 'Ch4 doit rester Vol2');
    }

    /**
     * Chapitres en début de série sans volume → assignés au premier volume trouvé
     *
     * Ch1→null, Ch2→null, Ch3→Vol1
     * Après sync : Ch1 et Ch2 doivent avoir Vol1
     */
    public function testChaptersWithoutVolumeAtStartGetNextVolume(): void
    {
        $manga = $this->makeManga();
        $this->client->addFeed('manga-123', [
            $this->chapterEntry('1', 'en', null),
            $this->chapterEntry('2', 'en', null),
            $this->chapterEntry('3', 'en', '1'),
        ]);

        $this->service->synchronizeChapters($manga);

        $chapters = $this->indexedByNumber($manga->pullNewChapters());

        $this->assertSame(1, $chapters[1.0]->getVolume(), 'Ch1 (début de série) doit prendre Vol1');
        $this->assertSame(1, $chapters[2.0]->getVolume(), 'Ch2 (début de série) doit prendre Vol1');
        $this->assertSame(1, $chapters[3.0]->getVolume(), 'Ch3 doit rester Vol1');
    }

    /**
     * Chapitres avec volumes explicites ne sont pas modifiés
     *
     * Ch1→Vol1, Ch2→Vol1, Ch3→Vol2 → inchangé
     */
    public function testChaptersWithExplicitVolumesArePreserved(): void
    {
        $manga = $this->makeManga();
        $this->client->addFeed('manga-123', [
            $this->chapterEntry('1', 'en', '1'),
            $this->chapterEntry('2', 'en', '1'),
            $this->chapterEntry('3', 'en', '2'),
        ]);

        $this->service->synchronizeChapters($manga);

        $chapters = $this->indexedByNumber($manga->pullNewChapters());

        $this->assertSame(1, $chapters[1.0]->getVolume());
        $this->assertSame(1, $chapters[2.0]->getVolume());
        $this->assertSame(2, $chapters[3.0]->getVolume());
    }

    /**
     * La version française est prioritaire sur l'anglaise
     *
     * Même chapitre disponible EN (volume 1) et FR (volume 2) → FR gagne
     */
    public function testFrenchChaptersTakePriorityOverEnglish(): void
    {
        $manga = $this->makeManga();
        $this->client->addFeed('manga-123', [
            $this->chapterEntry('1', 'en', '1'),
            $this->chapterEntry('1', 'fr', '2'),
        ]);

        $this->service->synchronizeChapters($manga);

        $chapters = $this->indexedByNumber($manga->pullNewChapters());

        $this->assertCount(1, $chapters, 'Un seul chapitre 1 doit exister');
        $this->assertSame(2, $chapters[1.0]->getVolume(), 'La version FR (Vol2) doit prendre la priorité');
    }

    /**
     * Seuls les nouveaux chapitres sont sauvegardés (pas les doublons)
     *
     * Ch1 déjà en DB + Ch2 nouveau → seul Ch2 est retourné
     */
    public function testOnlyNewChaptersAreSaved(): void
    {
        $manga = $this->makeManga();

        // Pré-peupler la DB avec Ch1
        $existingChapter = new Chapter(
            new ChapterId('existing-uuid'),
            new MangaId('manga-id'),
            1.0,
            'Chapter 1',
            1,
            true
        );
        $manga->addChapter($existingChapter);
        $this->repository->save($manga);

        // Feed contient Ch1 (déjà en DB) et Ch2 (nouveau)
        $this->client->addFeed('manga-123', [
            $this->chapterEntry('1', 'en', '1'),
            $this->chapterEntry('2', 'en', '1'),
        ]);

        $newChapterIds = $this->service->synchronizeChapters($manga);

        $this->assertCount(1, $newChapterIds, 'Seul Ch2 doit être retourné comme nouveau');

        $newChapters = $manga->pullNewChapters();
        $this->assertCount(1, $newChapters);
        $this->assertSame(2.0, $newChapters[0]->getNumber(), 'Le nouveau chapitre doit être Ch2');
    }

    /**
     * @param Chapter[] $chapters
     * @return array<float, Chapter>
     */
    private function indexedByNumber(array $chapters): array
    {
        $result = [];
        foreach ($chapters as $chapter) {
            $result[$chapter->getNumber()] = $chapter;
        }
        return $result;
    }
}
