<?php

namespace App\Doctor\Repository;

use App\Doctor\Entity\Doctor;
use App\Doctor\Entity\DoctorAddress;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

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

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

    public function remove(DoctorAddress $address, bool $flush = true): void
    {
        $this->getEntityManager()->remove($address);
        if ($flush) {
            $this->getEntityManager()->flush();
        }
    }

    public function findByUuidAndClinic(string $uuid, int $clinicId): ?DoctorAddress
    {
        return $this->createQueryBuilder('a')
            ->where('a.uuid = :uuid')
            ->andWhere('a.clinicId = :clinicId')
            ->setParameter('uuid', $uuid)
            ->setParameter('clinicId', $clinicId)
            ->getQuery()
            ->getOneOrNullResult();
    }

    /**
     * آدرس‌های یک محیط با جفت (entity_type, entity_id) — همان واژگانی که Room و منابع
     * تسک ۰۲ با آن ذخیره می‌شوند.
     *
     * قرینهٔ findForContext() است ولی Doctor لازم ندارد: در محیط کلینیک، آن متد
     * پارامتر doctor را نادیده می‌گیرد و مجبور کردن فراخوان به ساختن یک Doctor
     * الکی، همان نوع کدی است که بعداً کسی با getReference() پرش می‌کند.
     *
     * @return DoctorAddress[]
     */
    public function findForEntityPair(string $entityType, int $entityId): array
    {
        $qb = $this->createQueryBuilder('a');

        if ($entityType === 'clinic') {
            $qb->where('a.clinicId = :entityId')
                ->andWhere('a.type = :type')
                ->setParameter('type', DoctorAddress::TYPE_CLINIC);
        } else {
            $qb->where('IDENTITY(a.doctor) = :entityId')
                ->andWhere('a.type = :type')
                ->setParameter('type', DoctorAddress::TYPE_PERSONAL);
        }

        return $qb->setParameter('entityId', $entityId)
            ->orderBy('a.id', 'ASC')
            ->getQuery()
            ->getResult();
    }

    /**
     * یک آدرس با uuid، محدود به محیط داده‌شده.
     *
     * `doctor_addresses` جفت محیط ندارد (عمداً — در GlobalTables::ENTITIES ثبت شده)
     * پس TenantFilter رویش اعمال نمی‌شود و `findOneBy(['uuid' => …])` آدرس محیط دیگر
     * را هم برمی‌گرداند. هر مسیری که uuid آدرس را از request می‌گیرد باید از این
     * متد یا از {@see \App\Branch\Service\BranchResolver} رد شود.
     */
    public function findByUuidForEntityPair(string $uuid, string $entityType, int $entityId): ?DoctorAddress
    {
        $qb = $this->createQueryBuilder('a')
            ->where('a.uuid = :uuid')
            ->setParameter('uuid', $uuid);

        if ($entityType === 'clinic') {
            $qb->andWhere('a.clinicId = :entityId')
                ->andWhere('a.type = :type')
                ->setParameter('type', DoctorAddress::TYPE_CLINIC);
        } else {
            $qb->andWhere('IDENTITY(a.doctor) = :entityId')
                ->andWhere('a.type = :type')
                ->setParameter('type', DoctorAddress::TYPE_PERSONAL);
        }

        return $qb->setParameter('entityId', $entityId)
            ->getQuery()
            ->getOneOrNullResult();
    }

    public function findOneByClinic(int $clinicId): ?DoctorAddress
    {
        return $this->createQueryBuilder('a')
            ->where('a.clinicId = :clinicId')
            ->setParameter('clinicId', $clinicId)
            ->orderBy('a.id', 'ASC')
            ->setMaxResults(1)
            ->getQuery()
            ->getOneOrNullResult();
    }

    public function countByClinic(int $clinicId): int
    {
        return (int) $this->createQueryBuilder('a')
            ->select('COUNT(a.id)')
            ->where('a.clinicId = :clinicId')
            ->setParameter('clinicId', $clinicId)
            ->getQuery()
            ->getSingleScalarResult();
    }

    /**
     * آدرس‌های قابل‌انتخاب در یک context. مطب شخصی فقط آدرس‌های شخصی خود پزشک را
     * می‌بیند و کلینیک فقط آدرس‌های خودش — این دو هرگز union نمی‌شوند.
     *
     * @return DoctorAddress[]
     */
    public function findForContext(Doctor $doctor, ?int $clinicId): array
    {
        $qb = $this->createQueryBuilder('a');

        if ($clinicId === null) {
            $qb->where('a.doctor = :doctor')
                ->andWhere('a.type = :personal')
                ->setParameter('doctor', $doctor)
                ->setParameter('personal', DoctorAddress::TYPE_PERSONAL);
        } else {
            $qb->where('a.clinicId = :clinicId')
                ->andWhere('a.type = :clinic')
                ->setParameter('clinicId', $clinicId)
                ->setParameter('clinic', DoctorAddress::TYPE_CLINIC);
        }

        return $qb->orderBy('a.id', 'ASC')->getQuery()->getResult();
    }

    /** @deprecated آدرس‌های دو محیط را union می‌کند؛ از findForContext() استفاده کن. */
    public function findAvailableForDoctor(Doctor $doctor, array $clinicIds): array
    {
        $qb = $this->createQueryBuilder('a');
        $qb->where(
            $qb->expr()->orX(
                $qb->expr()->andX(
                    $qb->expr()->eq('a.doctor', ':doctor'),
                    $qb->expr()->eq('a.type', ':personal')
                ),
                $qb->expr()->andX(
                    $qb->expr()->in('a.clinicId', ':clinicIds'),
                    $qb->expr()->eq('a.type', ':clinic')
                )
            )
        )
        ->setParameter('doctor', $doctor)
        ->setParameter('personal', DoctorAddress::TYPE_PERSONAL)
        ->setParameter('clinicIds', empty($clinicIds) ? [0] : $clinicIds)
        ->setParameter('clinic', DoctorAddress::TYPE_CLINIC);

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