<?php

namespace App\Tests\Domain\Manga\Adapter;

use App\Domain\Manga\Application\Query\MonitoringCriteria;
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
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;

class InMemoryMangaRepository implements MangaRepositoryInterface
{
    /** @var array<string, Manga> */
    private array $mangas = [];

    /** @var array<string, array<Chapter>> */
    private array $chapters = [];

    /** @var array<string, Chapter> */
    private array $chaptersById = [];

    public function findAll(int $page = 1, int $limit = 20, string $sortBy = 'title', string $sortOrder = 'asc'): array
    {
        $sortedMangas = array_values($this->mangas);

        usort($sortedMangas, function (Manga $a, Manga $b) use ($sortBy, $sortOrder) {
            $valueA = $this->getPropertyValue($a, $sortBy);
            $valueB = $this->getPropertyValue($b, $sortBy);

            return $sortOrder === 'asc'
                ? $valueA <=> $valueB
                : $valueB <=> $valueA;
        });

        $offset = ($page - 1) * $limit;

        return array_slice($sortedMangas, $offset, $limit);
    }

    public function count(): int
    {
        return count($this->mangas);
    }

    public function findById(string $id): ?Manga
    {
        return $this->mangas[$id] ?? null;
    }

    public function findBySlug(MangaSlug $slug): ?Manga
    {
        foreach ($this->mangas as $manga) {
            if ($manga->getSlug()->getValue() === $slug->getValue()) {
                return $manga;
            }
        }
        return null;
    }

    public function save(Manga $manga): void
    {
        $this->mangas[$manga->getId()->getValue()] = $manga;

        foreach ($manga->pullNewChapters() as $chapter) {
            $mangaIdValue = $chapter->getMangaId()->getValue();
            if (!isset($this->chapters[$mangaIdValue])) {
                $this->chapters[$mangaIdValue] = [];
            }
            $this->chapters[$mangaIdValue][] = $chapter;
            $this->chaptersById[$chapter->getId()] = $chapter;
        }

        foreach ($manga->pullModifiedChapters() as $chapter) {
            $this->chaptersById[$chapter->getId()] = $chapter;
            $mangaIdValue = $chapter->getMangaId()->getValue();
            if (isset($this->chapters[$mangaIdValue])) {
                foreach ($this->chapters[$mangaIdValue] as $key => $existing) {
                    if ($existing->getId() === $chapter->getId()) {
                        $this->chapters[$mangaIdValue][$key] = $chapter;
                        break;
                    }
                }
            }
        }

        foreach ($manga->pullChaptersToDelete() as $chapter) {
            unset($this->chaptersById[$chapter->getId()]);
            $mangaIdValue = $chapter->getMangaId()->getValue();
            if (isset($this->chapters[$mangaIdValue])) {
                $this->chapters[$mangaIdValue] = array_values(array_filter(
                    $this->chapters[$mangaIdValue],
                    fn (Chapter $c) => $c->getId() !== $chapter->getId()
                ));
            }
        }
    }

    public function delete(Manga $manga): void
    {
        unset($this->mangas[$manga->getId()->getValue()]);
    }

    private function getPropertyValue(Manga $manga, string $property): mixed
    {
        return match($property) {
            'title' => $manga->getTitle()->getValue(),
            'publicationYear' => $manga->getPublicationYear(),
            default => throw new \InvalidArgumentException("Unknown sort property: $property")
        };
    }

    public function findAllChapters(string $mangaId, string $sortOrder = 'desc'): array
    {
        if (!isset($this->chapters[$mangaId])) {
            return [];
        }

        $chapters = $this->chapters[$mangaId];

        usort($chapters, function (Chapter $a, Chapter $b) use ($sortOrder) {
            return $sortOrder === 'desc'
                ? $b->getNumber() <=> $a->getNumber()
                : $a->getNumber() <=> $b->getNumber();
        });

        return $chapters;
    }

    public function findChapters(string $mangaId, int $page = 1, int $limit = 20, string $sortOrder = 'desc'): array
    {
        if (!isset($this->chapters[$mangaId])) {
            return [];
        }

        $chapters = $this->chapters[$mangaId];

        usort($chapters, function (Chapter $a, Chapter $b) use ($sortOrder) {
            return $sortOrder === 'desc'
                ? $b->getNumber() <=> $a->getNumber()
                : $a->getNumber() <=> $b->getNumber();
        });

        $offset = ($page - 1) * $limit;
        return array_slice($chapters, $offset, $limit);
    }

    public function countChapters(string $mangaId): int
    {
        return count($this->chapters[$mangaId] ?? []);
    }

    public function countAvailableChapters(string $mangaId): int
    {
        return count(array_filter(
            $this->chapters[$mangaId] ?? [],
            fn (Chapter $c) => $c->isAvailable()
        ));
    }

    public function findChapterById(string $id): ?Chapter
    {
        return $this->chaptersById[$id] ?? null;
    }

