<?php

namespace App\Tests\Appointment;

use App\Appointment\Plan\Entity\SegmentRequirement;
use App\Appointment\Plan\Entity\SegmentTemplate;
use App\Auth\Entity\User;
use App\Clinic\Entity\Clinic;
use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Entity\ServiceSection;
use App\Doctor\Entity\DoctorAddress;
use App\Resource\Entity\ResourceType;
use App\Tests\ApiTestCase;

/**
 * برنامهٔ چندبخشی نوبت — بند ۷ مستند.
 *
 * مثال مرجع: بی‌حسی ۵ · انتظار ۳۰ (اپراتور آزاد) · لیزر ۲۰ · مراقبت ۵ = ۶۰ دقیقه،
 * در حالی که اپراتور فقط ۳۰ دقیقه اشغال است.
 */
class AppointmentPlanTest extends ApiTestCase
{
    /** @return array{0: User, 1: ServiceSection, 2: DoctorAddress} */
    private function clinicWithBranch(): array
    {
        $user   = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']);
        $clinic = new Clinic($user);
        $clinic->setName('کلینیک برنامه');
        $this->em->persist($clinic);
        $this->em->flush();

        $section = new ServiceSection('clinic', $clinic->getId(), 'لیزر');
        $this->em->persist($section);

        $address = DoctorAddress::forClinic($clinic->getId());
        $address->setName('شعبهٔ مرکزی');
        $this->em->persist($address);
        $this->em->flush();

        return [$user, $section, $address];
    }

    private function service(ServiceSection $section, string $name, ?int $solo = 20, ?int $additional = null): ServiceItem
    {
        $item = new ServiceItem($section, $name);
        $item->setSoloDurationMinutes($solo);
        $item->setAdditionalDurationMinutes($additional);
        $this->em->persist($item);
        $this->em->flush();

        return $item;
    }

    private function resourceType(DoctorAddress $address, string $code, string $name): ResourceType
    {
        $type = new ResourceType($address->tenantEntityType(), $address->tenantEntityId(), $code, $name);
        $this->em->persist($type);
        $this->em->flush();

        return $type;
    }

    /** @param array<string, mixed> $extra */
    private function resource(User $user, DoctorAddress $address, ResourceType $type, string $name, array $extra = []): array
    {
        $body = $this->authJson('POST', '/api/v1/resource', $user, $extra + [
            'address_uuid' => $address->getUuid(),
            'type_uuid'    => $type->getUuid(),
            'name'         => $name,
        ]);
        self::assertSame(201, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));

