<?php

namespace App\Patient\Repository;

use App\Auth\Entity\User;
use App\Patient\Entity\PatientRecord;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

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

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

    public function findByEntityAndUser(string $entityType, int $entityId, User $user): ?PatientRecord
    {
        return $this->findOneBy([
            'entityType' => $entityType,
            'entityId'   => $entityId,
            'user'       => $user,
        ]);
    }

    /**
     * @param array<string, mixed> $filters tags(string[] tenant-tag uuids), gender,
     *        insurance_id, admitted_from/admitted_to (unix), service_status
     *        (pending|completed), has_debt(bool)
     * @return list<PatientRecord>
     */
    public function findByEntity(string $entityType, int $entityId, int $page = 1, int $limit = 20, ?string $search = null, array $filters = [], ?array $restrictToDoctorIds = null): array
    {
        $qb = $this->baseQuery($entityType, $entityId);
        $this->applyFilters($qb, $search, $filters);
        $this->applyDoctorRestriction($qb, $entityType, $entityId, $restrictToDoctorIds);

        return $qb->orderBy('r.id', 'DESC')
            ->setFirstResult(($page - 1) * $limit)
            ->setMaxResults($limit)
            ->getQuery()
            ->getResult();
    }

    /** @param array<string, mixed> $filters same shape as {@see findByEntity}. */
    public function countByEntity(string $entityType, int $entityId, ?string $search = null, array $filters = [], ?array $restrictToDoctorIds = null): int
    {
        $qb = $this->baseQuery($entityType, $entityId)->select('COUNT(r.id)');
        $this->applyFilters($qb, $search, $filters);
        $this->applyDoctorRestriction($qb, $entityType, $entityId, $restrictToDoctorIds);

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

    /**
     * آیا این پرونده در دسترسِ محدودشدهٔ این پزشک(ها) هست؟ همان قاعدهٔ لیست، برای یک
     * رکورد — تا detail و list هرگز از هم واگرا نشوند.
     *
     * @param int[]|null $restrictToDoctorIds
     */
    public function isVisibleToDoctors(PatientRecord $record, ?array $restrictToDoctorIds): bool
    {
        if ($restrictToDoctorIds === null) {
            return true;
        }

        if ($restrictToDoctorIds === []) {
            return false;
        }

        $qb = $this->createQueryBuilder('r')
            ->select('COUNT(r.id)')
            ->where('r.id = :recordId')
            ->setParameter('recordId', $record->getId());

        $this->applyDoctorRestriction($qb, $record->getEntityType(), $record->getEntityId(), $restrictToDoctorIds);

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

    /**
     * پزشکِ عضو فقط بیمارانِ خودش را می‌بیند. پروندهٔ کلینیکی ستون پزشک ندارد
     * (یکتایی clinic+user)، پس رابطه از نوبت‌های همان پزشک در همان کلینیک می‌آید —
     * نه از session، چون مراجعهٔ دستی اصلاً پزشک ثبت‌شده ندارد.
     *
     * @param int[]|null $restrictToDoctorIds
     */
    private function applyDoctorRestriction(
        \Doctrine\ORM\QueryBuilder $qb,
        string $entityType,
        ?int $entityId,
        ?array $restrictToDoctorIds,
    ): void {
        if ($restrictToDoctorIds === null || $entityType !== 'clinic') {
            return;
        }

        if ($restrictToDoctorIds === []) {
            $qb->andWhere('1 = 0');

            return;
        }

        $qb->andWhere(
            $qb->expr()->exists(
                'SELECT 1 FROM App\Appointment\Entity\Appointment ra
                 WHERE ra.user = r.user
                   AND IDENTITY(ra.clinic) = :restrictClinicId
                   AND IDENTITY(ra.doctor) IN (:restrictDoctorIds)'
            )
        )
        ->setParameter('restrictClinicId', $entityId)
        ->setParameter('restrictDoctorIds', $restrictToDoctorIds);
    }

    private function baseQuery(string $entityType, int $entityId): \Doctrine\ORM\QueryBuilder
    {
        return $this->createQueryBuilder('r')
            ->join('r.user', 'u')
            ->where('r.entityType = :type')
            ->andWhere('r.entityId = :id')
            ->setParameter('type', $entityType)
            ->setParameter('id', $entityId);
    }

    /**
     * Shared search + advanced filters for the patient records list, applied to
     * both the page query and its count so totals stay consistent.
     * @param array<string, mixed> $filters
     */
    private function applyFilters(\Doctrine\ORM\QueryBuilder $qb, ?string $search, array $filters): void
    {
        if ($search !== null && $search !== '') {
            $qb->andWhere('u.realName LIKE :search OR u.mobileNumber LIKE :search OR u.nationalCode LIKE :search')
               ->setParameter('search', '%' . $search . '%');
        }

        // برچسب‌ها — رکوردهایی که حداقل یکی از تگ‌های انتخاب‌شده را دارند.
        if (!empty($filters['tags'])) {
            $qb->andWhere('r.id IN (SELECT rt.id FROM App\Patient\Entity\PatientRecord rt JOIN rt.tags tg WHERE tg.uuid IN (:tagUuids))')
               ->setParameter('tagUuids', (array) $filters['tags']);
        }

        // جنسیت / نوع بیمه — از UserProfile بیمار (OneToOne با user).
        if (!empty($filters['gender']) || !empty($filters['insurance_id'])) {
            $qb->leftJoin(\App\UserProfile\Entity\UserProfile::class, 'pr', \Doctrine\ORM\Query\Expr\Join::WITH, 'pr.user = u');
            if (!empty($filters['gender'])) {
                $qb->andWhere('pr.gender = :gender')->setParameter('gender', $filters['gender']);
            }
            if (!empty($filters['insurance_id'])) {
                $qb->andWhere('pr.basicInsuranceId = :insId')->setParameter('insId', (int) $filters['insurance_id']);
            }
        }

        // تاریخ پذیرش — تاریخ تشکیل پرونده (record.createdAt).
        if (!empty($filters['admitted_from'])) {
            $qb->andWhere('r.createdAt >= :aFrom')->setParameter('aFrom', (int) $filters['admitted_from']);
        }
        if (!empty($filters['admitted_to'])) {
            $qb->andWhere('r.createdAt <= :aTo')->setParameter('aTo', (int) $filters['admitted_to']);
        }

        // وضعیت سرویس / بدهی — بر اساس وجود مراجعه‌ی پرداخت‌نشده (payment_method='pending').
        $pendingSub = 'SELECT sp.id FROM App\Patient\Entity\PatientSession sp WHERE sp.record = r AND sp.paymentMethod = :pendingPm';
        $anySub     = 'SELECT sa.id FROM App\Patient\Entity\PatientSession sa WHERE sa.record = r';

        if (!empty($filters['has_debt'])) {
            $qb->andWhere("EXISTS ($pendingSub)")->setParameter('pendingPm', 'pending');
        }
        if (($filters['service_status'] ?? null) === 'pending') {
            $qb->andWhere("EXISTS ($pendingSub)")->setParameter('pendingPm', 'pending');
        } elseif (($filters['service_status'] ?? null) === 'completed') {
            $qb->andWhere("NOT EXISTS ($pendingSub)")
               ->andWhere("EXISTS ($anySub)")
               ->setParameter('pendingPm', 'pending');
        }
    }

    public function countUnique(string $entityType, int $entityId, int $from, int $to): int
    {
        return (int) $this->createQueryBuilder('r')
            ->select('COUNT(DISTINCT r.user)')
            ->join('r.sessions', 's')
            ->where('r.entityType = :type')
            ->andWhere('r.entityId = :id')
            ->andWhere('s.createdAt BETWEEN :from AND :to')
            ->setParameter('type', $entityType)
            ->setParameter('id', $entityId)
            ->setParameter('from', $from)
            ->setParameter('to', $to)
            ->getQuery()
            ->getSingleScalarResult();
    }

    public function save(PatientRecord $record): void
    {
        $this->getEntityManager()->persist($record);
        $this->getEntityManager()->flush();
    }
}
