<?php

namespace App\Domain\Manga\Infrastructure\Provider;

use App\Domain\Manga\Domain\Contract\Client\MangadexClientInterface;
use App\Domain\Manga\Domain\Contract\Provider\MangaProviderInterface;
use App\Domain\Manga\Domain\Model\Manga;
use App\Domain\Manga\Domain\Model\MangaCollection;
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 Ramsey\Uuid\Uuid;
use Symfony\Component\String\Slugger\SluggerInterface;

readonly class MangadexProvider implements MangaProviderInterface
{
    public function __construct(
        private MangadexClientInterface $client,
        private SluggerInterface $slugger
    ) {
    }

    public function search(string $title): MangaCollection
    {
        $results = $this->client->searchManga($title);

        if (empty($results['data'])) {
            return new MangaCollection([]);
        }

        $mangas = $this->createMangasFromResults($results['data']);
        $this->enrichWithRatings($mangas);

        usort($mangas, fn ($a, $b) => ($b->getRating() ?? 0) <=> ($a->getRating() ?? 0));

        return new MangaCollection($mangas);
    }

    /**
     * @param array<mixed> $results
     * @return Manga[]
     */
    private function createMangasFromResults(array $results): array
    {
        $mangas = [];
        foreach ($results as $result) {
            $manga = $this->createMangaFromResult($result);
            if ($manga !== null) {
                $mangas[] = $manga;
            }
        }

        return $mangas;
    }

    private function createMangaFromResult(array $result): ?Manga
    {
        try {
            $attributes = $result['attributes'];
            $title = $attributes['title']['en']
                ?? $attributes['title']['fr']
                ?? $attributes['title']['ja-ro']
                ?? $attributes['title']['ko-ro']
                ?? $attributes['title']['zh-ro']
                ?? (!empty($attributes['title']) ? reset($attributes['title']) : null);

            if (!$title) {
                return null;
            }

            $genres = array_map(
                fn ($tag) => $tag['attributes']['name']['en'],
                $attributes['tags']
            );

            $author = '';
            $imageUrl = null;
            foreach ($result['relationships'] as $relationship) {
                if ($relationship['type'] === 'author') {
                    $author = $relationship['attributes']['name'];
                }
                if ($relationship['type'] === 'cover_art') {
                    $imageUrl = sprintf(
                        'https://uploads.mangadex.org/covers/%s/%s.512.jpg',
                        $result['id'],
                        $relationship['attributes']['fileName']
                    );
                }
            }

            return new Manga(
                id: new MangaId((string) Uuid::uuid4()),
                title: new MangaTitle($title),
                slug: new MangaSlug($this->slugger->slug($title)->lower()),
                description: $attributes['description']['fr'] ?? $attributes['description']['en'] ?? '',
                author: $author,
                publicationYear: $attributes['year'] ?? 0,
                genres: $genres,
                status: $attributes['status'],
                externalId: new ExternalId($result['id']),
                imageUrl: $imageUrl,
                rating: null,
                imageUrls: null,
                createdAt: new \DateTimeImmutable(),
            );
        } catch (\Exception $e) {
            return null;
        }
    }

    /**
     * @param Manga[] $mangas
     */
    private function enrichWithRatings(array $mangas): void
    {
        $externalIds = array_map(
            fn (Manga $manga) => $manga->getExternalId()->getValue(),
            $mangas
        );

        try {
            $ratings = $this->client->getMangaRatings($externalIds);
        } catch (\Exception $e) {
            return;
        }

        if (isset($ratings['statistics'])) {
            foreach ($mangas as $manga) {
                $externalId = $manga->getExternalId()->getValue();
                if (isset($ratings['statistics'][$externalId]['rating']['average'])) {
                    $manga->setRating($ratings['statistics'][$externalId]['rating']['average']);
                }
            }
        }
    }

    public function discover(array $sourceExternalIds): MangaCollection
    {
        if (empty($sourceExternalIds)) {
            return new MangaCollection([]);
        }

        // Compter les votes : un manga recommandé par plusieurs sources est plus pertinent.
        // On conserve aussi la position d'apparition pour départager les ex-aequo.
        $votes = [];
        $firstPosition = [];
        $resultsById = [];
        $position = 0;

        foreach ($sourceExternalIds as $externalId) {
            try {
                $response = $this->client->getMangaRecommendations($externalId);
                foreach ($response['data'] ?? [] as $result) {
                    $id = $result['id'];
                    $votes[$id] = ($votes[$id] ?? 0) + 1;
                    if (!isset($firstPosition[$id])) {
                        $firstPosition[$id] = $position++;
                        $resultsById[$id] = $result;
                    }
                }
            } catch (\Exception) {
                continue;
            }
        }

        if (empty($resultsById)) {
            return new MangaCollection([]);
        }

        // Trier : votes décroissants (multi-sources = plus pertinent), puis position croissante (score API)
        uksort($resultsById, function (string $a, string $b) use ($votes, $firstPosition): int {
            $voteDiff = $votes[$b] - $votes[$a];
            if ($voteDiff !== 0) {
                return $voteDiff;
            }

            return $firstPosition[$a] <=> $firstPosition[$b];
        });

        $mangas = $this->createMangasFromResults(array_values($resultsById));
        $this->enrichWithRatings($mangas);

        return new MangaCollection($mangas);
    }

    public function findByExternalId(ExternalId $externalId): ?Manga
    {
        try {
            $result = $this->client->getManga($externalId->getValue());

            if (!isset($result['data'])) {
                return null;
            }

            $manga = $this->createMangaFromResult($result['data']);

            if ($manga) {
                $this->enrichWithRatings([$manga]);
            }

            return $manga;
        } catch (\Exception) {
            return null;
        }
    }
}
