<?php

namespace App\Tests\Appointment;

use App\Appointment\Entity\Appointment;
use App\Appointment\Entity\WeeklySchedule;
use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Entity\ServiceSection;
use App\Doctor\Entity\Doctor;
use App\Tests\ApiTestCase;

/**
 * حالت نوبت‌دهی سرویسی: پاسخ booking-services باید بخش هر سرویس را بدهد؛ اسلات‌ها و
 * ثبت نوبت باید مدتِ override منشی را (فقط برای همان نوبت) لحاظ کنند بدون تغییر پیش‌فرضِ سرویس.
 */
class ServiceModeSectionDurationTest extends ApiTestCase
{
    /** @return array{0:\App\Auth\Entity\User,1:Doctor,2:string} */
    private function serviceDoctor(int $serviceMinutes = 30): array
    {
        $owner  = $this->createUser(['ROLE_DOCTOR']);
        $doctor = new Doctor($owner, 'دکتر سرویس');
        $this->em->persist($doctor);

        $date   = date('Y-m-d', strtotime('tomorrow'));
        $dayKey = (string) (((int) date('w', strtotime($date)) + 1) % 7);
        $schedule = $this->newWeeklySchedule($doctor, [
            $dayKey => ['sessions' => [[
                'active' => true, 'start_time' => '15:00', 'end_time' => '19:00',
                'duration_per_patient' => 20, 'location_id' => 1,
            ]]],
        ]);
        $schedule->setMeta(['booking_mode' => WeeklySchedule::MODE_SERVICE, 'buffer_minutes' => 0]);
        $this->em->persist($schedule);
        $this->em->flush();

        return [$owner, $doctor, $date];
    }

    private function service(Doctor $doctor, string $name, int $minutes): ServiceItem
    {
        $section = new ServiceSection('doctor', $doctor->getId(), 'بخش ' . $name);
        $this->em->persist($section);
        $item = new ServiceItem($section, $name, 0);
        $item->setDurationMinutes($minutes)->setBookable(true);
        $this->em->persist($item);
        $this->em->flush();

        return $item;
    }

    public function testBookingServicesReturnsSection(): void
    {
        [$owner, $doctor] = $this->serviceDoctor();
        $svc = $this->service($doctor, 'بوتاکس', 30);

        $res = $this->authJson('GET', '/api/v1/appointment-booking-services/' . $doctor->getUuid(), $owner);
        self::assertSame(200, $this->responseCode());

        $row = $res['data']['services'][0];
        self::assertSame($svc->getUuid(), $row['uuid']);
        self::assertArrayHasKey('service_section', $row);
        self::assertSame($svc->getSection()->getUuid(), $row['service_section']['uuid']);
        self::assertSame($svc->getSection()->getName(), $row['service_section']['name']);
    }

    public function testServiceSlotsHonorsDurationOverride(): void
    {
        [$owner, $doctor, $date] = $this->serviceDoctor();
        $svc = $this->service($doctor, 'فیلر', 30);

        // بدون override: مدت کل = ۳۰
        $base = $this->authJson('GET', sprintf(
            '/api/v1/appointment-service-slots?doctor_uuid=%s&date=%s&service_item_uuids[]=%s',
            $doctor->getUuid(), $date, $svc->getUuid()
        ), $owner);
        self::assertSame(30, $base['data']['total_duration_minutes']);

        // با override = ۹۰
        $over = $this->authJson('GET', sprintf(
            '/api/v1/appointment-service-slots?doctor_uuid=%s&date=%s&service_item_uuids[]=%s&durations[%s]=90',
            $doctor->getUuid(), $date, $svc->getUuid(), $svc->getUuid()
        ), $owner);
        self::assertSame(90, $over['data']['total_duration_minutes']);

        // پیش‌فرضِ سرویس در DB تغییر نکرده
        $this->em->clear();
        $reloaded = $this->em->getRepository(ServiceItem::class)->findOneBy(['uuid' => $svc->getUuid()]);
        self::assertSame(30, $reloaded->getDurationMinutes());
    }

