<?php

namespace App\Domain\Manga\Infrastructure\Persistence;

use App\Domain\Manga\Application\Query\MonitoringCriteria;
use App\Domain\Manga\Domain\Contract\Repository\MangaRepositoryInterface;
use App\Domain\Manga\Domain\Model\Manga as DomainManga;
use App\Domain\Manga\Domain\Model\ValueObject\ExternalId;
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\Domain\Model\ValueObject\MonitoringStatus;
use App\Entity\Manga as EntityManga;
use Doctrine\ORM\EntityManagerInterface;
use App\Domain\Manga\Domain\Model\Chapter;
use App\Domain\Manga\Domain\Model\ValueObject\ChapterId;
use App\Entity\Chapter as EntityChapter;
use DateTime;

readonly class LegacyMangaRepository implements MangaRepositoryInterface
{
    public function __construct(
        private EntityManagerInterface $entityManager
    ) {
    }

    public function findAll(int $page = 1, int $limit = 20, string $sortBy = 'title', string $sortOrder = 'asc'): array
    {
        $offset = ($page - 1) * $limit;

        $queryBuilder = $this->entityManager->createQueryBuilder()
            ->select('m')
            ->from(EntityManga::class, 'm')
            ->orderBy("m.$sortBy", $sortOrder)
            ->setFirstResult($offset)
            ->setMaxResults($limit);

        return array_map(
            fn (EntityManga $entity) => $this->toDomain($entity),
            $queryBuilder->getQuery()->getResult()
        );
    }

    public function count(): int
    {
        return $this->entityManager->createQueryBuilder()
            ->select('COUNT(m.id)')
            ->from(EntityManga::class, 'm')
            ->getQuery()
            ->getSingleScalarResult();
    }

    public function findById(string $id): ?DomainManga
    {
        // Convertir le string ID en integer pour la base de données
        if (!is_numeric($id)) {
            return null;
        }

        $entity = $this->entityManager->find(EntityManga::class, (int) $id);

        return $entity ? $this->toDomain($entity) : null;
    }

    public function findBySlug(MangaSlug $slug): ?DomainManga
    {
        $entity = $this->entityManager->getRepository(EntityManga::class)
            ->findOneBy(['slug' => $slug->getValue()]);

        return $entity ? $this->toDomain($entity) : null;
    }

    public function save(DomainManga $manga): void
    {
        // Check if this is an update (manga has a numeric ID) or a new creation
        $entity = null;
        if ($manga->getId() && $manga->getId()->getValue() && is_numeric($manga->getId()->getValue())) {
            $entity = $this->entityManager->find(EntityManga::class, (int) $manga->getId()->getValue());
        }

        if (!$entity) {
            $entity = new EntityManga();
        }

        $imageUrls = $manga->getImageUrls();
        $fullImageUrl = $imageUrls?->getFull();
        $thumbnailUrl = $imageUrls?->getThumbnail();

        $entity->setTitle($manga->getTitle()->getValue())
            ->setSlug($manga->getSlug()->getValue())
            ->setDescription($manga->getDescription())
            ->setAuthor($manga->getAuthor())
            ->setPublicationYear($manga->getPublicationYear())
            ->setGenres($manga->getGenres())
            ->setStatus($manga->getStatus())
            ->setImageUrl($fullImageUrl ?? null)
            ->setThumbnailUrl($thumbnailUrl ?? null)
            ->setAlternativeSlugs($manga->getAlternativeSlugs())
            ->setMonitored($manga->isMonitoringEnabled())
            ->setLastMonitoringCheck($manga->getLastMonitoringCheck());

        // Only set externalId if it exists (to avoid setting null on update)
        if ($manga->getExternalId()) {
            $entity->setExternalId($manga->getExternalId()->getValue());
        }

        if ($manga->getRating() !== null) {
            $entity->setRating($manga->getRating());
        }

        $this->entityManager->persist($entity);
        $this->entityManager->flush();

        // Met à jour l'ID du modèle du domaine avec l'ID généré par la BDD
        if ($entity->getId()) {
            $manga->updateId(new MangaId((string) $entity->getId()));
        }

        // Handle new chapters added through the aggregate
        foreach ($manga->pullNewChapters() as $chapter) {
            $mangaEntity = $this->entityManager->find(EntityManga::class, (int) $manga->getId()->getValue());
            $chapterEntity = new EntityChapter();
            $chapterEntity->setManga($mangaEntity)
                ->setNumber($chapter->getNumber())
                ->setTitle($chapter->getTitle())
                ->setVolume($chapter->getVolume())
                ->setVisible($chapter->isVisible())
                ->setPagesDirectory($chapter->getPagesDirectory())
                ->setPageCount($chapter->getPageCount());
            $this->entityManager->persist($chapterEntity);
        }

        // Handle chapters modified through the aggregate
        foreach ($manga->pullModifiedChapters() as $chapter) {
            $chapterEntity = $this->entityManager->find(EntityChapter::class, $chapter->getId());
            if ($chapterEntity) {
                $chapterEntity->setVisible($chapter->isVisible())
                    ->setPagesDirectory($chapter->getPagesDirectory())
                    ->setPageCount($chapter->getPageCount())
                    ->setTitle($chapter->getTitle())
                    ->setVolume($chapter->getVolume())
                    ->setCbzPath($chapter->getPagesDirectory());
                $this->entityManager->persist($chapterEntity);
            }
        }

        // Handle chapters deleted through the aggregate
        foreach ($manga->pullChaptersToDelete() as $chapter) {
            $chapterEntity = $this->entityManager->find(EntityChapter::class, $chapter->getId());
            if ($chapterEntity) {
                $this->entityManager->remove($chapterEntity);
            }
        }

        $this->entityManager->flush();
    }

    public function delete(DomainManga $manga): void
    {
        $entity = $this->entityManager->find(EntityManga::class, $manga->getId()->getValue());

        if ($entity) {
            $this->entityManager->remove($entity);
            $this->entityManager->flush();
        }
    }

    public function findChapters(string $mangaId, int $page = 1, int $limit = 20, string $sortOrder = 'desc'): array
    {
        $offset = ($page - 1) * $limit;

        $queryBuilder = $this->entityManager->createQueryBuilder()
            ->select('c')
            ->from(EntityChapter::class, 'c')
            ->where('c.manga = :mangaId')
            ->orderBy('c.number', $sortOrder)
            ->setParameter('mangaId', $mangaId)
            ->setFirstResult($offset)
            ->setMaxResults($limit);

        return array_map(
            fn (EntityChapter $entity) => $this->toChapterDomain($entity),
            $queryBuilder->getQuery()->getResult()
        );
    }

    public function findAllChapters(string $mangaId, string $sortOrder = 'desc'): array
    {
        $queryBuilder = $this->entityManager->createQueryBuilder()
            ->select('c')
            ->from(EntityChapter::class, 'c')
            ->where('c.manga = :mangaId')
            ->orderBy('c.number', $sortOrder)
            ->setParameter('mangaId', $mangaId);

        return array_map(
            fn (EntityChapter $entity) => $this->toChapterDomain($entity),
            $queryBuilder->getQuery()->getResult()
        );
    }

    public function countChapters(string $mangaId): int
    {
        return $this->entityManager->createQueryBuilder()
            ->select('COUNT(c.id)')
            ->from(EntityChapter::class, 'c')
            ->where('c.manga = :mangaId')
            ->setParameter('mangaId', $mangaId)
            ->getQuery()
            ->getSingleScalarResult();
    }

    public function countAvailableChapters(string $mangaId): int
    {
        return $this->entityManager->createQueryBuilder()
            ->select('COUNT(c.id)')
            ->from(EntityChapter::class, 'c')
            ->where('c.manga = :mangaId')
            ->andWhere('c.pagesDirectory IS NOT NULL OR c.cbzPath IS NOT NULL')
            ->setParameter('mangaId', $mangaId)
            ->getQuery()
            ->getSingleScalarResult();
    }

    public function findByExternalId(ExternalId $externalId): ?DomainManga
    {
        $entity = $this->entityManager->getRepository(EntityManga::class)->findOneBy([
            'externalId' => $externalId->getValue()
        ]);

        return $entity ? $this->toDomain($entity) : null;
    }

    public function findChapterById(string $id): ?Chapter
    {
        $entity = $this->entityManager->find(EntityChapter::class, $id);

        return $entity ? $this->toChapterDomain($entity) : null;
    }

    public function findVisibleChapterById(string $id): ?Chapter
    {
        $entity = $this->entityManager->createQueryBuilder()
            ->select('c')
            ->from(EntityChapter::class, 'c')
            ->where('c.id = :id')
            ->andWhere('c.visible = :visible')
            ->setParameter('id', $id)
            ->setParameter('visible', 1)
            ->getQuery()
            ->getOneOrNullResult();

        return $entity ? $this->toChapterDomain($entity) : null;
    }

    public function findChapterByMangaIdAndNumber(string $mangaId, float $chapterNumber): ?Chapter
    {
        $entity = $this->entityManager->createQueryBuilder()
            ->select('c')
            ->from(EntityChapter::class, 'c')
            ->where('c.manga = :mangaId')
            ->andWhere('c.number = :chapterNumber')
            ->setParameter('mangaId', $mangaId)
            ->setParameter('chapterNumber', $chapterNumber)
            ->getQuery()
            ->getOneOrNullResult();

        return $entity ? $this->toChapterDomain($entity) : null;
    }

    public function findChaptersByMangaIdAndVolume(string $mangaId, int $volume): array
    {
        $entities = $this->entityManager->getRepository(EntityChapter::class)
            ->findBy(['manga' => $mangaId, 'volume' => $volume]);

        return array_map([$this, 'toChapterDomain'], $entities);
    }

    public function findVisibleChaptersByMangaIdAndVolume(string $mangaId, int $volume): array
    {
        $entities = $this->entityManager->getRepository(EntityChapter::class)
            ->findBy(['manga' => $mangaId, 'volume' => $volume, 'visible' => true]);

        return array_map([$this, 'toChapterDomain'], $entities);
    }

    public function findVisibleChaptersWithPagesByMangaIdAndVolume(string $mangaId, int $volume): array
    {
        $entities = $this->entityManager->createQueryBuilder()
            ->select('c')
            ->from(EntityChapter::class, 'c')
            ->where('c.manga = :mangaId')
            ->andWhere('c.volume = :volume')
            ->andWhere('c.visible = true')
            ->andWhere('c.pagesDirectory IS NOT NULL OR c.cbzPath IS NOT NULL')
            ->setParameter('mangaId', $mangaId)
            ->setParameter('volume', $volume)
            ->orderBy('c.number', 'ASC')
            ->getQuery()
            ->getResult();

        return array_map([$this, 'toChapterDomain'], $entities);
    }

    public function search(string $query, int $page = 1, int $limit = 20): array
    {
        $offset = ($page - 1) * $limit;

        // Utiliser une requête native pour supporter la recherche dans le champ JSON AlternativeSlugs
        $sql = "SELECT m.* FROM manga m
                WHERE m.title LIKE :query
                OR m.slug LIKE :query
                OR CAST(m.alternative_slugs AS TEXT) LIKE :query
                ORDER BY m.title ASC
                LIMIT :limit OFFSET :offset";

        $rsm = new \Doctrine\ORM\Query\ResultSetMapping();
        $rsm->addEntityResult(EntityManga::class, 'm');
        $rsm->addFieldResult('m', 'id', 'id');
        $rsm->addFieldResult('m', 'title', 'title');
        $rsm->addFieldResult('m', 'slug', 'slug');
        $rsm->addFieldResult('m', 'image_url', 'imageUrl');
        $rsm->addFieldResult('m', 'publication_year', 'publicationYear');
        $rsm->addFieldResult('m', 'description', 'description');
        $rsm->addFieldResult('m', 'genres', 'genres');
        $rsm->addFieldResult('m', 'created_at', 'createdAt');
        $rsm->addFieldResult('m', 'rating', 'rating');
        $rsm->addFieldResult('m', 'author', 'author');
        $rsm->addFieldResult('m', 'external_id', 'externalId');
        $rsm->addFieldResult('m', 'status', 'status');
        $rsm->addFieldResult('m', 'thumbnail_url', 'thumbnailUrl');
        $rsm->addFieldResult('m', 'monitored', 'monitored');
        $rsm->addFieldResult('m', 'last_monitoring_check', 'lastMonitoringCheck');
        $rsm->addFieldResult('m', 'alternative_slugs', 'AlternativeSlugs');

        $nativeQuery = $this->entityManager->createNativeQuery($sql, $rsm);
        $nativeQuery->setParameter('query', '%' . $query . '%');
        $nativeQuery->setParameter('limit', $limit);
        $nativeQuery->setParameter('offset', $offset);

        return array_map(
            fn (EntityManga $entity) => $this->toDomain($entity),
            $nativeQuery->getResult()
        );
    }

    public function countSearch(string $query): int
    {
        // Utiliser une requête native pour supporter la recherche dans le champ JSON AlternativeSlugs
        $sql = "SELECT COUNT(m.id) FROM manga m
                WHERE m.title LIKE :query
                OR m.slug LIKE :query
                OR m.author LIKE :query
                OR m.description LIKE :query
                OR CAST(m.alternative_slugs AS TEXT) LIKE :query";

        $conn = $this->entityManager->getConnection();
        $stmt = $conn->prepare($sql);
        $result = $stmt->executeQuery(['query' => '%' . $query . '%']);

        return (int) $result->fetchOne();
    }

    /**
     * @param float[] $chapterNumbers
     * @return array<float, Chapter>
     */
    public function findExistingChaptersByNumbers(string $mangaId, array $chapterNumbers): array
    {
        $queryBuilder = $this->entityManager->createQueryBuilder()
            ->select('c')
            ->from(EntityChapter::class, 'c')
            ->where('c.manga = :mangaId')
            ->andWhere('c.number IN (:chapterNumbers)')
            ->setParameter('mangaId', $mangaId)
            ->setParameter('chapterNumbers', array_map('floatval', $chapterNumbers));

        $chapters = $queryBuilder->getQuery()->getResult();

        $chaptersByNumber = [];
        foreach ($chapters as $chapter) {
            $chaptersByNumber[(float) $chapter->getNumber()] = $this->toChapterDomain($chapter);
        }

        return $chaptersByNumber;
    }

    public function findByMonitoringCriteria(MonitoringCriteria $criteria): array
    {
        $queryBuilder = $this->entityManager->createQueryBuilder()
            ->select('m')
            ->from(EntityManga::class, 'm')
            ->where('m.monitored = :enabled')
            ->setParameter('enabled', $criteria->enabled);

        if ($criteria->lastCheckBefore) {
            $queryBuilder->andWhere('(m.lastMonitoringCheck IS NULL OR m.lastMonitoringCheck < :lastCheckBefore)')
                ->setParameter('lastCheckBefore', $criteria->lastCheckBefore);
        }

        return array_map(
            fn (EntityManga $entity) => $this->toDomain($entity),
            $queryBuilder->getQuery()->getResult()
        );
    }

    private function toDomain(EntityManga $entity): DomainManga
    {
        return new DomainManga(
            id: new MangaId((string) $entity->getId()),
            title: new MangaTitle($entity->getTitle()),
            slug: new MangaSlug($entity->getSlug()),
            description: $entity->getDescription() ?? '',
            author: $entity->getAuthor() ?? '',
            publicationYear: $entity->getPublicationYear() ?? 0,
            genres: $entity->getGenres() ?? [],
            status: $entity->getStatus() ?? '',
            externalId: $entity->getExternalId() ? new ExternalId($entity->getExternalId()) : null,
            imageUrl: $entity->getImageUrl(),
            rating: $entity->getRating(),
            imageUrls: $entity->getImageUrl() ? new ImageUrls($entity->getImageUrl() ?? '', $entity->getThumbnailUrl() ?? '') : null,
            alternativeSlugs: $entity->getAlternativeSlugs() ?? [],
            createdAt: $entity->getCreatedAt(),
            monitoringStatus: $entity->isMonitored() ? MonitoringStatus::enabled() : MonitoringStatus::disabled()
        );
    }

    private function toChapterDomain(EntityChapter $entity): Chapter
    {
        return new Chapter(
            id: new ChapterId((string) $entity->getId()),
            mangaId: new MangaId((string) $entity->getManga()->getId()),
            number: $entity->getNumber(),
            title: $entity->getTitle(),
            volume: $entity->getVolume(),
            isVisible: $entity->isVisible(),
            // Fallback to cbzPath during transition (Phase 4 will drop cbzPath column)
            pagesDirectory: $entity->getPagesDirectory() ?? $entity->getCbzPath(),
            pageCount: $entity->getPageCount(),
        );
    }
}
