<?php

namespace App\Appointment\Controller;

use App\Appointment\Entity\DateOverride;
use App\Appointment\Entity\Holiday;
use App\Appointment\Entity\WeeklySchedule;
use App\Appointment\Repository\DateOverrideRepository;
use App\Appointment\Repository\HolidayRepository;
use App\Appointment\Repository\WeeklyScheduleRepository;
use App\Auth\Entity\User;
use App\Clinic\Entity\Clinic;
use App\Clinic\Repository\ClinicRepository;
use App\Doctor\Entity\Doctor;
use App\Doctor\Entity\DoctorAddress;
use App\Doctor\Repository\DoctorAddressRepository;
use App\Doctor\Repository\DoctorRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Context\EntityContext;
use App\Shared\Controller\BaseController;
use App\Shared\Exception\AppException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use OpenApi\Attributes as OA;

#[OA\Tag(name: 'Appointment Settings')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class AppointmentSettingsController extends BaseController
{
    public function __construct(
        private readonly WeeklyScheduleRepository $scheduleRepo,
        private readonly DateOverrideRepository   $overrideRepo,
        private readonly HolidayRepository        $holidayRepo,
        private readonly DoctorRepository         $doctorRepo,
        private readonly DoctorAddressRepository  $addressRepo,
        private readonly ClinicRepository         $clinicRepo,
        private readonly \App\ClinicService\Repository\ServiceItemRepository $itemRepo,
        private readonly \App\Resource\Repository\ClinicResourceRepository $resourceRepo,
        private readonly \App\Appointment\Availability\Picker\ResourcePickerRegistry $pickers,
        private readonly \App\Clinic\Security\ClinicDoctorPermissionChecker $permChecker,
        private readonly \App\Secretary\Security\SecretaryAccessChecker $secretaryAccess,
    ) {}

    /**
     * تاریخِ روزمحور (`Y-m-d`) را به نیمه‌شبِ همان روز در تایم‌زون سرور تبدیل می‌کند.
     * بدون این نرمال‌سازی، ورودی‌هایی که ساعت دارند (مثلاً ISO با `Z`) روز را جابه‌جا
     * می‌کنند. `null` یعنی ورودی نامعتبر.
     */
    private function parseDayTimestamp(string $value): ?int
    {
        $ts = strtotime(trim($value));
        if ($ts === false) {
            return null;
        }

        return (int) strtotime(date('Y-m-d', $ts) . ' 00:00:00');
    }

    /**
     * نوع نوبت‌دهی پس از اولین ثبت غیرقابل‌تغییر است — اما فقط داخل همان context.
     * پزشکی که در مطب شخصی نوبت‌دهی اسلاتی دارد، همچنان می‌تواند در کلینیک سرویسی
     * انتخاب کند.
     */
    private function assertModeImmutable(?string $prevMode, array $newMeta): ?JsonResponse
    {
        if ($prevMode !== null && ($newMeta['booking_mode'] ?? null) !== $prevMode) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نوع نوبت‌دهی پس از ثبت قابل تغییر نیست', 422, 'booking_mode');
        }
        return null;
    }

    /**
     * در حالت نوبت‌دهی سرویسی، صاحبِ همین context باید حداقل یک سرویسِ
     * «نمایش در نوبت‌دهی» داشته باشد؛ وگرنه هیچ نوبتی قابل‌محاسبه نیست.
     *
     * سرویس‌ها polymorphic‌اند و بین پزشک و کلینیک مشترک نمی‌شوند، پس شمارش باید با
     * همان (entity_type, entity_id) محیط انجام شود — نه همیشه 'doctor'.
     */
    private function serviceModeHasNoBookable(array $meta, Doctor $doctor, ?Clinic $clinic): bool
    {
        if (($meta['booking_mode'] ?? WeeklySchedule::MODE_SLOT) !== WeeklySchedule::MODE_SERVICE) {
            return false;
        }

        [$type, $id] = $clinic !== null
            ? [EntityContext::TYPE_CLINIC, $clinic->getId()]
            : [EntityContext::TYPE_DOCTOR, $doctor->getId()];

        return $this->itemRepo->countBookableByEntity($type, $id) === 0;
    }

    /**
     * حالت منبع‌محور بدون هیچ منبع فعال، هیچ نوبتی نمی‌سازد — و چون انتخابِ حالت
     * برگشت‌ناپذیر است، محیط برای همیشه قفل می‌شد.
     */
    private function resourceModeHasNoResources(array $meta, Doctor $doctor, ?Clinic $clinic): bool
    {
        if (($meta['booking_mode'] ?? WeeklySchedule::MODE_SLOT) !== WeeklySchedule::MODE_RESOURCE) {
            return false;
        }

        [$type, $id] = $clinic !== null
            ? [EntityContext::TYPE_CLINIC, $clinic->getId()]
            : [EntityContext::TYPE_DOCTOR, $doctor->getId()];

        return $this->resourceRepo->countActiveForPair($type, (int) $id) === 0;
    }

    /**
     * استراتژی ناشناخته هنگام **ذخیره** رد می‌شود، نه هنگام اجرا.
     *
     * موتور در زمان جستجو به پیش‌فرض برمی‌گردد تا تنظیماتِ قدیمی نوبت‌دهی را نخواباند؛
     * ولی کاربری که همین حالا مقدار غلط می‌فرستد باید بداند، وگرنه فکر می‌کند
     * استراتژی‌اش اعمال می‌شود در حالی که نمی‌شود.
     */
    private function invalidStrategy(array $meta): bool
    {
        $code = $meta['resource_strategy'] ?? null;

        return is_string($code) && $code !== '' && !$this->pickers->has($code);
    }

    private function invalidStrategyError(): JsonResponse
    {
        return $this->error(
            ErrorCodes::ERR_VALIDATION_001,
            'استراتژی انتخاب منبع شناخته نمی‌شود',
            422,
            'resource_strategy',
        );
    }

    /** فهرست استراتژی‌های موجود — ورودی انتخابگر پنل، نه فهرستی که در فرانت تکرار شود. */
    #[Route('/api/v1/appointment-settings/resource-strategies', name: 'resource_strategies', methods: ['GET'])]
    public function resourceStrategies(): JsonResponse
    {
        return $this->success($this->pickers->describe());
    }

    private function noResourceError(): JsonResponse
    {
        return $this->error(
            ErrorCodes::ERR_VALIDATION_001,
            'برای نوبت‌دهی منبع‌محور حداقل یک منبع فعال لازم است؛ اول اتاق، اپراتور یا دستگاه تعریف کنید',
            422,
            'booking_mode',
        );
    }

    private function noBookableServiceError(?Clinic $clinic): JsonResponse
    {
        $message = $clinic !== null
            ? 'برای نوبت‌دهی سرویسی، کلینیک باید حداقل یک سرویس با «نمایش در نوبت‌دهی» داشته باشد'
            : 'برای نوبت‌دهی سرویسی حداقل یک سرویس با «نمایش در نوبت‌دهی» لازم است';

        return $this->error(ErrorCodes::ERR_VALIDATION_001, $message, 422, 'booking_mode');
    }

    /**
     * محیطی که این درخواست در آن اجرا می‌شود: کلینیکِ داده‌شده، یا null یعنی مطب
     * شخصی پزشک. پزشک حتماً باید عضو آن کلینیک باشد، وگرنه اصلاً چنین محیطی وجود
     * ندارد.
     */
    private function contextClinic(?string $clinicUuid, Doctor $doctor): ?Clinic
    {
        if ($clinicUuid === null || trim($clinicUuid) === '') {
            return null;
        }

        $clinic = $this->clinicRepo->findByUuid(trim($clinicUuid));
        if ($clinic === null) {
            throw new AppException(ErrorCodes::ERR_VALIDATION_002, 'کلینیک یافت نشد', 404);
        }

        if (!$clinic->hasDoctor($doctor)) {
            throw new AppException(ErrorCodes::ERR_VALIDATION_001, 'این پزشک عضو کلینیک انتخاب‌شده نیست', 422);
        }

        return $clinic;
    }

    // ── Weekly Schedule ───────────────────────────────────────────────────────

    #[Route('/api/v1/appointment-settings/weekly-schedule', methods: ['POST'])]
    public function createSchedule(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $data       = json_decode($request->getContent(), true) ?? [];
        $doctorUuid = trim($data['doctor_uuid'] ?? '');

        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if ($doctor === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'دکتر یافت نشد', 404);
        }

        $clinic = $this->contextClinic($data['clinic_uuid'] ?? null, $doctor);

        if (($err = $this->denyDoctorAccess($doctor, $user, 'update', $clinic)) !== null) {
            return $err;
        }

        if (($err = $this->validateSessions($data['schedule'] ?? [], $doctor, $clinic)) !== null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, $err, 422);
        }

        // یک برنامه به ازای هر context — upsert
        $schedule = $this->scheduleRepo->findByDoctorAndClinic($doctor, $clinic);
        $prevMode = $schedule?->getStoredBookingMode();
        if ($schedule !== null) {
            $schedule->setSetting($data['schedule'] ?? []);
        } else {
            $schedule = new WeeklySchedule($doctor, $data['schedule'] ?? [], $clinic);
            $schedule->assignTenant(EntityContext::forBooking($doctor, $clinic));
        }

        if (isset($data['meta']) && is_array($data['meta'])) {
            $schedule->setMeta($data['meta']);
        }

        if (($err = $this->assertModeImmutable($prevMode, $schedule->getMeta())) !== null) {
            return $err;
        }

        if ($this->invalidStrategy($schedule->getMeta())) {
            return $this->invalidStrategyError();
        }

        if ($this->resourceModeHasNoResources($schedule->getMeta(), $doctor, $clinic)) {
            return $this->noResourceError();
        }

        if ($this->serviceModeHasNoBookable($schedule->getMeta(), $doctor, $clinic)) {
            return $this->noBookableServiceError($clinic);
        }

        $this->scheduleRepo->save($schedule);

        return $this->success(['data' => $schedule->toArray()], 201);
    }

    #[Route('/api/v1/appointment-settings/weekly-schedule/{uuid}', methods: ['PATCH'])]
    public function updateSchedule(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $data = json_decode($request->getContent(), true) ?? [];

        // uuid may be doctor uuid or schedule uuid
        $schedule = $this->scheduleRepo->findByUuid($uuid);
        if ($schedule === null) {
            $doctor = $this->doctorRepo->findByUuid($uuid);
            if ($doctor === null) {
                return $this->error(ErrorCodes::ERR_VALIDATION_002, 'برنامه یافت نشد', 404);
            }
            $clinic   = $this->contextClinic($data['clinic_uuid'] ?? $request->query->get('clinic_uuid'), $doctor);
            $schedule = $this->scheduleRepo->findByDoctorAndClinic($doctor, $clinic);
        } else {
            $clinic = $schedule->getClinic();
        }

        if ($schedule === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'برنامه یافت نشد', 404);
        }

        if (($err = $this->denyDoctorAccess($schedule->getDoctor(), $user, 'update', $clinic)) !== null) {
            return $err;
        }

        $prevMode = $schedule->getStoredBookingMode();
        if (isset($data['schedule'])) {
            if (($err = $this->validateSessions($data['schedule'], $schedule->getDoctor(), $clinic)) !== null) {
                return $this->error(ErrorCodes::ERR_VALIDATION_001, $err, 422);
            }
            $schedule->setSetting($data['schedule']);
        }
        if (isset($data['meta']) && is_array($data['meta'])) {
            $schedule->setMeta($data['meta']);
        }

        if (($err = $this->assertModeImmutable($prevMode, $schedule->getMeta())) !== null) {
            return $err;
        }

        if ($this->invalidStrategy($schedule->getMeta())) {
            return $this->invalidStrategyError();
        }

        if ($this->resourceModeHasNoResources($schedule->getMeta(), $schedule->getDoctor(), $clinic)) {
            return $this->noResourceError();
        }

        if ($this->serviceModeHasNoBookable($schedule->getMeta(), $schedule->getDoctor(), $clinic)) {
            return $this->noBookableServiceError($clinic);
        }

        $this->scheduleRepo->save($schedule);

        return $this->success(['data' => $schedule->toArray()]);
    }

    #[Route('/api/v1/appointment-settings/weekly-schedule/{uuid}', methods: ['GET'])]
    public function getSchedule(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        // Try doctor uuid first, then schedule uuid
        $doctor = $this->doctorRepo->findByUuid($uuid);
        if ($doctor !== null) {
            $clinic   = $this->contextClinic($request->query->get('clinic_uuid'), $doctor);
            $schedule = $this->scheduleRepo->findByDoctorAndClinic($doctor, $clinic);
        } else {
            $schedule = $this->scheduleRepo->findByUuid($uuid);
            $clinic   = $schedule?->getClinic();
        }

        if ($schedule === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'برنامه یافت نشد', 404);
        }

        if (($err = $this->denyDoctorAccess($schedule->getDoctor(), $user, 'view', $clinic)) !== null) {
            return $err;
        }

        return $this->success(['data' => $schedule->toArray()]);
    }

    #[Route('/api/v1/booking-setting/{uuid}', methods: ['DELETE'])]
    public function deleteSchedule(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        $schedule = $this->scheduleRepo->findByUuid($uuid);
        if ($schedule === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'برنامه یافت نشد', 404);
        }

        if (($err = $this->denyDoctorAccess($schedule->getDoctor(), $user, 'update', $schedule->getClinic())) !== null) {
            return $err;
        }

        $this->scheduleRepo->remove($schedule);

        return $this->success(['message' => 'برنامه هفتگی با موفقیت حذف شد']);
    }

    // ── Date Overrides ────────────────────────────────────────────────────────

    #[Route('/api/v1/appointment-settings/date-override/list/{doctorUuid}', methods: ['GET'])]
    public function listOverrides(string $doctorUuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if ($doctor === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'دکتر یافت نشد', 404);
        }

        $clinic = $this->contextClinic($request->query->get('clinic_uuid'), $doctor);

        if (($err = $this->denyDoctorAccess($doctor, $user, 'view', $clinic)) !== null) {
            return $err;
        }

        $overrides = array_map(
            fn(DateOverride $o) => $o->toArray(),
            $this->overrideRepo->findByDoctorAndClinic($doctor, $clinic)
        );

        return $this->success(['data' => $overrides]);
    }

    #[Route('/api/v1/appointment-settings/date-override', methods: ['POST'])]
    public function createOverride(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $data       = json_decode($request->getContent(), true) ?? [];
        $doctorUuid = trim($data['doctor_uuid'] ?? '');
        $dateStr    = trim($data['date'] ?? '');

        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if ($doctor === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'دکتر یافت نشد', 404);
        }

        $clinic = $this->contextClinic($data['clinic_uuid'] ?? null, $doctor);

        if (($err = $this->denyDoctorAccess($doctor, $user, 'update', $clinic)) !== null) {
            return $err;
        }

        $timestamp = $this->parseDayTimestamp($dateStr);
        if ($timestamp === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'فرمت تاریخ نادرست است', 422, 'date');
        }

        $override = new DateOverride($doctor, $timestamp, (bool) ($data['active'] ?? false), $clinic);
        $override->assignTenant(EntityContext::forBooking($doctor, $clinic));
        if (isset($data['reason']))       $override->setReason($data['reason']);
        if (isset($data['custom_slots'])) $override->setSetting($data['custom_slots']);

        $this->overrideRepo->save($override);

        return $this->success(['data' => $override->toArray()], 201);
    }

    #[Route('/api/v1/appointment-settings/date-override/{uuid}', methods: ['PATCH'])]
    public function updateOverride(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $override = $this->overrideRepo->findByUuid($uuid);
        if ($override === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'Override یافت نشد', 404);
        }

        if (($err = $this->denyDoctorAccess($override->getDoctor(), $user, 'update', $override->getClinic())) !== null) {
            return $err;
        }

        $data = json_decode($request->getContent(), true) ?? [];
        if (array_key_exists('active', $data))       $override->setActive((bool) $data['active']);
        if (array_key_exists('reason', $data))       $override->setReason($data['reason']);
        if (array_key_exists('custom_slots', $data)) $override->setSetting($data['custom_slots']);
        if (!empty($data['date'])) {
            $ts = $this->parseDayTimestamp($data['date']);
            if ($ts !== null) $override->setDate($ts);
        }

        $this->overrideRepo->save($override);

        return $this->success(['data' => $override->toArray()]);
    }

    #[Route('/api/v1/appointment-settings/date-override/{uuid}', methods: ['DELETE'])]
    public function deleteOverride(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        $override = $this->overrideRepo->findByUuid($uuid);
        if ($override === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'Override یافت نشد', 404);
        }

        if (($err = $this->denyDoctorAccess($override->getDoctor(), $user, 'update', $override->getClinic())) !== null) {
            return $err;
        }

        $this->overrideRepo->remove($override);

        return $this->success(['message' => 'Override با موفقیت حذف شد']);
    }

    #[Route('/api/v1/appointment-settings/date-override/{uuid}', methods: ['GET'])]
    public function getOverride(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        $override = $this->overrideRepo->findByUuid($uuid);
        if ($override === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'Override یافت نشد', 404);
        }

        if (($err = $this->denyDoctorAccess($override->getDoctor(), $user, 'view', $override->getClinic())) !== null) {
            return $err;
        }

        return $this->success(['data' => $override->toArray()]);
    }

    // ── Holidays ──────────────────────────────────────────────────────────────

    #[Route('/api/v1/appointment-settings/holidays/list/{doctorUuid}', methods: ['GET'])]
    public function listHolidays(string $doctorUuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if ($doctor === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'دکتر یافت نشد', 404);
        }

        $clinic = $this->contextClinic($request->query->get('clinic_uuid'), $doctor);

        if (($err = $this->denyDoctorAccess($doctor, $user, 'view', $clinic)) !== null) {
            return $err;
        }

        // محیط کلینیک تعطیلی سراسری پزشک را هم می‌بیند (باید بداند پزشک نیست)، اما
        // editable=false یعنی اجازهٔ تغییرش را ندارد.
        $items = array_map(function (Holiday $h) use ($clinic): array {
            $data             = $h->toArray();
            $data['editable'] = $clinic === null || $h->getClinic() !== null;
            return $data;
        }, $this->holidayRepo->findAllByDoctorInContext($doctor, $clinic));

        return $this->success(['data' => $items]);
    }

    #[Route('/api/v1/appointment-settings/holidays/{uuid}', methods: ['DELETE'])]
    public function deleteHoliday(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        $holiday = $this->holidayRepo->findByUuid($uuid);
        if ($holiday === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'تعطیلات یافت نشد', 404);
        }

        if (($err = $this->denyDoctorAccess($holiday->getDoctor(), $user, 'update', $holiday->getClinic())) !== null) {
            return $err;
        }

        $this->holidayRepo->remove($holiday);

        return $this->success(['message' => 'تعطیلات حذف شد']);
    }

    #[Route('/api/v1/appointment-settings/holidays', methods: ['POST'])]
    public function createHoliday(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $data       = json_decode($request->getContent(), true) ?? [];
        $doctorUuid = trim($data['doctor_uuid'] ?? '');
        $startStr   = trim($data['start_date'] ?? '');
        $endStr     = trim($data['end_date'] ?? '');

        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if ($doctor === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'دکتر یافت نشد', 404);
        }

        $clinic = $this->contextClinic($data['clinic_uuid'] ?? null, $doctor);

        if (($err = $this->denyDoctorAccess($doctor, $user, 'update', $clinic)) !== null) {
            return $err;
        }

        // تعطیلی سراسری (بدون clinic_uuid) یعنی «پزشک در هیچ محلی نیست» و مطب شخصی
        // را هم می‌بندد؛ فقط خود پزشک یا ادمین حق چنین کاری دارد.
        if ($clinic === null && !$user->hasRole('ROLE_ADMIN') && $doctor->getUser()->getId() !== $user->getId()) {
            return $this->error(ErrorCodes::ERR_ACCESS_DENIED, 'کلینیک فقط می‌تواند تعطیلی مخصوص خودش را ثبت کند', 403, 'clinic_uuid');
        }

        $startTs = $this->parseDayTimestamp($startStr);
        $endTs   = $this->parseDayTimestamp($endStr);

        if ($startTs === null || $endTs === null || $endTs < $startTs) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'تاریخ نادرست است', 422);
        }

        $holiday = new Holiday($doctor, $startTs, $endTs, $clinic);
        if (isset($data['reason'])) $holiday->setReason($data['reason']);

        $this->holidayRepo->save($holiday);

        return $this->success(['data' => $holiday->toArray()], 201);
    }

    #[Route('/api/v1/appointment-settings/holidays/{uuid}', methods: ['PATCH'])]
    public function updateHoliday(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $holiday = $this->holidayRepo->findByUuid($uuid);
        if ($holiday === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'تعطیلات یافت نشد', 404);
        }

        if (($err = $this->denyDoctorAccess($holiday->getDoctor(), $user, 'update', $holiday->getClinic())) !== null) {
            return $err;
        }

        $data = json_decode($request->getContent(), true) ?? [];
        if (array_key_exists('active', $data))  $holiday->setActive((bool) $data['active']);
        if (array_key_exists('reason', $data))  $holiday->setReason($data['reason']);
        if (!empty($data['start_date'])) {
            $ts = $this->parseDayTimestamp($data['start_date']);
            if ($ts !== null) $holiday->setStartDate($ts);
        }
        if (!empty($data['end_date'])) {
            $ts = $this->parseDayTimestamp($data['end_date']);
            if ($ts !== null) $holiday->setEndDate($ts);
        }

        $this->holidayRepo->save($holiday);

        return $this->success(['data' => $holiday->toArray()]);
    }

    // ── Available Locations ───────────────────────────────────────────────────

    #[Route('/api/v1/appointment-settings/available-locations/{doctorUuid}', methods: ['GET'])]
    public function availableLocations(string $doctorUuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if ($doctor === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'دکتر یافت نشد', 404);
        }

        $clinic = $this->contextClinic($request->query->get('clinic_uuid'), $doctor);

        if (($err = $this->denyDoctorAccess($doctor, $user, 'view', $clinic)) !== null) {
            return $err;
        }

        $result = array_map(
            fn(DoctorAddress $a): array => $a->toArray($clinic?->getName()),
            $this->addressRepo->findForContext($doctor, $clinic?->getId())
        );

        return $this->success(['data' => $result]);
    }

    /**
     * تنها نقطهٔ تصمیم‌گیری دربارهٔ «چه کسی تنظیمات نوبت‌دهی این پزشک را می‌بیند/می‌نویسد».
     *
     * تصمیم به context وابسته است و نه فقط به شخص:
     *   • مطب شخصی ($clinic === null) فقط برای خود پزشک و ادمین باز است — مالک کلینیک
     *     هیچ کاری با برنامهٔ شخصی پزشک ندارد.
     *   • محیط کلینیک با مجوز appointment_settings همان کلینیک سنجیده می‌شود، نه
     *     حلقه روی همهٔ کلینیک‌های پزشک.
     *
     * @param 'view'|'update' $action
     */
    private function denyDoctorAccess(Doctor $doctor, User $user, string $action, ?Clinic $clinic): ?JsonResponse
    {
        if ($user->hasRole('ROLE_ADMIN') || $doctor->getUser()->getId() === $user->getId()) {
            return null;
        }

        if ($clinic !== null && $this->permChecker->can($user, $clinic, 'appointment_settings', $action)) {
            return null;
        }

        // منشی: توگلِ appointment_settings + پزشکِ هدف در اسکوپِ همان منشی.
        if ($user->hasRole('ROLE_SECRETARY')
            && $this->secretaryAccess->canForDoctor($user, $doctor, $clinic, 'appointment_settings', $action)) {
            return null;
        }

        return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
    }

    /**
     * هر شیفت فعال باید آدرسی داشته باشد که به همین context تعلق دارد. بدون بررسی
     * دوم، کلینیک می‌توانست شیفت را روی آدرس مطب شخصی پزشک بنشاند (و برعکس).
     */
    private function validateSessions(array $schedule, Doctor $doctor, ?Clinic $clinic): ?string
    {
        $allowed = [];
        foreach ($this->addressRepo->findForContext($doctor, $clinic?->getId()) as $address) {
            $allowed[(string) $address->getId()] = true;
        }

        foreach ($schedule as $day) {
            foreach (($day['sessions'] ?? []) as $session) {
                if (!($session['active'] ?? false)) {
                    continue;
                }

                $locationId = (string) ($session['location_id'] ?? '');
                if ($locationId === '') {
                    return 'برای هر شیفت فعال باید آدرس (مطب/کلینیک) انتخاب شود';
                }

                if (!isset($allowed[$locationId])) {
                    return $clinic !== null
                        ? 'آدرس انتخاب‌شده متعلق به این کلینیک نیست'
                        : 'آدرس انتخاب‌شده متعلق به مطب شخصی این پزشک نیست';
                }
            }
        }

        return null;
    }
}
