<?php

namespace App\Course\Entity;

use App\ClinicService\Entity\ServiceItem;
use App\Course\Repository\TreatmentCourseRepository;
use App\Package\Entity\PatientPackage;
use App\Patient\Entity\PatientRecord;
use App\Resource\Entity\ClinicResource;
use App\Shared\Tenant\TenantOwnedTrait;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Uuid;

/**
 * دورهٔ درمان یک بیمار.
 *
 * چهار عدد پروتکل **کپی** می‌شوند نه ارجاع: تغییر پروتکل فردا نباید دورهٔ در جریان را
 * عوض کند — همان تصمیمی که در `appointment_segments` و `patient_packages` گرفته شد.
 *
 * یکتایی «یک دورهٔ فعال per (بیمار، سرویس)» با `activeCourseKey` گرفته می‌شود، همان
 * الگوی `Appointment::activeSlotKey`: MariaDB کلید یکتای جزئی ندارد، ولی کلیدی که در
 * حالت‌های غیرفعال `null` می‌شود همان کار را می‌کند.
 */
#[ORM\Entity(repositoryClass: TreatmentCourseRepository::class)]
#[ORM\Table(name: 'treatment_courses')]
#[ORM\Index(columns: ['entity_type', 'entity_id', 'status', 'started_at'], name: 'idx_courses_tenant')]
#[ORM\Index(columns: ['patient_record_id', 'status'], name: 'idx_courses_patient')]
class TreatmentCourse
{
    use TenantOwnedTrait;

    public const STATUS_ACTIVE    = 'active';
    public const STATUS_COMPLETED = 'completed';
    public const STATUS_ABANDONED = 'abandoned';

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private ?int $id = null;

    #[ORM\Column(type: 'string', length: 36, unique: true)]
    private string $uuid;

    #[ORM\ManyToOne(targetEntity: PatientRecord::class)]
    #[ORM\JoinColumn(name: 'patient_record_id', nullable: false, onDelete: 'RESTRICT')]
    private PatientRecord $patientRecord;

    #[ORM\ManyToOne(targetEntity: ServiceItem::class)]
    #[ORM\JoinColumn(name: 'service_item_id', nullable: false, onDelete: 'RESTRICT')]
    private ServiceItem $serviceItem;

    #[ORM\ManyToOne(targetEntity: CourseProtocol::class)]
    #[ORM\JoinColumn(name: 'protocol_id', nullable: false, onDelete: 'RESTRICT')]
    private CourseProtocol $protocol;

    #[ORM\Column(name: 'session_count', type: 'smallint')]
    private int $sessionCount;

    #[ORM\Column(name: 'min_days', type: 'smallint')]
    private int $minDays;

    #[ORM\Column(name: 'ideal_days', type: 'smallint')]
    private int $idealDays;

    #[ORM\Column(name: 'max_days', type: 'smallint')]
    private int $maxDays;

    #[ORM\ManyToOne(targetEntity: PatientPackage::class)]
    #[ORM\JoinColumn(name: 'patient_package_id', nullable: true, onDelete: 'SET NULL')]
    private ?PatientPackage $patientPackage = null;

    #[ORM\ManyToOne(targetEntity: ClinicResource::class)]
    #[ORM\JoinColumn(name: 'preferred_resource_id', nullable: true, onDelete: 'SET NULL')]
    private ?ClinicResource $preferredResource = null;

    #[ORM\Column(type: 'string', length: 12, options: ['default' => self::STATUS_ACTIVE])]
    private string $status = self::STATUS_ACTIVE;

    /** `null` وقتی دوره فعال نیست — همین باعث می‌شود کلید یکتا فقط فعال‌ها را ببندد. */
    #[ORM\Column(name: 'active_course_key', type: 'string', length: 64, nullable: true, unique: true)]
    private ?string $activeCourseKey = null;

    #[ORM\Column(name: 'abandon_reason', type: 'string', length: 255, nullable: true)]
    private ?string $abandonReason = null;

    #[ORM\Column(name: 'started_at', type: 'integer')]
    private int $startedAt;

    #[ORM\Column(name: 'completed_at', type: 'integer', nullable: true)]
    private ?int $completedAt = null;

    /** @var Collection<int, CourseSession> */
    #[ORM\OneToMany(targetEntity: CourseSession::class, mappedBy: 'course', cascade: ['persist', 'remove'], orphanRemoval: true)]
    #[ORM\OrderBy(['sessionNumber' => 'ASC'])]
    private Collection $sessions;

    #[ORM\Column(name: 'created_at', type: 'integer')]
    private int $createdAt;

    #[ORM\Column(name: 'updated_at', type: 'integer')]
    private int $updatedAt;

