<?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\Shared\Constant\ErrorCodes;
use App\Tests\ApiTestCase;

/**
 * `PATCH /api/v1/appointment/{uuid}` در حالت نوبت‌دهی سرویسی: مدت داده است نه ورودی.
 *
 * پیش از این، مدت دلخواه پذیرفته می‌شد و فقط `service_item_uuid` تکی به‌روز می‌شد در
 * حالی که `service_items` دست‌نخورده می‌ماند — یعنی نوبت با سرویس‌های قبلی و مدت جدید.
 *
 * ⛔ در حالت اسلاتی هیچ‌کدام از این بررسی‌ها اجرا نمی‌شود؛ آخرین تست همین را می‌سنجد.
 */
class PatchServiceDurationTest extends ApiTestCase
{
    /** @return array{0:\App\Auth\Entity\User,1:Doctor,2:ServiceSection} */
    private function doctorInMode(string $mode, int $buffer = 0): array
    {
        $owner  = $this->createUser(['ROLE_DOCTOR']);
        $doctor = new Doctor($owner, 'دکتر ویرایش');
        $this->em->persist($doctor);
        $this->em->flush();

        $date   = date('Y-m-d', strtotime('+2 days'));
        $dayKey = (string) (((int) date('w', strtotime($date)) + 1) % 7);
        $schedule = $this->newWeeklySchedule($doctor, [
            $dayKey => ['sessions' => [[
                'active' => true, 'start_time' => '09:00', 'end_time' => '18:00',
                'duration_per_patient' => 20, 'location_id' => 1,
            ]]],
        ]);
        $schedule->setMeta(['booking_mode' => $mode, 'buffer_minutes' => $buffer]);
        $this->em->persist($schedule);

        $section = new ServiceSection('doctor', $doctor->getId(), 'بخش ویرایش');
        $this->em->persist($section);
        $this->em->flush();

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

    private function service(ServiceSection $section, string $name, int $minutes): ServiceItem
    {
        $item = new ServiceItem($section, $name, 0);
        $item->setDurationMinutes($minutes)->setBookable(true);
        $this->em->persist($item);
        $this->em->flush();

        return $item;
    }

    /** نوبت زمان‌دارِ تأییدشده با مدت دلخواه. */
    private function appointment(Doctor $doctor, int $minutes, array $items = []): Appointment
    {
        $patient = $this->createUser(['ROLE_USER']);
        $start   = (int) strtotime(date('Y-m-d', strtotime('+2 days')) . ' 10:00');
        $appt    = $this->newAppointment($doctor, $patient, $start, $start + $minutes * 60);
        $appt->transitionTo(Appointment::STATUS_CONFIRMED);
        if ($items !== []) {
            $appt->replaceServiceItems($items);
        }
        $this->em->persist($appt);
        $this->em->flush();

        return $appt;
    }

    private function patch(Appointment $appt, \App\Auth\Entity\User $actor, array $body): array
    {
        return $this->authJson('PATCH', '/api/v1/appointment/' . $appt->getUuid(), $actor, $body + [
            'version' => $appt->getVersion(),
        ]);
    }

    private function at(string $time): int
    {
        return (int) strtotime(date('Y-m-d', strtotime('+2 days')) . ' ' . $time);
    }

    // ── ✅ موفق ──────────────────────────────────────────────────────────────

    public function testReplacingServicesRecomputesTheStoredDuration(): void
    {
        [$owner, $doctor, $section] = $this->doctorInMode(WeeklySchedule::MODE_SERVICE, buffer: 10);
        $face   = $this->service($section, 'لیزر صورت', 20);
        $bikini = $this->service($section, 'لیزر بیکینی', 15);
        $appt   = $this->appointment($doctor, 20, [$face]);

        $res = $this->patch($appt, $owner, [
            'slot_start'         => $this->at('11:00'),
            'slot_end'           => $this->at('11:35'),   // ۳۵ = ۲۰ + ۱۵
            'service_item_uuids' => [$face->getUuid(), $bikini->getUuid()],
        ]);

        self::assertSame(200, $this->responseCode());
        self::assertSame(35, $res['data']['data']['service_total_minutes']);
        self::assertSame(10, $res['data']['data']['service_buffer_minutes']);
        self::assertCount(2, $res['data']['data']['service_items']);
    }

    public function testTheLegacySingleServiceFollowsTheList(): void
    {
        [$owner, $doctor, $section] = $this->doctorInMode(WeeklySchedule::MODE_SERVICE);
        $old = $this->service($section, 'سرویس قدیمی', 20);
        $new = $this->service($section, 'سرویس جدید', 30);
        $appt = $this->appointment($doctor, 20, [$old]);

        $res = $this->patch($appt, $owner, [
            'slot_start'         => $this->at('11:00'),
            'slot_end'           => $this->at('11:30'),
            'service_item_uuids' => [$new->getUuid()],
        ]);

        self::assertSame(200, $this->responseCode());
        self::assertSame($new->getUuid(), $res['data']['data']['service_item']['uuid'],
            'ستون تکی باید با فهرست هم‌گام شود، وگرنه لیست‌ها نام سرویس قدیمی را نشان می‌دهند');
    }

    public function testNoteOnlyPatchSkipsDurationValidation(): void
    {
        [$owner, $doctor, $section] = $this->doctorInMode(WeeklySchedule::MODE_SERVICE);
        $item = $this->service($section, 'مشاوره', 30);
        // نوبتی که مدتش با سرویسش نمی‌خواند (دادهٔ قدیمی).
        $appt = $this->appointment($doctor, 20, [$item]);

        $this->patch($appt, $owner, ['note' => 'یادداشت تازه']);

        self::assertSame(200, $this->responseCode(), 'PATCH بدون slot نباید مدت را بسنجد');
    }

    public function testReserveEntryStoresServicesWithoutDurationCheck(): void
    {
        [$owner, $doctor, $section] = $this->doctorInMode(WeeklySchedule::MODE_SERVICE);
        $item = $this->service($section, 'لیزر', 45);
        $appt = $this->appointment($doctor, 30, [$item]);
        $midnight = $this->at('00:00');

        $res = $this->patch($appt, $owner, [
            'slot_start'         => $midnight,
            'slot_end'           => $midnight,   // رزرو: start == end
            'is_reserve'         => true,
            'service_item_uuids' => [$item->getUuid()],
        ]);

        self::assertSame(200, $this->responseCode(), 'نوبت رزرو از بررسی مدت معاف است');
        self::assertTrue($res['data']['data']['is_reserve']);
        self::assertSame(45, $res['data']['data']['service_total_minutes'],
            'مدت برای تبدیل بعدیِ رزرو به نوبت زمان‌دار ذخیره می‌شود');
    }

    public function testReserveConvertsToATimedAppointmentThroughPatch(): void
    {
        [$owner, $doctor, $section] = $this->doctorInMode(WeeklySchedule::MODE_SERVICE);
        $item = $this->service($section, 'لیزر', 30);

        $patient  = $this->createUser(['ROLE_USER']);
        $midnight = $this->at('00:00');
        $reserve  = $this->newAppointment($doctor, $patient, $midnight, $midnight);
        $reserve->rescheduleTo($midnight, $midnight, true);
        $reserve->replaceServiceItems([$item]);
        $reserve->transitionTo(Appointment::STATUS_CONFIRMED);
        $this->em->persist($reserve);
        $this->em->flush();

        $res = $this->patch($reserve, $owner, [
            'slot_start' => $this->at('12:00'),
            'slot_end'   => $this->at('12:30'),
            'is_reserve' => false,
        ]);

        self::assertSame(200, $this->responseCode(), 'تبدیل رزرو با همان PATCH انجام می‌شود — endpoint جدید لازم نیست');
        self::assertFalse($res['data']['data']['is_reserve']);
        self::assertSame(30, $res['data']['data']['service_total_minutes']);
    }

    // ── ❌ خطا ───────────────────────────────────────────────────────────────

    public function testMismatchedDurationIsRejectedWithTheCorrectMinutes(): void
    {
        [$owner, $doctor, $section] = $this->doctorInMode(WeeklySchedule::MODE_SERVICE);
        $item = $this->service($section, 'لیزر', 45);
        $appt = $this->appointment($doctor, 45, [$item]);

        $res = $this->patch($appt, $owner, [
            'slot_start' => $this->at('11:00'),
            'slot_end'   => $this->at('11:20'),   // ۲۰ دقیقه به‌جای ۴۵
        ]);

        self::assertSame(422, $this->responseCode());
        self::assertSame(ErrorCodes::ERR_APPOINTMENT_003, $res['errors'][0]['code']);
        self::assertSame('slot_end', $res['errors'][0]['field']);
        self::assertStringContainsString('45', $res['errors'][0]['message'], 'پیام باید مدت درست را بگوید');
    }

    public function testForeignServiceIsRejected(): void
    {
        [$owner, $doctor, $section] = $this->doctorInMode(WeeklySchedule::MODE_SERVICE);
        $mine = $this->service($section, 'سرویس خودم', 30);
        $appt = $this->appointment($doctor, 30, [$mine]);

        [, $otherDoctor, $otherSection] = $this->doctorInMode(WeeklySchedule::MODE_SERVICE);
        $foreign = $this->service($otherSection, 'سرویس بیگانه', 30);
        self::assertNotNull($otherDoctor->getId());

        $res = $this->patch($appt, $owner, [
            'slot_start'         => $this->at('11:00'),
            'slot_end'           => $this->at('11:30'),
            'service_item_uuids' => [$foreign->getUuid()],
        ]);

        self::assertSame(422, $this->responseCode());
        self::assertSame('سرویس انتخاب‌شده به این محل نوبت‌دهی تعلق ندارد', $res['errors'][0]['message']);
    }

    // ── ⚠️ مرزی — خط سرخ ─────────────────────────────────────────────────────

    public function testSlotModeAppointmentKeepsAcceptingAnyDuration(): void
    {
        [$owner, $doctor, $section] = $this->doctorInMode(WeeklySchedule::MODE_SLOT);
        $item = $this->service($section, 'سرویس اسلاتی', 45);
        $appt = $this->appointment($doctor, 20, [$item]);

        $res = $this->patch($appt, $owner, [
            'slot_start' => $this->at('11:00'),
            'slot_end'   => $this->at('11:20'),   // با مدت سرویس نمی‌خواند — و باید بخورد
        ]);

        self::assertSame(200, $this->responseCode(), 'حالت اسلاتی نباید هیچ اعتبارسنجی سرویسی بگیرد');
        self::assertNull($res['data']['data']['service_total_minutes'], 'ستون سرویسی در حالت اسلاتی null می‌ماند');
    }

    public function testServiceModeAppointmentWithoutAnyServiceIsNotBlocked(): void
    {
        [$owner, $doctor] = $this->doctorInMode(WeeklySchedule::MODE_SERVICE);
        $appt = $this->appointment($doctor, 20);   // دادهٔ قدیمی: بدون سرویس

        $res = $this->patch($appt, $owner, [
            'slot_start' => $this->at('11:00'),
            'slot_end'   => $this->at('11:20'),
        ]);

        self::assertSame(200, $this->responseCode(), 'نوبت سرویسی بدون سرویس نباید قفل شود');
        self::assertNull($res['data']['data']['service_total_minutes']);
    }
}
