<?php

namespace App\Appointment\Entity;

use App\Clinic\Entity\Clinic;
use App\Doctor\Entity\Doctor;
use App\Shared\Context\EntityContext;
use App\Shared\Tenant\TenantOwnedTrait;
use Doctrine\ORM\Mapping as ORM;
use App\Appointment\Repository\WeeklyScheduleRepository;
use Symfony\Component\Uid\Uuid;

/**
 * برنامهٔ هفتگی نوبت‌دهی یک پزشک در یک context مشخص.
 *
 * context با ستون clinic_id بیان می‌شود: NULL یعنی مطب شخصی پزشک، و مقدار غیر-NULL
 * یعنی همان پزشک در آن کلینیک. یک پزشک می‌تواند هم‌زمان چند برنامه داشته باشد
 * (شخصی + یکی به ازای هر کلینیک) و این برنامه‌ها کاملاً مستقل‌اند.
 */
#[ORM\Entity(repositoryClass: WeeklyScheduleRepository::class)]
#[ORM\Table(name: 'weekly_schedules')]
// doctor_id در کلید می‌ماند: در یک کلینیک چند پزشک هستند و هر کدام برنامهٔ خودش را
// دارد، پس (entity_type, entity_id) به‌تنهایی برای پزشک دوم نقض یکتایی می‌سازد.
#[ORM\UniqueConstraint(name: 'uniq_weekly_schedule_doctor_tenant', columns: ['doctor_id', 'entity_type', 'entity_id'])]
class WeeklySchedule
{
    use TenantOwnedTrait;

    public const DAYS = ['saturday', 'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday'];

    public const META_KEY = 'meta';

    public const MODE_SLOT    = 'slot';    // نوبت‌دهی اسلاتی (رفتار پیش‌فرض)
    public const MODE_SERVICE = 'service'; // نوبت‌دهی بر اساس مدت سرویس

    /** واحدهای مجاز بازهٔ رزرو آنلاین؛ همان کلیدواژه‌های strtotime. */
    public const BOOKING_WINDOW_UNITS = ['day', 'week', 'month'];

    public const DEFAULT_META = [
        'online_booking_enabled' => true,
        'booking_window_value'   => 3,
        'booking_window_unit'    => 'month',
        'booking_mode'           => self::MODE_SLOT,
        'buffer_minutes'         => 0,
    ];

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

    /** NULL = مطب شخصی پزشک. */
    #[ORM\ManyToOne(targetEntity: Clinic::class)]
    #[ORM\JoinColumn(name: 'clinic_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
    private ?Clinic $clinic = null;

    #[ORM\Column(type: 'json')]
    private array $setting = [];

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

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

    public function __construct(Doctor $doctor, array $setting, ?Clinic $clinic = null)
    {
        $this->uuid      = Uuid::v4()->toRfc4122();
        $this->doctor    = $doctor;
        $this->clinic    = $clinic;
        $this->setting   = $setting;
        $this->createdAt = time();
        $this->updatedAt = time();
    }

    public function getId(): ?int      { return $this->id; }
    public function getUuid(): string  { return $this->uuid; }
    public function getDoctor(): Doctor { return $this->doctor; }
    public function getClinic(): ?Clinic { return $this->clinic; }

    /** فقط برای انتقال دستی برنامه‌های قدیمی به محیط کلینیک (app:schedule:assign-clinic). */
    public function setClinic(?Clinic $clinic): self
    {
        $this->clinic    = $clinic;
        $this->updatedAt = time();
        $this->assignTenant(EntityContext::forBooking($this->doctor, $clinic));
        return $this;
    }
    public function getSetting(): array { return $this->setting; }

    public function setSetting(array $setting): self
    {
        $meta = $this->setting[self::META_KEY] ?? null;
        unset($setting[self::META_KEY]);
        if ($meta !== null) {
            $setting[self::META_KEY] = $meta;
        }
        $this->setting = $setting;
        $this->updatedAt = time();
        return $this;
    }

    public function getMeta(): array
    {
        return array_merge(self::DEFAULT_META, $this->setting[self::META_KEY] ?? []);
    }

    /**
     * booking_mode ذخیره‌شده به‌صورت خام (بدون merge پیش‌فرض). null یعنی هنوز
     * صریحاً ثبت نشده — تا وقتی null است، انتخاب روش قابل‌تغییر است؛ پس از اولین
     * ثبت، قفل می‌شود.
     */
    public function getStoredBookingMode(): ?string
    {
        return $this->setting[self::META_KEY]['booking_mode'] ?? null;
    }

    public function setMeta(array $meta): self
    {
        $current = $this->getMeta();
        $this->setting[self::META_KEY] = [
            'online_booking_enabled' => (bool)($meta['online_booking_enabled'] ?? $current['online_booking_enabled']),
            'booking_window_value'   => max(1, (int)($meta['booking_window_value'] ?? $current['booking_window_value'])),
            'booking_window_unit'    => in_array($meta['booking_window_unit'] ?? null, self::BOOKING_WINDOW_UNITS, true)
                ? $meta['booking_window_unit']
                : $current['booking_window_unit'],
            'booking_mode'           => in_array($meta['booking_mode'] ?? null, [self::MODE_SLOT, self::MODE_SERVICE], true)
                ? $meta['booking_mode']
                : $current['booking_mode'],
            'buffer_minutes'         => max(0, (int)($meta['buffer_minutes'] ?? $current['buffer_minutes'])),
        ];
        $this->updatedAt = time();
        return $this;
    }

    public function getDaySchedule(): array
    {
        $schedule = $this->setting;
        unset($schedule[self::META_KEY]);
        return $schedule;
    }

    public function toArray(): array
    {
        return [
            'uuid'       => $this->uuid,
            'doctor_uuid' => $this->doctor->getUuid(),
            'clinic_uuid' => $this->clinic?->getUuid(),
            'context'    => $this->clinic === null ? 'personal' : 'clinic',
            'schedule'   => $this->getDaySchedule(),
            'meta'       => $this->getMeta(),
            // نوع نوبت‌دهی پس از اولین ثبت قفل می‌شود (پنل توگل را غیرفعال می‌کند).
            'booking_mode_locked' => $this->getStoredBookingMode() !== null,
            'created_at' => $this->createdAt,
            'updated_at' => $this->updatedAt,
        ];
    }
}