    public function testCreateAppliesDurationOverrideToSlotEnd(): void
    {
        [$owner, $doctor] = $this->serviceDoctor();
        $svc = $this->service($doctor, 'لیزر', 30);

        $start = time() + 86_400 + random_int(0, 3_600) * 100;
        $nc    = '00' . str_pad((string) random_int(0, 99_999_999), 8, '0', STR_PAD_LEFT);

        $res = $this->authJson('POST', '/api/v1/my/appointment', $owner, [
            'doctor_uuid'            => $doctor->getUuid(),
            'slot_start'             => $start,
            'slot_end'               => $start + 60, // نادیده گرفته می‌شود (بازمحاسبه)
            'patient_mobile'         => '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT),
            'patient_name'           => 'بیمار تست',
            'patient_national_code'  => $nc,
            'service_item_uuids'     => [$svc->getUuid()],
            'duration_from_services' => true,
            'service_durations'      => [$svc->getUuid() => 75],
        ]);
        self::assertSame(201, $this->responseCode());

        $this->em->clear();
        $appt = $this->em->getRepository(Appointment::class)->findOneBy(['uuid' => $res['data']['uuid']]);
        // slot_end = start + 75 دقیقه (override)، نه ۳۰ پیش‌فرض
        self::assertSame($start + 75 * 60, $appt->getSlotEnd());

        // پیش‌فرضِ سرویس دست‌نخورده
        $reloaded = $this->em->getRepository(ServiceItem::class)->findOneBy(['uuid' => $svc->getUuid()]);
        self::assertSame(30, $reloaded->getDurationMinutes());
    }

    // ── جداسازی محیط روی سرویس‌های انتخابی ──────────────────────────────────

    /**
     * سرویس‌ها فرزند aggregateاند (service_items → service_sections) و ستون محیط
     * ندارند، پس TenantFilter پوششان نمی‌دهد. مسیر ثبت نوبت با
     * assertServicesMatchContext محافظت می‌شد ولی مسیر محاسبهٔ اسلات نه — با uuid
     * سرویسِ محیط دیگر می‌شد وجود، فعال‌بودن و مدتش را استنتاج کرد.
     */
    public function testServiceSlotsRejectsAServiceFromAnotherEnvironment(): void
    {
        [$owner, $doctor, $date] = $this->serviceDoctor();
        [, $stranger]            = $this->serviceDoctor();
        $foreign                 = $this->service($stranger, 'سرویس بیگانه', 45);

        $this->authJson('GET', sprintf(
            '/api/v1/appointment-service-slots?doctor_uuid=%s&date=%s&service_item_uuids[]=%s',
            $doctor->getUuid(), $date, $foreign->getUuid(),
        ), $owner);

        self::assertSame(422, $this->responseCode());
    }

    /** حتی وقتی یک سرویسِ خودی هم در فهرست است، سرویسِ بیگانه کل درخواست را رد می‌کند. */
    public function testOneForeignServiceInvalidatesTheWholeRequest(): void
    {
        [$owner, $doctor, $date] = $this->serviceDoctor();
        $mine                    = $this->service($doctor, 'سرویس خودی', 30);
        [, $stranger]            = $this->serviceDoctor();
        $foreign                 = $this->service($stranger, 'سرویس بیگانه', 45);

        $this->authJson('GET', sprintf(
            '/api/v1/appointment-service-slots?doctor_uuid=%s&date=%s&service_item_uuids[]=%s&service_item_uuids[]=%s',
            $doctor->getUuid(), $date, $mine->getUuid(), $foreign->getUuid(),
        ), $owner);

        self::assertSame(422, $this->responseCode());
    }

    /** ⚠️ مسیر سالم نباید بشکند: سرویس همان پزشک همچنان اسلات می‌دهد. */
    public function testServiceSlotsStillWorkForTheDoctorsOwnService(): void
    {
        [$owner, $doctor, $date] = $this->serviceDoctor();
        $mine                    = $this->service($doctor, 'سرویس خودی', 30);

        $res = $this->authJson('GET', sprintf(
            '/api/v1/appointment-service-slots?doctor_uuid=%s&date=%s&service_item_uuids[]=%s',
            $doctor->getUuid(), $date, $mine->getUuid(),
        ), $owner);

        self::assertSame(200, $this->responseCode());
        self::assertSame(30, $res['data']['total_duration_minutes']);
        self::assertNotEmpty($res['data']['start_times']);
    }