    public function __construct(PatientRecord $patientRecord, CourseProtocol $protocol)
    {
        $this->uuid          = Uuid::v4()->toRfc4122();
        $this->patientRecord = $patientRecord;
        $this->protocol      = $protocol;
        $this->serviceItem   = $protocol->getServiceItem();
        $this->sessionCount  = $protocol->getSessionCount();
        $this->minDays       = $protocol->getMinDays();
        $this->idealDays     = $protocol->getIdealDays();
        $this->maxDays       = $protocol->getMaxDays();
        $this->sessions      = new ArrayCollection();
        $this->startedAt     = time();
        $this->createdAt     = time();
        $this->updatedAt     = time();

        $this->assignTenantPair($protocol->getEntityType(), $protocol->getEntityId());
        $this->refreshActiveKey();
    }

    public function getId(): ?int                     { return $this->id; }
    public function getUuid(): string                 { return $this->uuid; }
    public function getPatientRecord(): PatientRecord { return $this->patientRecord; }
    public function getServiceItem(): ServiceItem     { return $this->serviceItem; }
    public function getProtocol(): CourseProtocol     { return $this->protocol; }
    public function getSessionCount(): int            { return $this->sessionCount; }
    public function getMinDays(): int                 { return $this->minDays; }
    public function getIdealDays(): int               { return $this->idealDays; }
    public function getMaxDays(): int                 { return $this->maxDays; }
    public function getPatientPackage(): ?PatientPackage { return $this->patientPackage; }
    public function getPreferredResource(): ?ClinicResource { return $this->preferredResource; }
    public function getStatus(): string               { return $this->status; }
    public function getAbandonReason(): ?string       { return $this->abandonReason; }
    public function getStartedAt(): int               { return $this->startedAt; }
    public function getCompletedAt(): ?int            { return $this->completedAt; }
    public function isActive(): bool                  { return $this->status === self::STATUS_ACTIVE; }

    /** @return Collection<int, CourseSession> */
    public function getSessions(): Collection { return $this->sessions; }

    public function setPatientPackage(?PatientPackage $v): self    { $this->patientPackage = $v; return $this->touch(); }
    public function setPreferredResource(?ClinicResource $v): self { $this->preferredResource = $v; return $this->touch(); }

    public function addSession(CourseSession $session): self
    {
        if (!$this->sessions->contains($session)) {
            $this->sessions->add($session);
        }

        return $this;
    }

    public function abandon(string $reason): self
    {
        $this->status        = self::STATUS_ABANDONED;
        $this->abandonReason = $reason;

        return $this->refreshActiveKey()->touch();
    }

    public function complete(?int $at = null): self
    {
        $this->status      = self::STATUS_COMPLETED;
        $this->completedAt = $at ?? time();

        return $this->refreshActiveKey()->touch();
    }

    /** @return list<CourseSession> جلساتی که هنوز رزرو نشده‌اند، به ترتیب شماره */
    public function plannedSessions(): array
    {
        return array_values(array_filter(
            $this->sessions->toArray(),
            static fn (CourseSession $s): bool => $s->getStatus() === CourseSession::STATUS_PLANNED,
        ));
    }

    /** آخرین جلسهٔ **انجام‌شده** — مبنای فاصلهٔ جلسهٔ بعدی. */
    public function lastCompletedAt(): ?int
    {
        $times = [];

        foreach ($this->sessions as $session) {
            if ($session->getCompletedAt() !== null) {
                $times[] = $session->getCompletedAt();
            }
        }

        return $times === [] ? null : max($times);
    }

    public function completedCount(): int
    {
        return count(array_filter(
            $this->sessions->toArray(),
            static fn (CourseSession $s): bool => $s->getStatus() === CourseSession::STATUS_COMPLETED,
        ));
    }

    private function refreshActiveKey(): self
    {
        $this->activeCourseKey = $this->status === self::STATUS_ACTIVE
            ? sprintf('%d:%d', $this->patientRecord->getId(), $this->serviceItem->getId())
            : null;

        return $this;
    }

    private function touch(): self
    {
        $this->updatedAt = time();

        return $this;
    }

    /** @return array<string, mixed> */
    public function toArray(): array
    {
        return [
            'uuid'                  => $this->uuid,
            'patient_uuid'          => $this->patientRecord->getUuid(),
            'service_uuid'          => $this->serviceItem->getUuid(),
            'service_name'          => $this->serviceItem->getName(),
            'protocol_uuid'         => $this->protocol->getUuid(),
            'session_count'         => $this->sessionCount,
            'min_days'              => $this->minDays,
            'ideal_days'            => $this->idealDays,
            'max_days'              => $this->maxDays,
            'patient_package_uuid'  => $this->patientPackage?->getUuid(),
            'preferred_resource_uuid' => $this->preferredResource?->getUuid(),
            // نامش هم می‌آید تا پنل بتواند «همان دستگاه قبلی» را روی دکمهٔ رزرو بنویسد
            // بدون یک درخواست دیگر. ترجیح است نه الزام — موتور فقط جلوترش می‌آورد.
            'preferred_resource_name' => $this->preferredResource?->getName(),
            'status'                => $this->status,
            'abandon_reason'        => $this->abandonReason,
            'started_at'            => $this->startedAt,
            'completed_at'          => $this->completedAt,
        ];
    }
}