    public function findVisibleChapterById(string $id): ?Chapter
    {
        $chapter = $this->chaptersById[$id] ?? null;
        if ($chapter && $chapter->isVisible()) {
            return $chapter;
        }
        return null;
    }

    public function findChapterByMangaIdAndNumber(string $mangaId, float $chapterNumber): ?Chapter
    {
        foreach ($this->chaptersById as $chapter) {
            if ($chapter->getMangaId()->getValue() === $mangaId && $chapter->getNumber() === $chapterNumber) {
                return $chapter;
            }
        }
        return null;
    }

    public function findChaptersByMangaIdAndVolume(string $mangaId, int $volume): array
    {
        return array_values(array_filter(
            $this->chaptersById,
            fn (Chapter $chapter) => $chapter->getMangaId()->getValue() === $mangaId && $chapter->getVolume() === $volume
        ));
    }

    public function findVisibleChaptersByMangaIdAndVolume(string $mangaId, int $volume): array
    {
        return array_values(array_filter(
            $this->chaptersById,
            fn (Chapter $chapter) =>
                $chapter->getMangaId()->getValue() === $mangaId &&
                $chapter->getVolume() === $volume &&
                $chapter->isVisible()
        ));
    }

    public function findVisibleChaptersWithPagesByMangaIdAndVolume(string $mangaId, int $volume): array
    {
        return array_values(array_filter(
            $this->chaptersById,
            fn (Chapter $chapter) =>
                $chapter->getMangaId()->getValue() === $mangaId &&
                $chapter->getVolume() === $volume &&
                $chapter->isVisible() &&
                $chapter->isAvailable()
        ));
    }

    public function addChapter(string $mangaId, Chapter $chapter): void
    {
        if (!isset($this->chapters[$mangaId])) {
            $this->chapters[$mangaId] = [];
        }
        $this->chapters[$mangaId][] = $chapter;
        $this->chaptersById[$chapter->getId()] = $chapter;
    }

    public function addChaptersToManga(string $mangaId, int $count): void
    {
        $this->chapters[$mangaId] = [];

        for ($i = 1; $i <= $count; $i++) {
            $chapter = new Chapter(
                id: new ChapterId((string)$i),
                mangaId: new MangaId($mangaId),
                number: (float)$i,
                title: "Chapter $i",
                volume: (int)ceil($i / 10),
                isVisible: true,
                createdAt: new \DateTimeImmutable()
            );
            $this->chapters[$mangaId][] = $chapter;
            $this->chaptersById[$chapter->getId()] = $chapter;
        }
    }

    public function findByExternalId(ExternalId $externalId): ?Manga
    {
        foreach ($this->mangas as $manga) {
            if ($manga->getExternalId() && $manga->getExternalId()->getValue() === $externalId->getValue()) {
                return $manga;
            }
        }
        return null;
    }

    public function clear(): void
    {
        $this->mangas = [];
        $this->chapters = [];
        $this->chaptersById = [];
    }

    public function search(string $query, int $page = 1, int $limit = 20): array
    {
        $filteredMangas = array_filter($this->mangas, function (Manga $manga) use ($query) {
            $searchableFields = [
                $manga->getTitle()->getValue(),
                $manga->getSlug()->getValue(),
                $manga->getAuthor(),
                $manga->getDescription()
            ];

            foreach ($manga->getAlternativeSlugs() as $altSlug) {
                $searchableFields[] = $altSlug;
            }

            foreach ($searchableFields as $field) {
                if (str_contains(strtolower($field), strtolower($query))) {
                    return true;
                }
            }

            return false;
        });

        $sortedMangas = array_values($filteredMangas);
        usort($sortedMangas, fn (Manga $a, Manga $b) => $a->getTitle()->getValue() <=> $b->getTitle()->getValue());

        $offset = ($page - 1) * $limit;
        return array_slice($sortedMangas, $offset, $limit);
    }

    public function countSearch(string $query): int
    {
        return count($this->search($query, 1, PHP_INT_MAX));
    }

    public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array
    {
        if (!isset($this->chapters[$mangaId])) {
            return [];
        }

        $result = [];
        foreach ($this->chapters[$mangaId] as $chapter) {
            if (in_array($chapter->getNumber(), $chapterNumbers)) {
                $result[$chapter->getNumber()] = $chapter;
            }
        }

        return $result;
    }

    public function findByMonitoringCriteria(MonitoringCriteria $criteria): array
    {
        return array_filter(
            array_values($this->mangas),
            function (Manga $manga) use ($criteria) {
                if ($manga->getMonitoringStatus()->isEnabled() !== $criteria->enabled) {
                    return false;
                }

                if ($criteria->lastCheckBefore !== null) {
                    $lastCheck = $manga->getLastMonitoringCheck();
                    if ($lastCheck === null || $lastCheck >= $criteria->lastCheckBefore) {
                        return false;
                    }
                }

                return true;
            }
        );
    }
}
