<?php

namespace App\Policy\Simulation;

use App\Appointment\Entity\Appointment;
use App\Policy\Entity\Policy;
use App\UserProfile\Entity\UserProfile;
use Doctrine\ORM\EntityManagerInterface;

/**
 * حقایق یک نوبتِ ثبت‌شده، به همان شکلی که نقاط اجرای زنده می‌سازند.
 *
 * اگر این کلاس حقیقتی را طور دیگری بسازد، آزمایش دروغ می‌گوید — و آزمایشی که دروغ
 * بگوید بدتر از نداشتن آزمایش است. به همین دلیل نام‌ها عیناً از
 * {@see \App\Policy\Service\FieldRegistry} می‌آیند.
 */
final class SimulationFacts
{
    public function __construct(
        private readonly EntityManagerInterface $em,
    ) {}

    /** @return array<string, mixed> */
    public function forAppointment(Appointment $appointment, string $category): array
    {
        $service = $appointment->getServiceItem();
        $items   = $appointment->getServiceItems()->count();

        $common = [
            'service_uuid'     => $service?->getUuid(),
            'catalog_category' => $service?->getCatalogCategory()?->getUuid(),
            'item_count'       => max(1, $items),
        ];

        return match ($category) {
            Policy::CATEGORY_SELECTION => $common + [
                'item_uuids' => $this->itemUuids($appointment),
            ],
            Policy::CATEGORY_ELIGIBILITY => $common + $this->patientFacts($appointment),
            Policy::CATEGORY_TIMING      => $common + [
                'patient_age' => $this->patientFacts($appointment)['patient_age'],
            ],
            Policy::CATEGORY_PRICING => $common + [
                'subtotal_rials' => $this->subtotalOf($appointment),
                'patient_tags'   => [],
                'visit_count'    => $this->visitCount($appointment),
            ],
            default => $common,
        };
    }

    /** @return list<string> */
    private function itemUuids(Appointment $appointment): array
    {
        $uuids = [];

        foreach ($appointment->getServiceItems() as $item) {
            $uuids[] = $item->getUuid();
        }

        if ($uuids === [] && $appointment->getServiceItem() !== null) {
            $uuids[] = $appointment->getServiceItem()->getUuid();
        }

        return $uuids;
    }

    /** @return array{patient_age: int|null, patient_gender: string|null, patient_tags: list<string>, visit_count: int, has_parental_consent: bool} */
    private function patientFacts(Appointment $appointment): array
    {
        /** @var UserProfile|null $profile */
        $profile = $this->em->getRepository(UserProfile::class)
            ->findOneBy(['user' => $appointment->getUser()]);

        $dob = $profile?->getDateOfBirth();

        return [
            'patient_age'    => $dob === null || $dob <= 0
                ? null
                : (int) floor(($appointment->getSlotStart() - $dob) / 31556952),
            'patient_gender' => $profile?->getGender() ?? $appointment->getPatientGender(),
            'patient_tags'   => [],
            'visit_count'    => $this->visitCount($appointment),
            // نوبت گذشته پرچمِ لحظه‌ای ندارد؛ فرضِ «نگرفته» محافظه‌کارانه است و
            // باعث می‌شود قانون `require_flag` در گزارش **دیده** شود نه پنهان.
            'has_parental_consent' => false,
        ];
    }

    private function visitCount(Appointment $appointment): int
    {
        return (int) $this->em->createQueryBuilder()
            ->select('COUNT(a.id)')
            ->from(Appointment::class, 'a')
            ->where('a.user = :user')
            ->andWhere('a.slotStart < :before')
            ->andWhere('a.status = :status')
            ->setParameter('user', $appointment->getUser())
            ->setParameter('before', $appointment->getSlotStart())
            ->setParameter('status', Appointment::STATUS_COMPLETED)
            ->getQuery()
            ->getSingleScalarResult();
    }

    /** مبلغ ثبت‌شدهٔ همان نوبت؛ نه قیمت امروزِ سرویس. */
    public function subtotalOf(Appointment $appointment): int
    {
        return (int) ($appointment->getVisitPriceRials()
            ?? $appointment->getServiceItem()?->getPriceRials()
            ?? 0);
    }

    /** مدت ثبت‌شدهٔ همان نوبت، با بازگشت به طول بازهٔ اسلات. */
    public function durationOf(Appointment $appointment): int
    {
        return (int) ($appointment->getServiceTotalMinutes()
            ?? max(0, intdiv($appointment->getSlotEnd() - $appointment->getSlotStart(), 60)));
    }
}