        return $body['data'];
    }

    /** @param list<array<string, mixed>> $segments */
    private function setSegments(User $user, ServiceItem $service, array $segments): array
    {
        $body = $this->authJson('PUT', "/api/v1/service-item/{$service->getUuid()}/segments", $user, [
            'segments' => $segments,
        ]);

        return $body;
    }

    /** @param array<string, mixed> $extra */
    private function preview(User $user, ServiceItem $service, DoctorAddress $address, array $extra = []): array
    {
        return $this->authJson('POST', '/api/v1/appointment-plan/preview', $user, $extra + [
            'service_uuid' => $service->getUuid(),
            'branch_uuid'  => $address->getUuid(),
        ]);
    }

    /** مثال مرجع مستند، دقیقاً با همان آفست‌ها. */
    public function testFourSegmentLaserPlan(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service = $this->service($section, 'لیزر', 20);

        $room     = $this->resourceType($address, 'room', 'اتاق');
        $operator = $this->resourceType($address, 'operator', 'اپراتور');
        $device   = $this->resourceType($address, 'device', 'دستگاه');

        $this->resource($user, $address, $room, 'اتاق ۱');
        $this->resource($user, $address, $operator, 'اپراتور ۱');
        $this->resource($user, $address, $device, 'لیزر ۱');

        $roomReq     = ['type_uuid' => $room->getUuid()];
        $operatorReq = ['type_uuid' => $operator->getUuid()];
        $deviceReq   = ['type_uuid' => $device->getUuid()];

        $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'بی‌حسی موضعی', 'duration_minutes' => 5,  'requirements' => [$roomReq, $operatorReq]],
            ['sequence' => 2, 'name' => 'انتظار اثر کرم', 'duration_minutes' => 30, 'requirements' => [$roomReq]],
            ['sequence' => 3, 'name' => 'خود لیزر', 'duration_source' => 'items', 'requirements' => [$roomReq, $operatorReq, $deviceReq]],
            ['sequence' => 4, 'name' => 'مراقبت بعد', 'duration_minutes' => 5,  'requirements' => [$roomReq, $operatorReq]],
        ]);
        self::assertSame(200, $this->responseCode());

        $plan = $this->preview($user, $service, $address);
        self::assertSame(200, $this->responseCode(), json_encode($plan, JSON_UNESCAPED_UNICODE));

        $data = $plan['data'];
        self::assertSame(60, $data['total_minutes']);
        self::assertCount(4, $data['segments']);
        self::assertSame([0, 5, 35, 55], array_column($data['segments'], 'offset_minutes'));
        self::assertSame([5, 30, 20, 5], array_column($data['segments'], 'duration_minutes'));

        // نکتهٔ اصلی بند ۷: اپراتور در بخش انتظار **نیست**.
        $waitRoles = array_column($data['segments'][1]['requirements'], 'role');
        self::assertSame(['room'], $waitRoles, 'اپراتور در انتظار آزاد است');

        $laserRoles = array_column($data['segments'][2]['requirements'], 'role');
        sort($laserRoles);
        self::assertSame(['device', 'operator', 'room'], $laserRoles);
    }

    /** بخشی که مدتش از آیتم‌ها می‌آید با دو ناحیه طولانی‌تر می‌شود. */
    public function testItemDrivenSegmentUsesTheDurationCalculator(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service = $this->service($section, 'لیزر', 0);
        $face    = $this->service($section, 'صورت', 15, 8);
        $bikini  = $this->service($section, 'بیکینی', 12, 8);

        $room = $this->resourceType($address, 'room', 'اتاق');
        $this->resource($user, $address, $room, 'اتاق ۱');

        $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'آماده‌سازی', 'duration_minutes' => 5, 'mergeable' => true, 'requirements' => [['type_uuid' => $room->getUuid()]]],
            ['sequence' => 2, 'name' => 'خود لیزر', 'duration_source' => 'items', 'requirements' => [['type_uuid' => $room->getUuid()]]],
        ]);

        $plan = $this->preview($user, $service, $address, [
            'item_uuids' => [$face->getUuid(), $bikini->getUuid()],
        ]);

        self::assertSame(200, $this->responseCode());
        // ۵ آماده‌سازی + ۲۳ لیزر (۱۵ + ۸، نه ۲۷) = ۲۸
        self::assertSame(28, $plan['data']['total_minutes']);
        self::assertSame(23, $plan['data']['segments'][1]['duration_minutes']);
    }

    /** سرویس بدون الگوی بخش، همان رفتار امروز را می‌گیرد: یک بخش پیوسته با پزشک. */
    public function testServiceWithoutTemplatesFallsBackToASingleSegment(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service    = $this->service($section, 'ویزیت', 20);
        $doctorType = $this->resourceType($address, ResourceType::CODE_DOCTOR, 'پزشک');
        $this->resource($user, $address, $doctorType, 'دکتر یک');

        $plan = $this->preview($user, $service, $address);

        self::assertSame(200, $this->responseCode(), json_encode($plan, JSON_UNESCAPED_UNICODE));
        self::assertSame(20, $plan['data']['total_minutes']);
        self::assertCount(1, $plan['data']['segments']);
        self::assertSame('doctor', $plan['data']['segments'][0]['requirements'][0]['role']);
    }

    /** بخش بدون هیچ نیازمندی معتبر است: زمان می‌گیرد، منبعی نمی‌گیرد. */
    public function testSegmentWithoutRequirementsIsValid(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service = $this->service($section, 'مراقبت خانگی', 10);

        $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'انتظار در خانه', 'duration_minutes' => 45, 'patient_present' => false, 'requirements' => []],
        ]);
        self::assertSame(200, $this->responseCode());

        $plan = $this->preview($user, $service, $address);

        self::assertSame(45, $plan['data']['total_minutes']);
        self::assertSame([], $plan['data']['segments'][0]['requirements']);
        self::assertFalse($plan['data']['segments'][0]['patient_present']);
    }

    /** مهارتی که هیچ منبعی ندارد → خطای انسانی با نام نقش و مهارت (بند ۱۰). */
    public function testMissingEligibleResourceGivesAHumanError(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service  = $this->service($section, 'لیزر', 20);
        $operator = $this->resourceType($address, 'operator', 'اپراتور');

        $skill = $this->authJson('POST', '/api/v1/skills', $user, ['name' => 'لیزر آلکساندرایت']);
        self::assertSame(201, $this->responseCode());

        // اپراتور هست ولی مهارت را ندارد.
        $this->resource($user, $address, $operator, 'اپراتور بی‌مهارت');

        $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'لیزر', 'duration_minutes' => 20, 'requirements' => [
                ['type_uuid' => $operator->getUuid(), 'skill_uuid' => $skill['data']['uuid']],
            ]],
        ]);
        self::assertSame(200, $this->responseCode());

        $body = $this->preview($user, $service, $address);

        self::assertSame(422, $this->responseCode());
        self::assertSame('ERR_NO_ELIGIBLE_RESOURCE', $body['errors'][0]['code']);
        self::assertStringContainsString('اپراتور', $body['errors'][0]['message']);
        self::assertStringContainsString('لیزر آلکساندرایت', $body['errors'][0]['message']);
        self::assertStringContainsString('شعبهٔ مرکزی', $body['errors'][0]['message']);
    }

    /**
     * قید جنسیت وقتی جنسیت بیمار نامشخص است نادیده گرفته **نمی‌شود** — رد کردن بی‌صدا
     * یعنی بیمار به منبعی می‌رسد که قرار نبود.
     */
    public function testSameGenderConstraintRequiresAKnownPatientGender(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service  = $this->service($section, 'لیزر', 20);
        $operator = $this->resourceType($address, 'operator', 'اپراتور');

        $this->resource($user, $address, $operator, 'اپراتور خانم', ['attributes' => ['gender' => 'female']]);

        $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'لیزر', 'duration_minutes' => 20, 'requirements' => [
                ['type_uuid' => $operator->getUuid(), 'constraints' => [SegmentRequirement::CONSTRAINT_SAME_GENDER]],
            ]],
        ]);
        self::assertSame(200, $this->responseCode());

        $unknown = $this->preview($user, $service, $address);
        self::assertSame(422, $this->responseCode());
        self::assertStringContainsString('جنسیت بیمار', $unknown['errors'][0]['message']);

        $female = $this->preview($user, $service, $address, ['patient_gender' => 'female']);
        self::assertSame(200, $this->responseCode());
        self::assertSame(1, $female['data']['segments'][0]['requirements'][0]['candidates']);

        $male = $this->preview($user, $service, $address, ['patient_gender' => 'male']);
        self::assertSame(422, $this->responseCode(), 'هیچ اپراتور آقایی نیست');
        self::assertStringContainsString('آقایی', $male['errors'][0]['message']);
    }

    /** `setup/cleanup` در نمای کاربر نمی‌آید ولی برای تسک بعدی در پاسخ هست. */
    public function testOccupancyOffsetCarriesResourceSetupAndCleanup(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service = $this->service($section, 'لیزر', 20);
        $device  = $this->resourceType($address, 'device', 'دستگاه');

        $this->resource($user, $address, $device, 'لیزر ۱', ['setup_minutes' => 5, 'cleanup_minutes' => 10]);

        $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'لیزر', 'duration_minutes' => 20, 'requirements' => [['type_uuid' => $device->getUuid()]]],
        ]);

        $plan = $this->preview($user, $service, $address);

        $offset = $plan['data']['segments'][0]['requirements'][0]['occupancy_offset'];
        self::assertSame(5, $offset['setup_minutes']);
        self::assertSame(10, $offset['cleanup_minutes']);
    }

    public function testTotalBeyondTheDailyCapIsRejected(): void
    {
        [$user, $section] = $this->clinicWithBranch();
        $service = $this->service($section, 'ماراتن', 20);

        $body = $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'بخش بلند', 'duration_minutes' => 400],
            ['sequence' => 2, 'name' => 'بخش بلند دوم', 'duration_minutes' => 200],
        ]);

        self::assertSame(422, $this->responseCode());
        self::assertStringContainsString('سقف', $body['errors'][0]['message']);
    }

    public function testFixedSegmentNeedsAPositiveDuration(): void
    {
        [$user, $section] = $this->clinicWithBranch();
        $service = $this->service($section, 'لیزر', 20);

        $body = $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'بخش بی‌مدت', 'duration_minutes' => 0],
        ]);

        self::assertSame(422, $this->responseCode());
        self::assertSame('duration_minutes', $body['errors'][0]['field']);
    }

    /** بخش‌های `mergeable` هم‌نام یک بار می‌آیند. */
    public function testMergeableSegmentsAppearOnce(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service = $this->service($section, 'لیزر', 20);
        $room    = $this->resourceType($address, 'room', 'اتاق');
        $this->resource($user, $address, $room, 'اتاق ۱');

        $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'آماده‌سازی', 'duration_minutes' => 5, 'mergeable' => true, 'requirements' => [['type_uuid' => $room->getUuid()]]],
            ['sequence' => 2, 'name' => 'آماده‌سازی', 'duration_minutes' => 5, 'mergeable' => true, 'requirements' => [['type_uuid' => $room->getUuid()]]],
            ['sequence' => 3, 'name' => 'کار اصلی', 'duration_minutes' => 20, 'requirements' => [['type_uuid' => $room->getUuid()]]],
        ]);

        $plan = $this->preview($user, $service, $address);

        self::assertCount(2, $plan['data']['segments'], 'آماده‌سازی یک بار');
        self::assertSame(25, $plan['data']['total_minutes']);
    }

    /**
     * ⭐ ادغام واقعی: دو ناحیه که هرکدام «آماده‌سازی» خودشان را دارند.
     *
     * پیش از این، الگوهای آیتم‌های انتخاب‌شده اصلاً خوانده نمی‌شدند و پرچم `mergeable`
     * هیچ کاری نمی‌کرد — در یک سرویس، دو بخشِ هم‌نام معنا ندارد.
     */
    public function testSegmentsOfSelectedItemsAreMergedByName(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();

        $face   = $this->service($section, 'لیزر صورت', 20);
        $bikini = $this->service($section, 'لیزر بیکینی', 30);

        $room = $this->resourceType($address, 'room', 'اتاق');
        $this->resource($user, $address, $room, 'اتاق ۱');

        // آماده‌سازیِ بیکینی طولانی‌تر است؛ ادغام باید طولانی‌ترین را نگه دارد.
        $this->setSegments($user, $face, [
            ['sequence' => 1, 'name' => 'آماده‌سازی', 'duration_minutes' => 5, 'mergeable' => true, 'requirements' => [['type_uuid' => $room->getUuid()]]],
            ['sequence' => 2, 'name' => 'کار اصلی', 'duration_minutes' => 0, 'duration_source' => 'items', 'requirements' => [['type_uuid' => $room->getUuid()]]],
        ]);

        $this->setSegments($user, $bikini, [
            ['sequence' => 1, 'name' => 'آماده‌سازی', 'duration_minutes' => 12, 'mergeable' => true, 'requirements' => [['type_uuid' => $room->getUuid()]]],
        ]);

        $plan = $this->preview($user, $face, $address, [
            'item_uuids' => [$face->getUuid(), $bikini->getUuid()],
        ]);

        $segments = $plan['data']['segments'];
        $names    = array_column($segments, 'name');

        self::assertSame(['آماده‌سازی', 'کار اصلی'], $names, 'آماده‌سازی یک بار می‌آید');
        self::assertSame(12, $segments[0]['duration_minutes'], 'طولانی‌ترین آماده‌سازی می‌ماند');

        // کار اصلی از `DurationCalculator` می‌آید: ۲۰ + ۳۰.
        self::assertSame(50, $segments[1]['duration_minutes']);
        self::assertSame(62, $plan['data']['total_minutes']);
    }

    /**
     * ⭐ تعداد منبع پس از ادغام **بیشینه** است، نه جمع و نه اولی.
     *
     * دو ناحیه با هم دو اتاق نمی‌خواهند؛ ولی اگر یکی دو نفر لازم داشت، ادغام نباید آن
     * را به یک تنزل بدهد — نوبتی که نیروی کافی ندارد بدتر از نوبتِ نگرفته است.
     */
    public function testMergingTakesTheLargestRequiredCount(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();

        $face   = $this->service($section, 'ناحیهٔ یک', 20);
        $bikini = $this->service($section, 'ناحیهٔ دو', 20);

        $operator = $this->resourceType($address, 'operator', 'اپراتور');
        $this->resource($user, $address, $operator, 'اپراتور ۱');
        $this->resource($user, $address, $operator, 'اپراتور ۲');

        $this->setSegments($user, $face, [
            ['sequence' => 1, 'name' => 'آماده‌سازی', 'duration_minutes' => 10, 'mergeable' => true, 'requirements' => [['type_uuid' => $operator->getUuid(), 'count' => 1]]],
        ]);

        $this->setSegments($user, $bikini, [
            ['sequence' => 1, 'name' => 'آماده‌سازی', 'duration_minutes' => 8, 'mergeable' => true, 'requirements' => [['type_uuid' => $operator->getUuid(), 'count' => 2]]],
        ]);

        $plan = $this->preview($user, $face, $address, [
            'item_uuids' => [$face->getUuid(), $bikini->getUuid()],
        ]);

        $segments = $plan['data']['segments'];

        self::assertCount(1, $segments);
        self::assertSame(2, $segments[0]['requirements'][0]['count'], 'بیشینه، نه اولی');
    }

    /**
     * ⭐ قطعیت: دو build با همان ورودی باید **بایت‌به‌بایت** یکی باشند.
     *
     * ترتیب منابع و بخش‌ها از کوئری می‌آید و کوئریِ بدون `ORDER BY` قطعی نیست. برنامه‌ای
     * که بین پیش‌نمایش و رزرو جابه‌جا شود، یعنی کاربر چیزی را تأیید کرده که رزرو نشد.
     */
    public function testTwoIdenticalBuildsProduceTheSamePlan(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service = $this->service($section, 'لیزر', 20);

        $room     = $this->resourceType($address, 'room', 'اتاق');
        $operator = $this->resourceType($address, 'operator', 'اپراتور');

        foreach (['اتاق ۱', 'اتاق ۲', 'اتاق ۳'] as $name) {
            $this->resource($user, $address, $room, $name);
        }
        foreach (['اپراتور ۱', 'اپراتور ۲'] as $name) {
            $this->resource($user, $address, $operator, $name);
        }

        $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'آماده‌سازی', 'duration_minutes' => 5, 'requirements' => [['type_uuid' => $room->getUuid()], ['type_uuid' => $operator->getUuid()]]],
            ['sequence' => 2, 'name' => 'کار اصلی', 'duration_minutes' => 20, 'requirements' => [['type_uuid' => $room->getUuid()]]],
        ]);

        $first  = $this->preview($user, $service, $address)['data'];
        $second = $this->preview($user, $service, $address)['data'];

        self::assertSame(
            json_encode($first, JSON_UNESCAPED_UNICODE),
            json_encode($second, JSON_UNESCAPED_UNICODE),
        );
    }

    /**
     * ⭐ الگوی نمونه نقطهٔ شروع است، نه پیکربندی نهایی — و **بازنویسی خاموش نمی‌کند**.
     */
    public function testSeedingCreatesAStarterSetAndRefusesToOverwrite(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service = $this->service($section, 'لیزر', 20);
        $room     = $this->resourceType($address, 'room', 'اتاق');
        $operator = $this->resourceType($address, 'operator', 'اپراتور');
        $this->resource($user, $address, $room, 'اتاق ۱');
        $this->resource($user, $address, $operator, 'اپراتور ۱');

        $seed = static::getContainer()->get(\App\Appointment\Plan\Command\SeedSegmentTemplatesCommand::class);
        $run  = static function (array $input) use ($seed): array {
            $tester = new \Symfony\Component\Console\Tester\CommandTester($seed);
            $tester->execute($input);

            return [$tester->getStatusCode(), $tester->getDisplay()];
        };

        [$code] = $run(['--service' => $service->getUuid(), '--preset' => 'beauty']);
        self::assertSame(0, $code);

        $plan = $this->preview($user, $service, $address);
        self::assertCount(5, $plan['data']['segments']);
        self::assertSame('آماده‌سازی', $plan['data']['segments'][0]['name']);

        // بخشِ تمیزکاری بدون حضور بیمار است — همان چیزی که گزارش بهره‌وری با آن کار می‌کند.
        $cleanup = end($plan['data']['segments']);
        self::assertFalse($cleanup['patient_present']);

        // اجرای دوباره بدون `--force` دست به چیزی نمی‌زند.
        [, $display] = $run(['--service' => $service->getUuid(), '--preset' => 'dental']);
        self::assertStringContainsString('--force', $display);

        $unchanged = $this->preview($user, $service, $address);
        self::assertCount(5, $unchanged['data']['segments'], 'بدون --force بازنویسی نمی‌شود');

        [, $forced] = $run(['--service' => $service->getUuid(), '--preset' => 'dental', '--force' => true]);
        self::assertStringNotContainsString('--force', $forced);

        $replaced = $this->preview($user, $service, $address);
        self::assertCount(3, $replaced['data']['segments']);
    }

    /**
     * ⭐⭐ جایگزینی بخش‌ها **حذف‌کن-و-بنویس** است. اگر اعتبارسنجی بعد از حذف بیفتد،
     * سرویس بدون هیچ بخشی می‌ماند و نوبت‌دهی‌اش بی‌صدا به «یک بخش پیوسته» برمی‌گردد —
     * یعنی مدت و منابع همهٔ نوبت‌های بعدی عوض می‌شود، بدون اینکه کسی چیزی خواسته باشد.
     */
    public function testARejectedReplaceLeavesTheExistingSegmentsIntact(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service = $this->service($section, 'لیزر', 20);
        $room    = $this->resourceType($address, 'room', 'اتاق');
        $this->resource($user, $address, $room, 'اتاق ۱');

        $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'آماده‌سازی', 'duration_minutes' => 10, 'requirements' => [['type_uuid' => $room->getUuid()]]],
            ['sequence' => 2, 'name' => 'کار اصلی', 'duration_minutes' => 20, 'requirements' => [['type_uuid' => $room->getUuid()]]],
        ]);
        self::assertCount(2, $this->preview($user, $service, $address)['data']['segments']);

        // قیدِ ناشناخته: باید ۴۲۲ بگیرد **و** چیزی را خراب نکند.
        $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'بخش تازه', 'duration_minutes' => 15, 'requirements' => [
                ['type_uuid' => $room->getUuid(), 'constraints' => ['same_blood_type']],
            ]],
        ]);
        self::assertSame(422, $this->responseCode());

        $after = $this->preview($user, $service, $address);

        self::assertCount(2, $after['data']['segments'], 'بخش‌های قبلی باید دست‌نخورده مانده باشند');
        self::assertSame('آماده‌سازی', $after['data']['segments'][0]['name']);
    }

    public function testTooManySegmentsIsRejected(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service = $this->service($section, 'لیزر', 20);

        $segments = [];
        for ($i = 1; $i <= 21; $i++) {
            $segments[] = ['sequence' => $i, 'name' => sprintf('بخش %d', $i), 'duration_minutes' => 5];
        }

        $this->setSegments($user, $service, $segments);

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

    /**
     * ⭐ «نود دقیقه وقت بگذارید» برای نوبتی که چهل دقیقه‌اش انتظار است، حرفِ درستی نیست.
     * محاسبه یک‌جا در بک‌اند است تا هر کلاینت خودش جمع نزند.
     */
    public function testThePlanReportsHowLongThePatientIsActuallyPresent(): void
    {
        [$user, $section, $address] = $this->clinicWithBranch();
        $service = $this->service($section, 'لیزر', 20);
        $room    = $this->resourceType($address, 'room', 'اتاق');
        $this->resource($user, $address, $room, 'اتاق ۱');

        $this->setSegments($user, $service, [
            ['sequence' => 1, 'name' => 'ویزیت', 'duration_minutes' => 20, 'patient_present' => true, 'requirements' => [['type_uuid' => $room->getUuid()]]],
            ['sequence' => 2, 'name' => 'تمیزکاری', 'duration_minutes' => 15, 'patient_present' => false, 'requirements' => [['type_uuid' => $room->getUuid()]]],
        ]);

        $plan = $this->preview($user, $service, $address)['data'];

        self::assertSame(35, $plan['total_minutes']);
        self::assertSame(20, $plan['patient_facing_minutes']);
    }

    public function testForeignServiceIsNotFound(): void
    {
        [$user, , $address] = $this->clinicWithBranch();
        [, $otherSection]   = $this->clinicWithBranch();
        $foreign = $this->service($otherSection, 'سرویس بیگانه', 20);

        $this->authJson('POST', '/api/v1/appointment-plan/preview', $user, [
            'service_uuid' => $foreign->getUuid(),
            'branch_uuid'  => $address->getUuid(),
        ]);

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