<?php

namespace App\Report\Service;

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

/**
 * مدت پیش‌بینی‌شده در برابر مدت واقعی — تشخیص تعریف غلط بخش‌ها.
 *
 * بند ۱۷ مستند، ریسک سوم: «کلینیک بخش‌های نوبت را اشتباه تعریف کند → ظرفیت غلط حساب
 * می‌شود». سرویسی که یک ساعت پیش‌بینی شده ولی یک‌ساعت‌ونیم طول می‌کشد، هر روز نیم ساعت
 * از ظرفیت کلینیک را بی‌صدا می‌خورد و هیچ خطایی هم نمی‌دهد.
 *
 * مبنای «واقعی» فاصلهٔ ثبت‌شدهٔ اسلات است، نه ساعت ورود و خروج بیمار — چون آن دومی
 * جایی ثبت نمی‌شود و حدس زدنش بدتر از نداشتنش است.
 */
final class PlanAccuracyReporter
{
    /** زیر این تعداد نمونه، میانگین معنا ندارد. */
    public const MIN_SAMPLE = 10;

    public function __construct(
        private readonly EntityManagerInterface $em,
    ) {}

    /**
     * @return list<array<string, mixed>> مرتب بر اساس شدت انحراف
     */
    public function report(string $entityType, int $entityId, int $from, int $to): array
    {
        $rows = $this->em->createQueryBuilder()
            ->select(
                'si.uuid AS service_uuid',
                'si.name AS service_name',
                'COUNT(a.id) AS sample_size',
                'AVG(a.serviceTotalMinutes) AS planned',
                'AVG((a.slotEnd - a.slotStart) / 60) AS actual',
            )
            ->from(Appointment::class, 'a')
            ->join('a.serviceItem', 'si')
            ->where('a.entityType = :type')
            ->andWhere('a.entityId = :id')
            ->andWhere('a.slotStart >= :from')
            ->andWhere('a.slotStart < :to')
            ->andWhere('a.status = :status')
            ->andWhere('a.serviceTotalMinutes IS NOT NULL')
            ->setParameter('type', $entityType)
            ->setParameter('id', $entityId)
            ->setParameter('from', $from)
            ->setParameter('to', $to)
            // فقط نوبت‌های انجام‌شده: لغوشده چیزی دربارهٔ مدت واقعی نمی‌گوید.
            ->setParameter('status', Appointment::STATUS_COMPLETED)
            ->groupBy('si.uuid')
            ->addGroupBy('si.name')
            ->getQuery()
            ->getArrayResult();

        $out = [];

        foreach ($rows as $row) {
            $sample  = (int) $row['sample_size'];
            $planned = (float) $row['planned'];
            $actual  = (float) $row['actual'];

            if ($planned <= 0) {
                continue;
            }

            // زیر آستانه، **حذف نمی‌شود بلکه بی‌شدت برمی‌گردد**.
            //
            // میانگینِ سه نمونه معنا ندارد و نباید کسی رویش تصمیم بگیرد؛ ولی حذف کاملش
            // یعنی کلینیک کوچک یک گزارش خالی می‌بیند و فکر می‌کند همه‌چیز درست است.
            // این‌طوری هم عدد را می‌بیند هم می‌داند که هنوز قابل استناد نیست.
            if ($sample < self::MIN_SAMPLE) {
                $out[] = [
                    'service_uuid'      => $row['service_uuid'],
                    'service_name'      => $row['service_name'],
                    'sample_size'       => $sample,
                    'planned_minutes'   => (int) round($planned),
                    'actual_minutes'    => (int) round($actual),
                    'deviation_percent' => (int) round(($actual - $planned) / $planned * 100),
                    'severity'          => null,
                    'below_min_sample'  => true,
                ];

                continue;
            }

            $deviation = (int) round(($actual - $planned) / $planned * 100);

            $out[] = [
                'service_uuid'      => $row['service_uuid'],
                'service_name'      => $row['service_name'],
                'sample_size'       => $sample,
                'planned_minutes'   => (int) round($planned),
                'actual_minutes'    => (int) round($actual),
                'deviation_percent' => $deviation,
                'severity'          => $this->severityFor($deviation),
                'below_min_sample'  => false,
            ];
        }

        // ردیف‌های قابل استناد اول؛ بین خودشان، بدترین انحراف بالاتر.
        usort($out, static fn (array $a, array $b): int
            => [$a['below_min_sample'], abs($b['deviation_percent'])]
           <=> [$b['below_min_sample'], abs($a['deviation_percent'])]);

        return $out;
    }

    /**
     * شدت از **قدر مطلق** انحراف می‌آید: سرویسی که نصف زمان پیش‌بینی‌شده طول می‌کشد هم
     * غلط تعریف شده — ظرفیتی که می‌شد فروخت، خالی مانده.
     */
    private function severityFor(int $deviationPercent): string
    {
        return match (true) {
            abs($deviationPercent) >= 30 => 'high',
            abs($deviationPercent) >= 15 => 'medium',
            abs($deviationPercent) >= 5  => 'low',
            default                      => 'none',
        };
    }
}
