<?php

namespace App\Repository;

use App\Entity\Announcement;
use App\Entity\Conversation;
use App\Entity\LocationProvider;
use App\Entity\Message;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @extends ServiceEntityRepository<Conversation>
 */
class ConversationRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Conversation::class);
    }

    /**
     * Trouve ou crée une conversation entre deux utilisateurs pour une annonce
     */
    public function findOrCreateConversation(Announcement $announcement, User $user1, User $user2): Conversation
    {
        // S'assurer que user1 a un ID plus petit que user2 pour la cohérence
        if ($user1->getId() > $user2->getId()) {
            $temp = $user1;
            $user1 = $user2;
            $user2 = $temp;
        }

        $conversation = $this->createQueryBuilder('c')
            ->where('c.announcement = :announcement')
            ->andWhere('c.user1 = :user1')
            ->andWhere('c.user2 = :user2')
            ->setParameter('announcement', $announcement)
            ->setParameter('user1', $user1)
            ->setParameter('user2', $user2)
            ->getQuery()
            ->getOneOrNullResult();

        if (!$conversation) {
            $conversation = new Conversation();
            $conversation->setAnnouncement($announcement);
            $conversation->setUser1($user1);
            $conversation->setUser2($user2);

            $this->getEntityManager()->persist($conversation);
            $this->getEntityManager()->flush();
        }

        return $conversation;
    }

    /**
     * Conversation entre deux utilisateurs, rattachée à l'une ou l'autre de deux annonces,
     * SANS filtrer sur isActive ni sur les flags de suppression (utilisé pour ressusciter
     * une conversation supprimée lorsque l'utilisateur démarre une nouvelle conversation via un matching).
     */
    public function findAnyBetweenUsersForEitherAnnouncement(
        User $viewer,
        User $counterpart,
        Announcement $announcementA,
        Announcement $announcementB
    ): ?Conversation {
        if ($viewer->getId() === null || $counterpart->getId() === null) {
            return null;
        }

        if ($viewer->getId() > $counterpart->getId()) {
            $userLow = $counterpart;
            $userHigh = $viewer;
        } else {
            $userLow = $viewer;
            $userHigh = $counterpart;
        }

        return $this->createQueryBuilder('c')
            ->where('c.user1 = :userLow')
            ->andWhere('c.user2 = :userHigh')
            ->andWhere('(c.announcement = :announcementA OR c.announcement = :announcementB)')
            ->setParameter('userLow', $userLow)
            ->setParameter('userHigh', $userHigh)
            ->setParameter('announcementA', $announcementA)
            ->setParameter('announcementB', $announcementB)
            ->orderBy('c.updatedAt', 'DESC')
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult();
    }

    /**
     * Conversation active entre deux utilisateurs, rattachée à l'une ou l'autre de deux annonces (ex. paire d'un matching).
     */
    public function findActiveBetweenUsersForEitherAnnouncement(
        User $viewer,
        User $counterpart,
        Announcement $announcementA,
        Announcement $announcementB
    ): ?Conversation {
        if ($viewer->getId() === null || $counterpart->getId() === null) {
            return null;
        }

        if ($viewer->getId() > $counterpart->getId()) {
            $userLow = $counterpart;
            $userHigh = $viewer;
        } else {
            $userLow = $viewer;
            $userHigh = $counterpart;
        }

        return $this->createQueryBuilder('c')
            ->where('c.user1 = :userLow')
            ->andWhere('c.user2 = :userHigh')
            ->andWhere('c.isActive = :active')
            ->andWhere('(c.announcement = :announcementA OR c.announcement = :announcementB)')
            ->setParameter('userLow', $userLow)
            ->setParameter('userHigh', $userHigh)
            ->setParameter('active', true)
            ->setParameter('announcementA', $announcementA)
            ->setParameter('announcementB', $announcementB)
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult();
    }

    /**
     * Récupère toutes les conversations d'un utilisateur.
     *
     * @param LocationProvider|null $providerScope Prestataire actif (compte lié ou en-tête X-Provider-Context-Id).
     * @param bool $providerMessagingScope true = inbox partenaire uniquement ; false = messagerie personnelle Sherlook.
     */
    public function findUserConversations(
        User $user,
        ?LocationProvider $providerScope = null,
        bool $providerMessagingScope = false,
    ): array {
        $qb = $this->createQueryBuilder('c')
            ->leftJoin('c.announcement', 'a')
            ->leftJoin('c.user1', 'u1')
            ->leftJoin('c.user2', 'u2')
            ->leftJoin('c.messages', 'm')
            ->addSelect('a', 'u1', 'u2', 'm')
            ->andWhere('c.isActive = true')
            ->orderBy('c.updatedAt', 'DESC')
            ->distinct();

        if ($providerMessagingScope) {
            if ($providerScope === null) {
                return [];
            }

            $qb
                ->andWhere('a.locationProvider = :provider')
                ->setParameter('provider', $providerScope);

            return $qb->getQuery()->getResult();
        }

        $qb
            ->andWhere('((c.user1 = :user AND c.deletedByUser1 = false) OR (c.user2 = :user AND c.deletedByUser2 = false))')
            ->setParameter('user', $user);

        return $qb->getQuery()->getResult();
    }

    /**
     * Récupère une conversation spécifique si l'utilisateur y a accès.
     *
     * @param LocationProvider|null $providerScope Voir {@see findUserConversations()}
     * @param bool $providerMessagingScope Voir {@see findUserConversations()}
     */
    public function findConversationForUser(
        int $conversationId,
        User $user,
        ?LocationProvider $providerScope = null,
        bool $providerMessagingScope = false,
    ): ?Conversation {
        $qb = $this->createQueryBuilder('c')
            ->leftJoin('c.announcement', 'a')
            ->leftJoin('c.user1', 'u1')
            ->leftJoin('c.user2', 'u2')
            ->leftJoin('c.messages', 'm')
            ->leftJoin('m.sender', 'ms')
            ->leftJoin('m.recipient', 'mr')
            ->addSelect('a', 'u1', 'u2', 'm', 'ms', 'mr')
            ->where('c.id = :conversationId')
            ->andWhere('c.isActive = true')
            ->setParameter('conversationId', $conversationId);

        if ($providerMessagingScope) {
            if ($providerScope === null) {
                return null;
            }

            $qb
                ->andWhere('a.locationProvider = :provider')
                ->setParameter('provider', $providerScope);

            return $qb->getQuery()->getOneOrNullResult();
        }

        $qb
            ->andWhere('((c.user1 = :user AND c.deletedByUser1 = false) OR (c.user2 = :user AND c.deletedByUser2 = false))')
            ->setParameter('user', $user);

        return $qb->getQuery()->getOneOrNullResult();
    }

    /**
     * Compte le nombre total de messages non lus pour un utilisateur
     */
    /**
     * Conversations actives liées à une annonce (messagerie).
     */
    public function countActiveForAnnouncement(Announcement $announcement): int
    {
        return (int) $this->createQueryBuilder('c')
            ->select('COUNT(c.id)')
            ->where('c.announcement = :announcement')
            ->andWhere('c.isActive = :active')
            ->setParameter('announcement', $announcement)
            ->setParameter('active', true)
            ->getQuery()
            ->getSingleScalarResult();
    }

    /**
     * @param int[] $announcementIds
     *
     * @return array<int, int>
     */
    public function countConversationsGroupedByAnnouncementIds(array $announcementIds): array
    {
        $announcementIds = array_values(array_unique(array_filter($announcementIds, static fn ($id) => is_int($id) && $id > 0)));
        if ($announcementIds === []) {
            return [];
        }

        $rows = $this->createQueryBuilder('c')
            ->select('IDENTITY(c.announcement) AS annId, COUNT(c.id) AS cnt')
            ->where('c.announcement IN (:ids)')
            ->setParameter('ids', $announcementIds)
            ->groupBy('annId')
            ->getQuery()
            ->getArrayResult();

        $counts = array_fill_keys($announcementIds, 0);
        foreach ($rows as $row) {
            $counts[(int) $row['annId']] = (int) $row['cnt'];
        }

        return $counts;
    }

    /**
     * @return Conversation[]
     */
    public function findForAnnouncementAdmin(Announcement $announcement): array
    {
        return $this->createQueryBuilder('c')
            ->leftJoin('c.user1', 'u1')->addSelect('u1')
            ->leftJoin('c.user2', 'u2')->addSelect('u2')
            ->leftJoin('c.messages', 'm')
            ->where('c.announcement = :announcement')
            ->setParameter('announcement', $announcement)
            ->orderBy('c.updatedAt', 'DESC')
            ->getQuery()
            ->getResult();
    }

    /**
     * Messages entrants non lus pour un établissement (tous les comptes staff voient le même compteur).
     */
    public function countUnreadIncomingForProvider(Conversation $conversation, LocationProvider $provider): int
    {
        return (int) $this->getEntityManager()->createQueryBuilder()
            ->select('COUNT(m.id)')
            ->from(Message::class, 'm')
            ->leftJoin('m.sender', 's')
            ->where('m.conversation = :conversation')
            ->andWhere('m.isRead = false')
            ->andWhere('s.locationProvider IS NULL OR s.locationProvider != :provider')
            ->setParameter('conversation', $conversation)
            ->setParameter('provider', $provider)
            ->getQuery()
            ->getSingleScalarResult();
    }

    /**
     * @param LocationProvider|null $providerScope Voir {@see findUserConversations()}
     * @param bool $providerMessagingScope Voir {@see findUserConversations()}
     */
    public function getTotalUnreadCountForUser(
        User $user,
        ?LocationProvider $providerScope = null,
        bool $providerMessagingScope = false,
    ): int {
        $qb = $this->createQueryBuilder('c')
            ->leftJoin('c.messages', 'm')
            ->leftJoin('m.sender', 'ms')
            ->leftJoin('c.announcement', 'a')
            ->where('c.isActive = true');

        $providerIncomingUnreadCase =
            'SUM(CASE WHEN m.isRead = false AND (ms.locationProvider IS NULL OR ms.locationProvider != :provider) THEN 1 ELSE 0 END)';
        $recipientUnreadCase =
            'SUM(CASE WHEN m.recipient = :user AND m.isRead = false THEN 1 ELSE 0 END)';

        if ($providerMessagingScope) {
            if ($providerScope === null) {
                return 0;
            }

            $qb
                ->select($providerIncomingUnreadCase)
                ->andWhere('a.locationProvider = :provider')
                ->setParameter('provider', $providerScope);

            return $this->fetchAggregateCount($qb);
        }

        $qb
            ->select($recipientUnreadCase)
            ->andWhere('((c.user1 = :user AND c.deletedByUser1 = false) OR (c.user2 = :user AND c.deletedByUser2 = false))')
            ->setParameter('user', $user);

        return $this->fetchAggregateCount($qb);
    }

    private function fetchAggregateCount(\Doctrine\ORM\QueryBuilder $qb): int
    {
        try {
            return (int) ($qb->getQuery()->getSingleScalarResult() ?? 0);
        } catch (NoResultException|NonUniqueResultException) {
            return 0;
        }
    }
}

