<?php

namespace App\Tests\Course;

use App\Auth\Entity\User;
use App\Clinic\Entity\Clinic;
use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Entity\ServiceSection;
use App\Course\Entity\CourseSession;
use App\Course\Entity\TreatmentCourse;
use App\Course\Repository\CourseSessionRepository;
use App\Course\Repository\TreatmentCourseRepository;
use App\Course\Service\CourseSessionLinker;
use App\Doctor\Entity\Doctor;
use App\Doctor\Entity\DoctorAddress;
use App\Patient\Entity\PatientRecord;
use App\Tests\ApiTestCase;

/**
 * دورهٔ درمان — تسک ۱۲.
 *
 * «لیزر معمولاً شش تا هشت جلسه است»؛ طراحی قبلی فقط نوبت تکی می‌شناخت. تست‌ها روی سه
 * چیز تمرکز دارند: snapshot پروتکل، لنگر متحرک فاصله‌ها، و اینکه لغو یک جلسه بقیهٔ
 * دوره را خراب نکند.
 */
class TreatmentCourseTest extends ApiTestCase
{
    private int $slotCursor = 0;

    /** @return array{0: User, 1: ServiceSection, 2: DoctorAddress, 3: Doctor, 4: PatientRecord} */
    private function clinicWithPatient(): 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);

        $doctorUser = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
        $doctor     = new Doctor($doctorUser, 'دکتر دوره');
        $this->em->persist($doctor);

        $patientUser = $this->createUser(['ROLE_USER']);
        $patient     = new PatientRecord('clinic', (int) $clinic->getId(), $patientUser, 'clinic', (int) $clinic->getId());
        $this->em->persist($patient);
        $this->em->flush();

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

    private function service(ServiceSection $section, string $name = 'لیزر فول‌بادی'): ServiceItem
    {
        $item = new ServiceItem($section, $name);
        $item->setSoloDurationMinutes(30);
        $item->setPriceRials(5_000_000);
        $this->em->persist($item);
        $this->em->flush();

        return $item;
    }

    /** @param array<string, mixed> $extra */
    private function protocol(User $user, ServiceItem $service, array $extra = []): array
    {
        $body = $this->authJson('POST', '/api/v1/course-protocols', $user, $extra + [
            'service_uuid'  => $service->getUuid(),
            'session_count' => 8,
            'min_days'      => 21,
            'ideal_days'    => 28,
            'max_days'      => 45,
            'steps'         => [
                ['session_number' => 1, 'params' => ['energy' => 12]],
                ['session_number' => 2, 'params' => ['energy' => 14]],
                ['session_number' => 3, 'params' => ['energy' => 16]],
                ['session_number' => 4, 'params' => ['energy' => 18]],
            ],
        ]);

        self::assertSame(201, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));

        return $body['data'];
    }

    private function startCourse(User $user, PatientRecord $patient, string $protocolUuid): array
    {
        $body = $this->authJson('POST', '/api/v1/treatment-course', $user, [
            'patient_uuid'  => $patient->getUuid(),
            'protocol_uuid' => $protocolUuid,
        ]);

        self::assertSame(201, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));

        return $body['data'];
    }

    private function courseEntity(string $uuid): TreatmentCourse
    {
        return static::getContainer()->get(TreatmentCourseRepository::class)->findByUuid($uuid);
    }

    private function linker(): CourseSessionLinker
    {
        return static::getContainer()->get(CourseSessionLinker::class);
    }

    // ── پروتکل ──────────────────────────────────────────────────────────────

    public function testProtocolKeepsItsStepsAndSpacing(): void
    {
        [$user, $section] = $this->clinicWithPatient();
        $protocol = $this->protocol($user, $this->service($section));

        self::assertSame(8, $protocol['session_count']);
        self::assertSame([21, 28, 45], [$protocol['min_days'], $protocol['ideal_days'], $protocol['max_days']]);
        self::assertSame([1, 2, 3, 4], array_column($protocol['steps'], 'session_number'));
        self::assertSame(16, $protocol['steps'][2]['params']['energy']);
    }

    /** ترتیب فاصله‌ها معنا دارد؛ حداکثر کوچک‌تر از حداقل یعنی پروتکل غیرقابل اجرا. */
    public function testSpacingMustBeOrdered(): void
    {
        [$user, $section] = $this->clinicWithPatient();

        $this->authJson('POST', '/api/v1/course-protocols', $user, [
            'service_uuid'  => $this->service($section)->getUuid(),
            'session_count' => 6,
            'min_days'      => 30,
            'ideal_days'    => 20,
            'max_days'      => 45,
        ]);

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

    /** دورهٔ یک‌جلسه‌ای همان نوبت تکی است. */
    public function testASingleSessionCourseIsRejected(): void
    {
        [$user, $section] = $this->clinicWithPatient();

        $this->authJson('POST', '/api/v1/course-protocols', $user, [
            'service_uuid'  => $this->service($section)->getUuid(),
            'session_count' => 1,
            'min_days'      => 7,
            'ideal_days'    => 7,
            'max_days'      => 14,
        ]);

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

    public function testOneProtocolPerService(): void
    {
        [$user, $section] = $this->clinicWithPatient();
        $service = $this->service($section);

        $this->protocol($user, $service);

        $this->authJson('POST', '/api/v1/course-protocols', $user, [
            'service_uuid'  => $service->getUuid(),
            'session_count' => 4,
            'min_days'      => 7,
            'ideal_days'    => 14,
            'max_days'      => 21,
        ]);

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

    // ── شروع دوره ───────────────────────────────────────────────────────────

    public function testStartingACourseCreatesEverySessionWithItsParams(): void
    {
        [$user, $section, , , $patient] = $this->clinicWithPatient();
        $protocol = $this->protocol($user, $this->service($section));

        $course = $this->startCourse($user, $patient, $protocol['uuid']);

        self::assertCount(8, $course['sessions']);
        self::assertSame(array_fill(0, 8, 'planned'), array_column($course['sessions'], 'status'));
        self::assertSame(12, $course['sessions'][0]['params']['energy']);
        self::assertSame(18, $course['sessions'][3]['params']['energy']);

        // جلسات ۵ تا ۸ پارامتری در پروتکل ندارند — آرایهٔ خالی، نه خطا.
        self::assertSame([], (array) $course['sessions'][7]['params']);

        self::assertSame(
            ['completed' => 0, 'booked' => 0, 'planned' => 8, 'skipped' => 0, 'total' => 8],
            array_intersect_key($course['progress'], array_flip(['completed', 'booked', 'planned', 'skipped', 'total'])),
        );
        self::assertSame(1, $course['progress']['next_session_number']);
    }

    /** ⭐ تغییر پروتکل نباید دورهٔ در جریان را عوض کند. */
    public function testChangingTheProtocolLeavesRunningCoursesAlone(): void
    {
        [$user, $section, , , $patient] = $this->clinicWithPatient();
        $protocol = $this->protocol($user, $this->service($section));
        $course   = $this->startCourse($user, $patient, $protocol['uuid']);

        $this->authJson('PATCH', "/api/v1/course-protocol/{$protocol['uuid']}", $user, [
            'session_count' => 12,
            'min_days'      => 30,
            'ideal_days'    => 40,
            'max_days'      => 60,
        ]);
        self::assertSame(200, $this->responseCode());

        $after = $this->authJson('GET', "/api/v1/treatment-course/{$course['uuid']}", $user)['data'];

        self::assertSame(8, $after['session_count']);
        self::assertSame([21, 28, 45], [$after['min_days'], $after['ideal_days'], $after['max_days']]);
        self::assertCount(8, $after['sessions']);
    }

    public function testASecondActiveCourseForTheSameServiceIsRejected(): void
    {
        [$user, $section, , , $patient] = $this->clinicWithPatient();
        $protocol = $this->protocol($user, $this->service($section));

        $first = $this->startCourse($user, $patient, $protocol['uuid']);

        $body = $this->authJson('POST', '/api/v1/treatment-course', $user, [
            'patient_uuid'  => $patient->getUuid(),
            'protocol_uuid' => $protocol['uuid'],
        ]);

        self::assertSame(422, $this->responseCode());
        // پیام باید شناسهٔ دورهٔ موجود را بدهد تا اپراتور بتواند برود سراغش.
        self::assertStringContainsString($first['uuid'], $body['errors'][0]['message']);
    }

    /** رهاکردن دوره جا را برای دورهٔ تازه باز می‌کند. */
    public function testAbandoningACourseFreesTheSlotForANewOne(): void
    {
        [$user, $section, , , $patient] = $this->clinicWithPatient();
        $protocol = $this->protocol($user, $this->service($section));
        $course   = $this->startCourse($user, $patient, $protocol['uuid']);

        $this->authJson('POST', "/api/v1/treatment-course/{$course['uuid']}/abandon", $user, ['reason' => '']);
        self::assertSame(422, $this->responseCode(), 'رهاکردن بدون دلیل نباید پذیرفته شود');

        $abandoned = $this->authJson('POST', "/api/v1/treatment-course/{$course['uuid']}/abandon", $user, [
            'reason' => 'انصراف بیمار',
        ]);

        self::assertSame(200, $this->responseCode());
        self::assertSame('abandoned', $abandoned['data']['status']);

        $this->startCourse($user, $patient, $protocol['uuid']);
    }

    // ── پیشرفت و لنگر متحرک ─────────────────────────────────────────────────

    public function testProgressCountsCompletedSessionsAndPointsAtTheNextOne(): void
    {
        [$user, $section, $address, $doctor, $patient] = $this->clinicWithPatient();
        $service  = $this->service($section);
        $protocol = $this->protocol($user, $service);
        $course   = $this->startCourse($user, $patient, $protocol['uuid']);

        $this->completeSessions($course['uuid'], $doctor, $patient, $service, (int) $address->getClinicId(), 3);

        $after = $this->authJson('GET', "/api/v1/treatment-course/{$course['uuid']}", $user)['data'];

        self::assertSame(3, $after['progress']['completed']);
        self::assertSame(8, $after['progress']['total']);
        self::assertSame(4, $after['progress']['next_session_number']);
        self::assertSame(18, $after['progress']['next_params']['energy']);
    }

    /** ⭐ فاصله از آخرین جلسهٔ **انجام‌شده** حساب می‌شود، نه از شروع دوره. */
    public function testTheSuggestionAnchorsOnTheLastCompletedSession(): void
    {
        [$user, $section, $address, $doctor, $patient] = $this->clinicWithPatient();
        $service  = $this->service($section);
        $protocol = $this->protocol($user, $service);
        $course   = $this->startCourse($user, $patient, $protocol['uuid']);

        $completedAt = time() - 10 * 86400;
        $this->completeSessions($course['uuid'], $doctor, $patient, $service, (int) $address->getClinicId(), 3, $completedAt);

        $body = $this->authJson(
            'GET',
            "/api/v1/treatment-course/{$course['uuid']}/next-slot-suggestion?branch_uuid={$address->getUuid()}",
            $user,
        );

        self::assertSame(200, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));

        $data = $body['data'];

        self::assertSame(4, $data['session_number']);
        self::assertSame(18, $data['params']['energy']);
        self::assertSame($completedAt + 21 * 86400, $data['range']['min']);
        self::assertSame($completedAt + 45 * 86400, $data['range']['max']);
        self::assertSame($completedAt + 28 * 86400, $data['ideal_at']);
        self::assertNull($data['warning']);
    }

    public function testPassingTheMaximumGapProducesAWarning(): void
    {
        [$user, $section, $address, $doctor, $patient] = $this->clinicWithPatient();
        $service  = $this->service($section);
        $protocol = $this->protocol($user, $service);
        $course   = $this->startCourse($user, $patient, $protocol['uuid']);

        $this->completeSessions($course['uuid'], $doctor, $patient, $service, (int) $address->getClinicId(), 1, time() - 50 * 86400);

        $data = $this->authJson(
            'GET',
            "/api/v1/treatment-course/{$course['uuid']}/next-slot-suggestion?branch_uuid={$address->getUuid()}",
            $user,
        )['data'];

        self::assertNotNull($data['warning']);
        self::assertStringContainsString('45', $data['warning'], 'پیام باید حداکثر فاصلهٔ همان دوره را بگوید');
    }

    // ── لغو یک جلسهٔ وسط دوره ───────────────────────────────────────────────

    /** ⭐ لغو یک جلسه فقط همان جلسه را برمی‌گرداند؛ بقیهٔ دوره دست‌نخورده. */
    public function testCancellingOneSessionOnlyResetsThatSession(): void
    {
        [$user, $section, $address, $doctor, $patient] = $this->clinicWithPatient();
        $service  = $this->service($section);
        $protocol = $this->protocol($user, $service);
        $course   = $this->startCourse($user, $patient, $protocol['uuid']);

        $entity   = $this->courseEntity($course['uuid']);
        $sessions = $entity->getSessions()->toArray();
        usort($sessions, static fn (CourseSession $a, CourseSession $b): int => $a->getSessionNumber() <=> $b->getSessionNumber());

        $appointment = $this->appointment($doctor, $patient, $service, (int) $address->getClinicId());
        $this->linker()->link($sessions[0], $appointment);

        $second = $this->appointment($doctor, $patient, $service, (int) $address->getClinicId());
        $this->linker()->link($this->reloadSession($sessions[1]->getUuid()), $second);

        $before = $this->authJson('GET', "/api/v1/treatment-course/{$course['uuid']}", $user)['data'];
        self::assertSame(['booked', 'booked'], array_slice(array_column($before['sessions'], 'status'), 0, 2));

        self::assertTrue($this->linker()->unlink($this->reloadAppointment($appointment->getUuid())));

        $after = $this->authJson('GET', "/api/v1/treatment-course/{$course['uuid']}", $user)['data'];

        self::assertSame('planned', $after['sessions'][0]['status']);
        self::assertSame('booked', $after['sessions'][1]['status'], 'بقیهٔ جلسات نباید دست بخورند');
    }

    public function testTheCourseCompletesOnlyWhenEverySessionIsDone(): void
    {
        [$user, $section, $address, $doctor, $patient] = $this->clinicWithPatient();
        $service  = $this->service($section);

        // پروتکل کوتاه تا کل دوره در تست تمام شود.
        $protocol = $this->authJson('POST', '/api/v1/course-protocols', $user, [
            'service_uuid'  => $service->getUuid(),
            'session_count' => 2,
            'min_days'      => 7,
            'ideal_days'    => 14,
            'max_days'      => 21,
        ])['data'];

        $course = $this->startCourse($user, $patient, $protocol['uuid']);

        $this->completeSessions($course['uuid'], $doctor, $patient, $service, (int) $address->getClinicId(), 1);
        self::assertSame('active', $this->authJson('GET', "/api/v1/treatment-course/{$course['uuid']}", $user)['data']['status']);

        $this->completeSessions($course['uuid'], $doctor, $patient, $service, (int) $address->getClinicId(), 1);
        self::assertSame('completed', $this->authJson('GET', "/api/v1/treatment-course/{$course['uuid']}", $user)['data']['status']);
    }

    // ── جداسازی محیط ────────────────────────────────────────────────────────

    /**
     * ⭐ «سخت‌گیرانه‌تر برنده»: قانون `spacing` کلینیک با پروتکل دوره نمی‌جنگد.
     *
     * پروتکل ۷ روز می‌گوید و قانون ۲۱ روز؛ فاصلهٔ مؤثر باید ۲۱ باشد. اگر پروتکل برنده
     * می‌شد، قانونِ ایمنی کلینیک با تعریف یک پروتکل کوتاه دور زده می‌شد.
     */
    public function testTheStricterOfProtocolAndSpacingPolicyWins(): void
    {
        [$user, $section, , , $patient] = $this->clinicWithPatient();
        $service  = $this->service($section);
        $protocol = $this->protocol($user, $service, ['min_days' => 7, 'ideal_days' => 10, 'max_days' => 20]);
        $started  = $this->startCourse($user, $patient, $protocol['uuid']);

        $course    = $this->courseEntity($started['uuid']);
        $scheduler = static::getContainer()->get(\App\Course\Service\CourseScheduler::class);

        self::assertSame(7, $scheduler->effectiveMinDays($course), 'بدون قانون، پروتکل حاکم است');

        $policy = new \App\Policy\Entity\Policy(
            $course->getEntityType(),
            $course->getEntityId(),
            \App\Policy\Entity\Policy::CATEGORY_SPACING,
            'حداقل ۲۱ روز بین جلسات لیزر',
        );
        $policy->setCondition(['match' => 'all', 'conditions' => []]);
        $policy->setEffects([['type' => 'min_days_between', 'value' => 21]]);
        $policy->setActive(true);

        $this->em->persist($policy);
        $this->em->flush();
        $this->em->clear();

        self::assertSame(
            21,
            $scheduler->effectiveMinDays($this->courseEntity($started['uuid'])),
            'قانون سخت‌گیرتر برنده است',
        );
    }

    /**
     * ⭐ سقف افق: جلسه‌ای که حتی حداقلِ فاصله‌اش بیرون ۹۰ روز می‌افتد **رد** می‌شود، نه
     * اینکه `book-all` را بشکند. جلسات بیرون بازه `planned` می‌مانند تا بعداً رزرو شوند.
     */
    public function testSessionsBeyondTheHorizonAreSkippedNotFailed(): void
    {
        [$user, $section, , , $patient] = $this->clinicWithPatient();
        $service  = $this->service($section);

        // فاصلهٔ ۶۰ روزه با ۸ جلسه: جلسهٔ سوم به بعد بیرون افق ۹۰ روزه است.
        $protocol = $this->protocol($user, $service, ['min_days' => 60, 'ideal_days' => 60, 'max_days' => 70]);
        $started  = $this->startCourse($user, $patient, $protocol['uuid']);

        $course    = $this->courseEntity($started['uuid']);
        $scheduler = static::getContainer()->get(\App\Course\Service\CourseScheduler::class);

        $now     = time();
        $minDays = $scheduler->effectiveMinDays($course, $now);
        $horizon = $now + \App\Course\Service\CourseScheduler::SEARCH_HORIZON_DAYS * 86400;

        // لنگر دوم = لنگر اول + ۶۰ روز؛ سومی از افق می‌گذرد.
        $third = $now + 3 * $minDays * 86400;

        self::assertGreaterThan($horizon, $third, 'جلسهٔ سوم باید بیرون افق باشد');
        self::assertSame(60, $minDays);

        // خودِ دوره دست‌نخورده می‌ماند: هیچ جلسه‌ای حذف نمی‌شود.
        self::assertCount(8, $course->getSessions()->toArray());
    }

    /**
     * ⭐ `book-all` همه یا هیچ است.
     *
     * تقویم فقط یک روزِ هفته باز است و فاصلهٔ پروتکل ۱ تا ۲ روز؛ پس جلسهٔ اول وقت پیدا
     * می‌کند و جلسهٔ دوم نه. اگر تراکنش کار نکند، بیمار با یک نوبتِ تنها از یک دورهٔ
     * هشت‌جلسه‌ای می‌ماند و هیچ‌کس نمی‌فهمد کجا قطع شد.
     */
    public function testAFailedBookAllLeavesEverySessionPlanned(): void
    {
        [$user, $section, $address, $doctor, $patient] = $this->clinicWithPatient();
        $service = $this->service($section);

        $type = $this->authJson('POST', '/api/v1/resource-types', $user, [
            'address_uuid' => $address->getUuid(),
            'code'         => 'room',
            'name'         => 'اتاق',
        ]);
        self::assertSame(201, $this->responseCode(), json_encode($type, JSON_UNESCAPED_UNICODE));

        $resource = $this->authJson('POST', '/api/v1/resource', $user, [
            'address_uuid' => $address->getUuid(),
            'type_uuid'    => $type['data']['uuid'],
            'name'         => 'اتاق ۱',
        ]);
        self::assertSame(201, $this->responseCode());

        // فقط شنبه‌ها باز است.
        $this->authJson('PUT', "/api/v1/resource/{$resource['data']['uuid']}/calendar", $user, [
            'days' => [6 => [['start_minute' => 540, 'end_minute' => 1020]]],
        ]);
        self::assertSame(200, $this->responseCode());

        $this->authJson('PUT', "/api/v1/service-item/{$service->getUuid()}/segments", $user, [
            'segments' => [
                ['sequence' => 1, 'name' => 'جلسه', 'duration_minutes' => 30, 'requirements' => [['type_uuid' => $type['data']['uuid']]]],
            ],
        ]);
        self::assertSame(200, $this->responseCode());

        // بازهٔ ۱ تا ۲ روز: جلسهٔ دوم حتماً بیرون تنها روزِ باز می‌افتد.
        $protocol = $this->protocol($user, $service, ['min_days' => 1, 'ideal_days' => 1, 'max_days' => 2]);
        $started  = $this->startCourse($user, $patient, $protocol['uuid']);

        $body = $this->authJson('POST', "/api/v1/treatment-course/{$started['uuid']}/book-all", $user, [
            'branch_uuid' => $address->getUuid(),
            'doctor_uuid' => $doctor->getUuid(),
        ]);

        self::assertSame(422, $this->responseCode(), json_encode($body, JSON_UNESCAPED_UNICODE));

        $this->em->clear();
        $course = $this->courseEntity($started['uuid']);

        foreach ($course->getSessions() as $session) {
            self::assertSame(
                CourseSession::STATUS_PLANNED,
                $session->getStatus(),
                sprintf('جلسهٔ %d نباید رزرو مانده باشد', $session->getSessionNumber()),
            );
            self::assertNull($session->getAppointment());
        }
    }

    public function testAnotherClinicCannotSeeTheCourse(): void
    {
        [$owner, $section, , , $patient] = $this->clinicWithPatient();
        [$other] = $this->clinicWithPatient();

        $protocol = $this->protocol($owner, $this->service($section));
        $course   = $this->startCourse($owner, $patient, $protocol['uuid']);

        $this->authJson('GET', "/api/v1/treatment-course/{$course['uuid']}", $other);
        self::assertSame(404, $this->responseCode());
    }

    // ── کمکی ────────────────────────────────────────────────────────────────

    private function reloadSession(string $uuid): CourseSession
    {
        return static::getContainer()->get(CourseSessionRepository::class)->findByUuid($uuid);
    }

    private function reloadAppointment(string $uuid): \App\Appointment\Entity\Appointment
    {
        return static::getContainer()->get(\Doctrine\ORM\EntityManagerInterface::class)
            ->getRepository(\App\Appointment\Entity\Appointment::class)
            ->findOneBy(['uuid' => $uuid]);
    }

    private function appointment(Doctor $doctor, PatientRecord $patient, ServiceItem $service, int $clinicId): \App\Appointment\Entity\Appointment
    {
        $em = static::getContainer()->get(\Doctrine\ORM\EntityManagerInterface::class);

        $start = time() + 86400 + (++$this->slotCursor) * 3600;

        $appointment = new \App\Appointment\Entity\Appointment(
            $em->getRepository(Doctor::class)->find($doctor->getId()),
            $em->getRepository(PatientRecord::class)->find($patient->getId())->getUser(),
            $start,
            $start + 1800,
        );
        $appointment->assignTenantPair('clinic', $clinicId);
        $appointment->setServiceItem($em->getRepository(ServiceItem::class)->find($service->getId()));
        $appointment->setPatientName('بیمار دوره');

        $em->persist($appointment);
        $em->flush();

        return $appointment;
    }

    /** n جلسهٔ بعدی را رزرو و انجام‌شده می‌کند. */
    private function completeSessions(
        string $courseUuid,
        Doctor $doctor,
        PatientRecord $patient,
        ServiceItem $service,
        int $clinicId,
        int $count,
        ?int $completedAt = null,
    ): void {
        for ($i = 0; $i < $count; $i++) {
            $course   = $this->courseEntity($courseUuid);
            $sessions = $course->plannedSessions();

            usort($sessions, static fn (CourseSession $a, CourseSession $b): int
                => $a->getSessionNumber() <=> $b->getSessionNumber());

            $appointment = $this->appointment($doctor, $patient, $service, $clinicId);

            $this->linker()->link($this->reloadSession($sessions[0]->getUuid()), $appointment);
            $this->linker()->complete($this->reloadAppointment($appointment->getUuid()), $completedAt);
        }
    }
}