    /**
     * نشتیِ نوشتنی: مسیر ثبت نوبت از پنل، بخش/سرویس/پرسنل را با uuid از بدنهٔ
     * درخواست می‌گرفت و بدون بررسی محیط به نوبت می‌چسباند — یعنی دادهٔ محیط دیگری
     * ذخیره می‌شد، نه فقط خوانده.
     */
    public function testPanelBookingRejectsAServiceFromAnotherEnvironment(): void
    {
        [$owner, $doctor] = $this->serviceDoctor();
        [, $stranger]     = $this->serviceDoctor();
        $foreign          = $this->service($stranger, 'سرویس بیگانه', 30);

        $start = time() + 86_400 + random_int(0, 3_600) * 100;

        $this->authJson('POST', '/api/v1/my/appointment', $owner, [
            'doctor_uuid'           => $doctor->getUuid(),
            'slot_start'            => $start,
            'slot_end'              => $start + 1_800,
            'patient_mobile'        => '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT),
            'patient_name'          => 'بیمار تست',
            'patient_national_code' => '00' . str_pad((string) random_int(0, 99_999_999), 8, '0', STR_PAD_LEFT),
            'service_item_uuids'    => [$foreign->getUuid()],
        ]);

        self::assertSame(422, $this->responseCode());
    }

    /** همان نشتی از مسیر service_item_uuid تکی (فیلد workflow کلینیک). */
    public function testPanelBookingRejectsAForeignServiceItemField(): void
    {
        [$owner, $doctor] = $this->serviceDoctor();
        [, $stranger]     = $this->serviceDoctor();
        $foreign          = $this->service($stranger, 'سرویس بیگانه', 30);

        $start = time() + 86_400 + random_int(0, 3_600) * 100;

        $this->authJson('POST', '/api/v1/my/appointment', $owner, [
            'doctor_uuid'           => $doctor->getUuid(),
            'slot_start'            => $start,
            'slot_end'              => $start + 1_800,
            'patient_mobile'        => '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT),
            'patient_name'          => 'بیمار تست',
            'patient_national_code' => '00' . str_pad((string) random_int(0, 99_999_999), 8, '0', STR_PAD_LEFT),
            'service_item_uuid'     => $foreign->getUuid(),
        ]);

        self::assertSame(422, $this->responseCode());
    }

    /** و بخشِ محیط دیگر هم رد می‌شود. */
    public function testPanelBookingRejectsAForeignServiceSection(): void
    {
        [$owner, $doctor] = $this->serviceDoctor();
        [, $stranger]     = $this->serviceDoctor();
        $foreignSection   = $this->service($stranger, 'سرویس بیگانه', 30)->getSection();

        $start = time() + 86_400 + random_int(0, 3_600) * 100;

        $this->authJson('POST', '/api/v1/my/appointment', $owner, [
            'doctor_uuid'           => $doctor->getUuid(),
            'slot_start'            => $start,
            'slot_end'              => $start + 1_800,
            'patient_mobile'        => '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT),
            'patient_name'          => 'بیمار تست',
            'patient_national_code' => '00' . str_pad((string) random_int(0, 99_999_999), 8, '0', STR_PAD_LEFT),
            'service_section_uuid'  => $foreignSection->getUuid(),
        ]);

        self::assertSame(422, $this->responseCode());
    }

    /** ⚠️ مسیر سالم نباید بشکند: سرویس خودی همچنان ثبت می‌شود. */
    public function testPanelBookingStillAcceptsTheDoctorsOwnService(): void
    {
        [$owner, $doctor] = $this->serviceDoctor();
        $mine             = $this->service($doctor, 'سرویس خودی', 30);

        $start = time() + 86_400 + random_int(0, 3_600) * 100;

        $this->authJson('POST', '/api/v1/my/appointment', $owner, [
            'doctor_uuid'           => $doctor->getUuid(),
            'slot_start'            => $start,
            'slot_end'              => $start + 1_800,
            'patient_mobile'        => '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT),
            'patient_name'          => 'بیمار تست',
            'patient_national_code' => '00' . str_pad((string) random_int(0, 99_999_999), 8, '0', STR_PAD_LEFT),
            'service_item_uuids'    => [$mine->getUuid()],
            'service_item_uuid'     => $mine->getUuid(),
        ]);

        self::assertSame(201, $this->responseCode());
    }

    /** uuid کاملاً ناموجود هم همان ۴۲۲ را می‌گیرد — پیام «وجود ندارد» جدا لو نمی‌رود. */
    public function testUnknownServiceUuidIsIndistinguishableFromAForeignOne(): void
    {
        [$owner, $doctor, $date] = $this->serviceDoctor();

        $this->authJson('GET', sprintf(
            '/api/v1/appointment-service-slots?doctor_uuid=%s&date=%s&service_item_uuids[]=%s',
            $doctor->getUuid(), $date, '00000000-0000-4000-8000-000000000000',
        ), $owner);

        self::assertSame(422, $this->responseCode());
    }
}
