<?php

namespace App\Report\Service;

use App\Appointment\Availability\Entity\ResourceOccupancy;
use App\Doctor\Entity\DoctorAddress;
use App\Resource\Entity\ClinicResource;
use App\Resource\Service\ResourceAvailabilityService;
use Doctrine\ORM\EntityManagerInterface;

/**
 * بهره‌وری منابع — تنها ابزاری که به کلینیک می‌گوید تعریف بخش‌هایش درست است یا نه.
 *
 * سه عدد، سه معنای متفاوت:
 *
 * | عدد | یعنی |
 * |---|---|
 * | `available_minutes` | منبع طبق تقویمش چقدر در دسترس بوده |
 * | `occupied_minutes` | چقدر **گرفته** شده — شامل آماده‌سازی، تمیزکاری و بخش‌های انتظار |
 * | `active_minutes` | چقدر واقعاً کار انجام شده — فقط بخش‌هایی که بیمار حاضر بوده |
 *
 * فاصلهٔ `occupied` و `active` همان چیزی است که تعریف غلط بخش‌ها را لو می‌دهد: منبعی که
 * هشت ساعت اشغال بوده ولی دو ساعت کار کرده، یا بخش‌های `passive` زیادی گرفته یا
 * زمان‌های انتظارش اشتباه به او نسبت داده شده.
 */
final class ResourceUtilizationReporter
{
    /** زیر این نسبت، ظرفیت عملاً هدر می‌رود. */
    public const WASTE_THRESHOLD = 0.3;

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

    /**
     * @param ClinicResource[] $resources
     * @return list<array<string, mixed>>
     */
    public function report(array $resources, DoctorAddress $address, int $from, int $to): array
    {
        $occupied = $this->occupiedMinutes($resources, $from, $to);
        $active   = $this->activeMinutes($resources, $from, $to);

        // تقویم همهٔ منابع هم دسته‌ای خوانده می‌شود؛ وگرنه هر منبع پنج کوئری اضافه
        // می‌آورد و گزارشِ یک کلینیک متوسط دویست کوئری می‌شد.
        $availability = $this->calendars->rawAvailabilityForAll($resources, $from, $to);

        $rows = [];

        foreach ($resources as $resource) {
            $id        = (int) $resource->getId();
            $available = $this->availableMinutes($resource, $availability[$id] ?? []);

            $rows[] = $this->row(
                $resource,
                $available,
                $occupied[$id] ?? 0,
                $active[$id] ?? 0,
            );
        }

        return $rows;
    }

    /** @return array<string, mixed> */
    private function row(ClinicResource $resource, int $available, int $occupied, int $active): array
    {
        // تقسیم بر صفر معنای متفاوتی دارد: منبعی بدون تقویم «۰٪ بهره‌وری» ندارد،
        // اصلاً بهره‌وری‌اش تعریف‌نشده است.
        $utilization = $available > 0 ? round($occupied / $available, 2) : null;
        $activeRatio = $occupied > 0 ? round($active / $occupied, 2) : null;

        return [
            'resource_uuid'     => $resource->getUuid(),
            'resource_name'     => $resource->getName(),
            'role'              => $resource->getType()->getCode(),
            'available_minutes' => $available,
            'occupied_minutes'  => $occupied,
            'active_minutes'    => $active,
            'utilization'       => $utilization,
            'active_ratio'      => $activeRatio,
            'wasted_capacity'   => $activeRatio !== null && $activeRatio < self::WASTE_THRESHOLD,
        ];
    }

    /** @param list<\App\Resource\ValueObject\DayAvailability> $days */
    private function availableMinutes(ClinicResource $resource, array $days): int
    {
        $minutes = 0;

        foreach ($days as $day) {
            $minutes += $day->totalMinutes();
        }

        // ظرفیت ضرب می‌شود: اتاق سه‌تخته در یک ساعت، سه ساعت-منبع عرضه دارد. بدون آن،
        // هر منبع چندظرفیتی همیشه «بیش از ۱۰۰٪ بهره‌وری» نشان می‌داد.
        return $minutes * max(1, $resource->getCapacity());
    }

    /**
     * دقایق اشغال از `resource_occupancy` — شامل setup/cleanup، چون منبع واقعاً
     * اشغال بوده.
     *
     * @param ClinicResource[] $resources
     * @return array<int, int>
     */
    private function occupiedMinutes(array $resources, int $from, int $to): array
    {
        if ($resources === []) {
            return [];
        }

        $rows = $this->em->createQueryBuilder()
            ->select('IDENTITY(o.resource) AS resource_id', 'SUM(o.endsAt - o.startsAt) AS seconds')
            ->from(ResourceOccupancy::class, 'o')
            ->where('o.resource IN (:resources)')
            ->andWhere('o.startsAt < :to')
            ->andWhere('o.endsAt > :from')
            ->andWhere('o.status IN (:statuses)')
            ->setParameter('resources', $resources)
            ->setParameter('from', $from)
            ->setParameter('to', $to)
            // ردیف آزادشده اشغال نبوده؛ آوردنش یعنی هر لغو، بهره‌وری را بالا ببرد.
            ->setParameter('statuses', ResourceOccupancy::BLOCKING_STATUSES)
            ->groupBy('resource_id')
            ->getQuery()
            ->getArrayResult();

        $out = [];

        foreach ($rows as $row) {
            $out[(int) $row['resource_id']] = (int) round(((int) $row['seconds']) / 60);
        }

        return $out;
    }

    /**
     * دقایقی که بیمار حاضر بوده — بخش‌های `passive` عمداً نمی‌آیند.
     *
     * @param ClinicResource[] $resources
     * @return array<int, int>
     */
    private function activeMinutes(array $resources, int $from, int $to): array
    {
        if ($resources === []) {
            return [];
        }

        $sql = <<<'SQL'
            SELECT o.resource_id AS resource_id,
                   SUM(LEAST(o.ends_at, s.ends_at) - GREATEST(o.starts_at, s.starts_at)) AS seconds
            FROM resource_occupancy o
            JOIN appointment_segments s
              ON s.appointment_id = o.appointment_id
             AND s.patient_present = 1
             AND s.starts_at < o.ends_at
             AND s.ends_at   > o.starts_at
            WHERE o.resource_id IN (:resources)
              AND o.starts_at < :to
              AND o.ends_at   > :from
              AND o.status IN (:statuses)
            GROUP BY o.resource_id
            SQL;

        $rows = $this->em->getConnection()->fetchAllAssociative($sql, [
            'resources' => array_map(static fn (ClinicResource $r): int => (int) $r->getId(), $resources),
            'from'      => $from,
            'to'        => $to,
            'statuses'  => ResourceOccupancy::BLOCKING_STATUSES,
        ], [
            'resources' => \Doctrine\DBAL\ArrayParameterType::INTEGER,
            'statuses'  => \Doctrine\DBAL\ArrayParameterType::STRING,
        ]);

        $out = [];

        foreach ($rows as $row) {
            $out[(int) $row['resource_id']] = (int) round(((int) $row['seconds']) / 60);
        }

        return $out;
    }
}
