<?php

namespace App\Resource\Entity;

use App\Resource\Repository\ResourceExceptionRepository;
use App\Shared\Tenant\TenantOwnedTrait;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Uuid;

/**
 * بازه‌ای که منبع در آن در دسترس نیست — مرخصی، غیبت، سرویس دوره‌ای دستگاه، تعطیلی موردی.
 *
 * چهار نوع، یک جدول: هر چهار «یک بازهٔ کسرشونده از تقویم منبع»اند و جدا کردنشان یعنی
 * چهار کوئری در محاسبهٔ ساعت آزاد به‌جای یکی. `reason` فقط برای نمایش و گزارش است.
 *
 * جفت محیط دارد چون uuidش از request می‌آید (`PATCH/DELETE /resource-exception/{uuid}`).
 *
 * زمان‌ها **timestamp مطلق**اند نه دقیقه‌از-نیمه‌شب: یک مرخصی می‌تواند چندروزه باشد و
 * بیانش با دقیقه‌ی روز، هر مصرف‌کننده را مجبور می‌کرد خودش روزها را بشکافد.
 */
#[ORM\Entity(repositoryClass: ResourceExceptionRepository::class)]
#[ORM\Table(name: 'resource_exceptions')]
#[ORM\Index(columns: ['entity_type', 'entity_id'], name: 'idx_rex_tenant')]
#[ORM\Index(columns: ['resource_id', 'starts_at', 'ends_at'], name: 'idx_rex_resource_range')]
class ResourceException
{
    use TenantOwnedTrait;

    public const TYPE_LEAVE       = 'leave';        // مرخصی
    public const TYPE_ABSENCE     = 'absence';      // غیبت
    public const TYPE_MAINTENANCE = 'maintenance';  // سرویس دوره‌ای دستگاه
    public const TYPE_CLOSURE     = 'closure';      // تعطیلی موردی

    public const TYPES = [
        self::TYPE_LEAVE       => 'مرخصی',
        self::TYPE_ABSENCE     => 'غیبت',
        self::TYPE_MAINTENANCE => 'سرویس دوره‌ای',
        self::TYPE_CLOSURE     => 'تعطیلی موردی',
    ];

    #[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: ClinicResource::class)]
    #[ORM\JoinColumn(name: 'resource_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
    private ClinicResource $resource;

    #[ORM\Column(type: 'string', length: 20)]
    private string $type;

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

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

    #[ORM\Column(type: 'string', length: 200, nullable: true)]
    private ?string $reason = null;

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

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

    public function __construct(ClinicResource $resource, string $type, int $startsAt, int $endsAt)
    {
        if (!array_key_exists($type, self::TYPES)) {
            throw new \InvalidArgumentException(sprintf('Unknown resource exception type "%s".', $type));
        }

        if ($endsAt <= $startsAt) {
            throw new \InvalidArgumentException('Exception end must be after its start.');
        }

        $this->uuid      = Uuid::v4()->toRfc4122();
        $this->resource  = $resource;
        $this->type      = $type;
        $this->startsAt  = $startsAt;
        $this->endsAt    = $endsAt;
        $this->createdAt = time();
        $this->updatedAt = time();

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

    public function getId(): ?int                 { return $this->id; }
    public function getUuid(): string             { return $this->uuid; }
    public function getResource(): ClinicResource { return $this->resource; }
    public function getType(): string             { return $this->type; }
    public function getStartsAt(): int            { return $this->startsAt; }
    public function getEndsAt(): int              { return $this->endsAt; }
    public function getReason(): ?string          { return $this->reason; }
    public function getCreatedAt(): int           { return $this->createdAt; }
    public function getUpdatedAt(): int           { return $this->updatedAt; }

    public function setReason(?string $v): self { $this->reason = $v; $this->touch(); return $this; }

    /** @throws \InvalidArgumentException روی بازهٔ وارونه */
    public function reschedule(int $startsAt, int $endsAt): self
    {
        if ($endsAt <= $startsAt) {
            throw new \InvalidArgumentException('Exception end must be after its start.');
        }

        $this->startsAt = $startsAt;
        $this->endsAt   = $endsAt;
        $this->touch();

        return $this;
    }

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

    public function toArray(): array
    {
        return [
            'uuid'          => $this->uuid,
            'resource_uuid' => $this->resource->getUuid(),
            'resource_name' => $this->resource->getName(),
            'type'          => $this->type,
            'type_label'    => self::TYPES[$this->type],
            'starts_at'     => $this->startsAt,
            'ends_at'       => $this->endsAt,
            'reason'        => $this->reason,
            'created_at'    => $this->createdAt,
            'updated_at'    => $this->updatedAt,
        ];
    }
}
