<?php

namespace App\Cancellation\Service;

use App\Appointment\Entity\Appointment;
use App\Auth\Entity\User;
use App\Cancellation\Entity\NoShowRecord;
use App\Cancellation\Repository\CancellationPolicyRepository;
use App\Cancellation\Repository\NoShowRecordRepository;
use App\Patient\Entity\PatientRecord;
use App\Shared\Event\DomainEventPublisher;
use App\Shared\Event\DomainEvents;
use App\Tag\Entity\TenantTag;
use Doctrine\ORM\EntityManagerInterface;

/**
 * ثبت عدم حضور و برچسب‌گذاری بیمار پرریسک.
 *
 * برچسب **مسدود نمی‌کند**. مسدودسازی یک قانون `eligibility` (تسک ۰۹) روی همین برچسب
 * است؛ اینجا فقط واقعیت ثبت می‌شود. تفکیکش عمدی است: کلینیکی که می‌خواهد بیمار پرریسک
 * را ببیند ولی بیعانه بگیرد، نباید مجبور شود برچسب را خاموش کند.
 */
final class NoShowService
{
    public function __construct(
        private readonly NoShowRecordRepository       $records,
        private readonly CancellationPolicyRepository $policies,
        private readonly DomainEventPublisher         $events,
        private readonly EntityManagerInterface       $em,
    ) {}

    /**
     * @return array{recorded: bool, count: int, threshold: int, tagged: bool}
     */
    public function record(Appointment $appointment, PatientRecord $patient, ?User $actor = null, ?int $now = null): array
    {
        $now = $now ?? time();

        // تغییر وضعیت به `no_show` ممکن است دو بار اتفاق بیفتد؛ رکورد دوم ثبت نشود.
        $existing = $this->records->findForAppointment($appointment);

        $policy = $this->policies->resolve(
            $appointment->getEntityType(),
            $appointment->getEntityId(),
            $appointment->getServiceItem(),
        );

        $threshold = $policy?->getNoShowThreshold() ?? 3;

        if ($existing !== null) {
            return [
                'recorded'  => false,
                'count'     => $this->records->countRecent($patient, $now),
                'threshold' => $threshold,
                'tagged'    => false,
            ];
        }

        $this->em->persist(new NoShowRecord($patient, $appointment, $actor, $now));

        $this->events->record(
            $appointment->getEntityType(),
            $appointment->getEntityId(),
            DomainEvents::PATIENT_NO_SHOW,
            ['appointment_uuid' => $appointment->getUuid(), 'patient_uuid' => $patient->getUuid()],
            $now,
        );

        $this->em->flush();

        $count  = $this->records->countRecent($patient, $now);
        $tagged = false;

        if ($count >= $threshold && $policy?->getRiskTagUuid() !== null) {
            $tagged = $this->applyRiskTag($patient, $policy->getRiskTagUuid());
        }

        return ['recorded' => true, 'count' => $count, 'threshold' => $threshold, 'tagged' => $tagged];
    }

    /** @return bool `false` یعنی برچسب از قبل بود یا وجود ندارد */
    private function applyRiskTag(PatientRecord $patient, string $tagUuid): bool
    {
        $tag = $this->em->getRepository(TenantTag::class)->findOneBy(['uuid' => $tagUuid]);

        if ($tag === null || $patient->getTags()->contains($tag)) {
            return false;
        }

        $patient->getTags()->add($tag);
        $this->em->flush();

        return true;
    }

    public function countFor(PatientRecord $patient, ?int $now = null): int
    {
        return $this->records->countRecent($patient, $now);
    }
}
