<?php

declare(strict_types=1);

namespace App\Module\Mail\Infrastructure\Doctrine;

use App\Module\Mail\Domain\Entity\MailFolder;
use App\Module\Mail\Domain\Entity\MailMessage;
use App\Module\Mail\Domain\Repository\MailMessageRepositoryInterface;
use DateTimeImmutable;
use DateTimeInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @extends ServiceEntityRepository<MailMessage>
 */
class DoctrineMailMessageRepository extends ServiceEntityRepository implements MailMessageRepositoryInterface
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, MailMessage::class);
    }

    public function findById(int $id): ?MailMessage
    {
        return $this->find($id);
    }

    public function findByMessageId(string $messageId): ?MailMessage
    {
        return $this->findOneBy(['messageId' => $messageId]);
    }

    public function findByFolderAndUid(MailFolder $folder, int $uid): ?MailMessage
    {
        return $this->findOneBy(['folder' => $folder, 'uid' => $uid]);
    }

    /**
     * @return list<MailMessage>
     */
    public function findByFolderPaginated(MailFolder $folder, int $limit, int $offset): array
    {
        return $this->createQueryBuilder('m')
            ->andWhere('m.folder = :folder')
            ->setParameter('folder', $folder)
            ->orderBy('m.sentAt', 'DESC')
            ->addOrderBy('m.id', 'DESC')
            ->setMaxResults($limit)
            ->setFirstResult($offset)
            ->getQuery()
            ->getResult()
        ;
    }

    public function countUnreadByFolder(MailFolder $folder): int
    {
        return (int) $this->createQueryBuilder('m')
            ->select('COUNT(m.id)')
            ->andWhere('m.folder = :folder')
            ->andWhere('m.isRead = false')
            ->setParameter('folder', $folder)
            ->getQuery()
            ->getSingleScalarResult()
        ;
    }

    public function findMaxUidInFolder(MailFolder $folder): int
    {
        $result = $this->createQueryBuilder('m')
            ->select('MAX(m.uid)')
            ->andWhere('m.folder = :folder')
            ->setParameter('folder', $folder)
            ->getQuery()
            ->getSingleScalarResult()
        ;

        return (int) ($result ?? 0);
    }

    /**
     * @return list<MailMessage>
     */
    public function findLastNByFolder(MailFolder $folder, int $limit): array
    {
        return $this->createQueryBuilder('m')
            ->andWhere('m.folder = :folder')
            ->setParameter('folder', $folder)
            ->orderBy('m.sentAt', 'DESC')
            ->addOrderBy('m.id', 'DESC')
            ->setMaxResults($limit)
            ->getQuery()
            ->getResult()
        ;
    }

    /**
     * @return list<int>
     */
    public function findAllUidsByFolder(MailFolder $folder): array
    {
        $rows = $this->createQueryBuilder('m')
            ->select('m.uid')
            ->andWhere('m.folder = :folder')
            ->setParameter('folder', $folder)
            ->getQuery()
            ->getArrayResult()
        ;

        return array_column($rows, 'uid');
    }

    /**
     * Pagination cursor : retourne $limit messages apres le cursor (sentAt DESC, id DESC).
     * Cursor format : base64url(sentAt_iso8601:id) - null pour la premiere page.
     *
     * @return array{messages: list<MailMessage>, nextCursor: ?string}
     */
    public function findByFolderCursor(MailFolder $folder, int $limit, ?string $cursor): array
    {
        $qb = $this->createQueryBuilder('m')
            ->andWhere('m.folder = :folder')
            ->setParameter('folder', $folder)
            ->orderBy('m.sentAt', 'DESC')
            ->addOrderBy('m.id', 'DESC')
            ->setMaxResults($limit + 1)
        ;

        if (null !== $cursor) {
            $decoded = base64_decode(strtr($cursor, '-_', '+/'), true);
            if (false !== $decoded && str_contains($decoded, ':')) {
                [$sentAtStr, $idStr] = explode(':', $decoded, 2);
                $cursorSentAt        = DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $sentAtStr);
                $cursorId            = (int) $idStr;

                if ($cursorSentAt instanceof DateTimeImmutable) {
                    $qb
                        ->andWhere('m.sentAt < :cursorSentAt OR (m.sentAt = :cursorSentAt AND m.id < :cursorId)')
                        ->setParameter('cursorSentAt', $cursorSentAt)
                        ->setParameter('cursorId', $cursorId)
                    ;
                }
            }
        }

        /** @var list<MailMessage> $results */
        $results    = $qb->getQuery()->getResult();
        $hasMore    = count($results) > $limit;
        $messages   = $hasMore ? array_slice($results, 0, $limit) : $results;
        $nextCursor = null;

        if ($hasMore && [] !== $messages) {
            $last       = end($messages);
            $raw        = $last->getSentAt()->format(DateTimeInterface::ATOM).':'.$last->getId();
            $nextCursor = rtrim(strtr(base64_encode($raw), '+/', '-_'), '=');
        }

        return ['messages' => $messages, 'nextCursor' => $nextCursor];
    }
}
