<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Entity;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

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

    /**
     * @return list<DealTask>
     */
    public function findByDeal(Deal $deal): array
    {
        return $this->findBy(['deal' => $deal], ['status' => 'ASC', 'dueAt' => 'ASC', 'createdAt' => 'DESC']);
    }

    /**
     * @param list<int> $dealIds
     *
     * @return array<int, int>
     */
    public function getOpenOverdueCountsByDealIds(array $dealIds, \DateTimeInterface $now): array
    {
        if ([] === $dealIds) {
            return [];
        }

        $rows = $this->createQueryBuilder('t')
            ->select('IDENTITY(t.deal) AS dealId, COUNT(t.id) AS overdueCount')
            ->andWhere('IDENTITY(t.deal) IN (:dealIds)')
            ->andWhere('t.status = :status')
            ->andWhere('t.dueAt IS NOT NULL')
            ->andWhere('t.dueAt < :now')
            ->setParameter('dealIds', array_values(array_unique($dealIds)))
            ->setParameter('status', 'open')
            ->setParameter('now', $now)
            ->groupBy('t.deal')
            ->getQuery()
            ->getArrayResult();

        $result = [];
        foreach ($rows as $row) {
            $result[(int) $row['dealId']] = (int) $row['overdueCount'];
        }

        return $result;
    }

    /**
     * @return list<DealTask>
     */
    public function findQueue(
        ?Pipeline $pipeline = null,
        ?int $ownerId = null,
        bool $focusOnly = false,
        ?\DateTimeImmutable $now = null,
        ?int $currentUserId = null,
        bool $includeOtherDeals = true,
    ): array {
        $now ??= new \DateTimeImmutable();
        $soon = $now->modify('+3 days');

        $qb = $this->createQueryBuilder('t')
            ->addSelect('d', 's', 'p')
            ->innerJoin('t.deal', 'd')
            ->leftJoin('d.stage', 's')
            ->leftJoin('d.pipeline', 'p')
            ->andWhere('t.status = :status')
            ->setParameter('status', 'open')
            ->addSelect(
                "CASE
                    WHEN t.priority = 'urgent' THEN 0
                    WHEN t.priority = 'high' THEN 1
                    WHEN t.priority = 'normal' THEN 2
                    WHEN t.priority = 'low' THEN 3
                    ELSE 4
                END AS HIDDEN prioritySort"
            )
            ->addSelect('CASE WHEN t.dueAt IS NULL THEN 1 ELSE 0 END AS HIDDEN dueNullSort')
            ->addSelect('CASE WHEN t.reminderAt IS NULL THEN 1 ELSE 0 END AS HIDDEN reminderNullSort')
            ->orderBy('prioritySort', 'ASC')
            ->addOrderBy('dueNullSort', 'ASC')
            ->addOrderBy('t.dueAt', 'ASC')
            ->addOrderBy('reminderNullSort', 'ASC')
            ->addOrderBy('t.reminderAt', 'ASC')
            ->addOrderBy('t.createdAt', 'DESC');

        if ($pipeline instanceof Pipeline) {
            $qb->andWhere('d.pipeline = :pipeline')
                ->setParameter('pipeline', $pipeline);
        }

        if (null !== $ownerId) {
            $qb->andWhere('t.ownerId = :ownerId')
                ->setParameter('ownerId', $ownerId);
        }

        if (!$includeOtherDeals && null !== $currentUserId) {
            $qb->andWhere('d.ownerId = :currentUserId')
                ->setParameter('currentUserId', $currentUserId);
        }

        if ($focusOnly) {
            $qb->andWhere(
                $qb->expr()->orX(
                    't.priority IN (:focusPriorities)',
                    '(t.dueAt IS NOT NULL AND t.dueAt <= :soon)',
                    '(t.reminderAt IS NOT NULL AND t.reminderAt <= :now)'
                )
            )
                ->setParameter('focusPriorities', [DealTask::PRIORITY_HIGH, DealTask::PRIORITY_URGENT])
                ->setParameter('soon', $soon)
                ->setParameter('now', $now);
        }

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

    /**
     * @return list<DealTask>
     */
    public function findQueuePaginated(
        ?Pipeline $pipeline = null,
        ?int $ownerId = null,
        bool $focusOnly = false,
        ?\DateTimeImmutable $now = null,
        ?int $currentUserId = null,
        bool $includeOtherDeals = true,
        int $page = 1,
        int $limit = 30,
    ): array {
        $qb = $this->buildQueueQueryBuilder($pipeline, $ownerId, $focusOnly, $now, $currentUserId, $includeOtherDeals);

        $qb->setFirstResult(($page - 1) * $limit)
            ->setMaxResults($limit);

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

    public function countQueue(
        ?Pipeline $pipeline = null,
        ?int $ownerId = null,
        bool $focusOnly = false,
        ?\DateTimeImmutable $now = null,
        ?int $currentUserId = null,
        bool $includeOtherDeals = true,
    ): int {
        $now ??= new \DateTimeImmutable();
        $soon = $now->modify('+3 days');

        $qb = $this->createQueryBuilder('t')
            ->select('COUNT(t.id)')
            ->innerJoin('t.deal', 'd')
            ->andWhere('t.status = :status')
            ->setParameter('status', 'open');

        if ($pipeline instanceof Pipeline) {
            $qb->andWhere('d.pipeline = :pipeline')
                ->setParameter('pipeline', $pipeline);
        }

        if (null !== $ownerId) {
            $qb->andWhere('t.ownerId = :ownerId')
                ->setParameter('ownerId', $ownerId);
        }

        if (!$includeOtherDeals && null !== $currentUserId) {
            $qb->andWhere('d.ownerId = :currentUserId')
                ->setParameter('currentUserId', $currentUserId);
        }

        if ($focusOnly) {
            $qb->andWhere(
                $qb->expr()->orX(
                    't.priority IN (:focusPriorities)',
                    '(t.dueAt IS NOT NULL AND t.dueAt <= :soon)',
                    '(t.reminderAt IS NOT NULL AND t.reminderAt <= :now)'
                )
            )
                ->setParameter('focusPriorities', [DealTask::PRIORITY_HIGH, DealTask::PRIORITY_URGENT])
                ->setParameter('soon', $soon)
                ->setParameter('now', $now);
        }

        return (int) $qb->getQuery()->getSingleScalarResult();
    }

    private function buildQueueQueryBuilder(
        ?Pipeline $pipeline,
        ?int $ownerId,
        bool $focusOnly,
        ?\DateTimeImmutable $now,
        ?int $currentUserId,
        bool $includeOtherDeals,
    ): \Doctrine\ORM\QueryBuilder {
        $now ??= new \DateTimeImmutable();
        $soon = $now->modify('+3 days');

        $qb = $this->createQueryBuilder('t')
            ->addSelect('d', 's', 'p')
            ->innerJoin('t.deal', 'd')
            ->leftJoin('d.stage', 's')
            ->leftJoin('d.pipeline', 'p')
            ->andWhere('t.status = :status')
            ->setParameter('status', 'open')
            ->addSelect(
                "CASE
                    WHEN t.priority = 'urgent' THEN 0
                    WHEN t.priority = 'high' THEN 1
                    WHEN t.priority = 'normal' THEN 2
                    WHEN t.priority = 'low' THEN 3
                    ELSE 4
                END AS HIDDEN prioritySort"
            )
            ->addSelect('CASE WHEN t.dueAt IS NULL THEN 1 ELSE 0 END AS HIDDEN dueNullSort')
            ->addSelect('CASE WHEN t.reminderAt IS NULL THEN 1 ELSE 0 END AS HIDDEN reminderNullSort')
            ->orderBy('prioritySort', 'ASC')
            ->addOrderBy('dueNullSort', 'ASC')
            ->addOrderBy('t.dueAt', 'ASC')
            ->addOrderBy('reminderNullSort', 'ASC')
            ->addOrderBy('t.reminderAt', 'ASC')
            ->addOrderBy('t.createdAt', 'DESC');

        if ($pipeline instanceof Pipeline) {
            $qb->andWhere('d.pipeline = :pipeline')
                ->setParameter('pipeline', $pipeline);
        }

        if (null !== $ownerId) {
            $qb->andWhere('t.ownerId = :ownerId')
                ->setParameter('ownerId', $ownerId);
        }

        if (!$includeOtherDeals && null !== $currentUserId) {
            $qb->andWhere('d.ownerId = :currentUserId')
                ->setParameter('currentUserId', $currentUserId);
        }

        if ($focusOnly) {
            $qb->andWhere(
                $qb->expr()->orX(
                    't.priority IN (:focusPriorities)',
                    '(t.dueAt IS NOT NULL AND t.dueAt <= :soon)',
                    '(t.reminderAt IS NOT NULL AND t.reminderAt <= :now)'
                )
            )
                ->setParameter('focusPriorities', [DealTask::PRIORITY_HIGH, DealTask::PRIORITY_URGENT])
                ->setParameter('soon', $soon)
                ->setParameter('now', $now);
        }

        return $qb;
    }

    /**
     * @return DealTask[]
     */
    public function findOpenTasks(int $limit = 10, ?int $ownerId = null): array
    {
        $qb = $this->createQueryBuilder('t')
            ->andWhere('t.status = :status')
            ->setParameter('status', 'open')
            ->orderBy('t.dueAt', 'ASC')
            ->setMaxResults($limit);

        if (null !== $ownerId) {
            $qb->andWhere('t.ownerId = :ownerId')
                ->setParameter('ownerId', $ownerId);
        }

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

    public function countOverdueTasks(?\DateTimeInterface $now = null): int
    {
        $now ??= new \DateTimeImmutable();

        return (int) $this->createQueryBuilder('t')
            ->select('COUNT(t.id)')
            ->andWhere('t.status = :status')
            ->andWhere('t.dueAt IS NOT NULL')
            ->andWhere('t.dueAt < :now')
            ->setParameter('status', 'open')
            ->setParameter('now', $now)
            ->getQuery()
            ->getSingleScalarResult();
    }
}
