<?php

namespace App\Payment\Repository;

use App\Appointment\Entity\Appointment;
use App\Auth\Entity\User;
use App\Payment\Entity\Payment;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

class PaymentRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, Payment::class);
    }

    public function findByUuid(string $uuid): ?Payment
    {
        return $this->findOneBy(['uuid' => $uuid]);
    }

    /** @return Payment[] */
    public function findByUser(User $user, ?string $status = null, int $page = 1, int $limit = 20): array
    {
        $qb = $this->createQueryBuilder('p')
            ->where('p.user = :user')->setParameter('user', $user)
            ->orderBy('p.createdAt', 'DESC')
            ->setFirstResult(($page - 1) * $limit)
            ->setMaxResults($limit);

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

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

    public function countByUser(User $user, ?string $status = null): int
    {
        $qb = $this->createQueryBuilder('p')
            ->select('COUNT(p.id)')
            ->where('p.user = :user')->setParameter('user', $user);

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

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

    public function findByOrderId(string $orderId): ?Payment
    {
        return $this->findOneBy(['orderId' => $orderId]);
    }

    public function findByReferenceId(string $referenceId): ?Payment
    {
        return $this->findOneBy(['referenceId' => $referenceId]);
    }

    /**
     * قفل بدبینانه روی ردیف پرداخت (باید داخل یک transaction فعال صدا زده شود).
     * برای جلوگیری از verify هم‌زمانِ دو callback (race / double-verify).
     */
    public function findByOrderIdForUpdate(string $orderId): ?Payment
    {
        return $this->createQueryBuilder('p')
            ->where('p.orderId = :o')->setParameter('o', $orderId)
            ->getQuery()
            ->setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)
            ->getOneOrNullResult();
    }

    public function findPendingByAppointment(Appointment $appointment): ?Payment
    {
        return $this->findOneBy([
            'appointment' => $appointment,
            'status'      => Payment::STATUS_PENDING,
        ]);
    }

    /**
     * Batch variant of findPendingByAppointment: all pending payments for the
     * given appointments in ONE query, keyed by appointment id. Avoids the N+1
     * in AppointmentExpiryService when many bookings expire at once.
     *
     * @param Appointment[] $appointments
     * @return array<int, Payment> appointment id => pending Payment
     */
    public function findPendingByAppointments(array $appointments): array
    {
        if ($appointments === []) {
            return [];
        }

        $payments = $this->createQueryBuilder('p')
            ->andWhere('p.appointment IN (:appointments)')
            ->andWhere('p.status = :status')
            ->setParameter('appointments', $appointments)
            ->setParameter('status', Payment::STATUS_PENDING)
            ->getQuery()
            ->getResult();

        $byAppointment = [];
        foreach ($payments as $payment) {
            $byAppointment[$payment->getAppointment()->getId()] = $payment;
        }

        return $byAppointment;
    }

    public function save(Payment $entity, bool $flush = true): void
    {
        $this->getEntityManager()->persist($entity);
        if ($flush) $this->getEntityManager()->flush();
    }
}
