<?php

namespace App\Tests\Appointment;

use App\Appointment\Entity\WeeklySchedule;
use App\Appointment\Service\SlotCalculatorService;
use App\Auth\Entity\User;
use App\Clinic\Entity\Clinic;
use App\Doctor\Entity\Doctor;
use App\Tests\ApiTestCase;

/**
 * خاموش‌بودنِ نوبت‌دهی آنلاین فقط رزرو عمومی از سایت را متوقف می‌کند. کانتکست مدیریت
 * (پزشک/منشی/ادمین، $forManagement=true) باید مستقلاً اسلات ببیند و بتواند نوبت ثبت کند.
 * تاریخِ گذشته اما در هر دو حالت رد می‌شود.
 */
class OnlineBookingManagementTest extends ApiTestCase
{
    /** @return array{0: Doctor, 1: string} */
    private function makeDoctorWithBookingDisabled(): 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'           => '10:00',
                'end_time'             => '12:00',
                'duration_per_patient' => 30,
                'location_id'          => 1,
            ]]],
        ]);
        $schedule->setMeta(['online_booking_enabled' => false]);
        $this->em->persist($schedule);
        $this->em->flush();

        return [$doctor, $date];
    }

    public function testPublicSeesNoSlotsWhenBookingDisabled(): void
    {
        [$doctor, $date] = $this->makeDoctorWithBookingDisabled();
        $calc = static::getContainer()->get(SlotCalculatorService::class);

        $this->assertSame([], $calc->getAllSlotsWithAvailability($doctor, $date));
        $this->assertSame([], $calc->getAvailableSlots($doctor, $date));
        $this->assertFalse($calc->hasAnyAvailability($doctor, $date));
        $this->assertSame(SlotCalculatorService::EMPTY_OUTSIDE_WINDOW, $calc->explainEmptyDay($doctor, $date));
    }

    public function testManagementSeesSlotsWhenBookingDisabled(): void
    {
        [$doctor, $date] = $this->makeDoctorWithBookingDisabled();
        $calc = static::getContainer()->get(SlotCalculatorService::class);

        $sessions = $calc->getAllSlotsWithAvailability($doctor, $date, null, true);
        $this->assertNotSame([], $sessions);
        $this->assertNotSame([], $calc->getAvailableSlots($doctor, $date, null, true));
        $this->assertTrue($calc->hasAnyAvailability($doctor, $date, null, true));
        $this->assertNull($calc->explainEmptyDay($doctor, $date, null, true));

        // location هم باید تعیین شود تا نوبتِ دستی بدون آدرس ثبت نشود.
        $firstStart = (int) $sessions[0]['slots'][0]['start'];
        $this->assertSame(1, $calc->resolveSlotLocationId($doctor, $firstStart, null, true));
        $this->assertNull($calc->resolveSlotLocationId($doctor, $firstStart, null, false));
    }

    public function testPastDateRejectedEvenForManagement(): void
    {
        [$doctor] = $this->makeDoctorWithBookingDisabled();
        $calc = static::getContainer()->get(SlotCalculatorService::class);

        $yesterday = date('Y-m-d', strtotime('yesterday'));
        $this->assertSame([], $calc->getAllSlotsWithAvailability($doctor, $yesterday, null, true));
    }

    private function slotsUri(Doctor $doctor, string $date, bool $management): string
    {
        return sprintf(
            '/api/v1/appointment-slots?doctor_uuid=%s&date=%s%s',
            $doctor->getUuid(),
            $date,
            $management ? '&management=1' : '',
        );
    }

    public function testEndpointAnonymousGetsNoSlots(): void
    {
        [$doctor, $date] = $this->makeDoctorWithBookingDisabled();

        $this->client->request('GET', $this->slotsUri($doctor, $date, false));
        $body = json_decode($this->client->getResponse()->getContent(), true);

        self::assertSame(200, $this->responseCode());
        self::assertSame([], $body['data']['sessions']);
        self::assertSame(SlotCalculatorService::EMPTY_OUTSIDE_WINDOW, $body['data']['empty_reason']);
    }

    public function testEndpointOwnerDoctorWithManagementGetsSlots(): void
    {
        [$doctor, $date] = $this->makeDoctorWithBookingDisabled();

        $body = $this->authJson('GET', $this->slotsUri($doctor, $date, true), $doctor->getUser());

        self::assertSame(200, $this->responseCode());
        self::assertNotSame([], $body['data']['sessions']);
    }

    public function testEndpointManagementFlagIgnoredForUnauthorizedUser(): void
    {
        [$doctor, $date] = $this->makeDoctorWithBookingDisabled();
        $stranger        = $this->createUser(['ROLE_USER']);

        // management=1 بدون دسترسی نباید توگل را دور بزند (fail-safe عمومی).
        $body = $this->authJson('GET', $this->slotsUri($doctor, $date, true), $stranger);

        self::assertSame(200, $this->responseCode());
        self::assertSame([], $body['data']['sessions']);
    }

    /**
     * سناریوی واقعیِ گزارش‌شده: پزشک عضو کلینیک، نوبت‌دهی آنلاینِ کلینیک خاموش،
     * مالک کلینیک از پنل «نوبت‌ها» با management=1 باید اسلات‌ها را ببیند.
     */
    public function testClinicOwnerSeesClinicSlotsWhenBookingDisabled(): void
    {
        $doctorOwner = $this->createUser(['ROLE_DOCTOR']);
        $doctor      = new Doctor($doctorOwner, 'دکتر احمدی');
        $this->em->persist($doctor);

        $clinicOwner = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']);
        $clinic      = new Clinic($clinicOwner);
        $clinic->setName('کلینیک تخصصی نمونه');
        $clinic->getDoctors()->add($doctor);
        $this->em->persist($clinic);

        $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' => '10:00', 'end_time' => '12:00',
                'duration_per_patient' => 30, 'location_id' => 1,
            ]]],
        ], $clinic);
        $schedule->setMeta(['online_booking_enabled' => false]);
        $this->em->persist($schedule);
        $this->em->flush();

        $uri = sprintf(
            '/api/v1/appointment-slots?doctor_uuid=%s&date=%s&clinic_uuid=%s&management=1',
            $doctor->getUuid(), $date, $clinic->getUuid(),
        );

        // مالک کلینیک → اسلات می‌بیند.
        $body = $this->authJson('GET', $uri, $clinicOwner);
        self::assertSame(200, $this->responseCode());
        self::assertNotSame([], $body['data']['sessions'], 'مالک کلینیک باید اسلات‌های محیط کلینیک را ببیند');

        // بدون احراز → عمومی، خالی.
        $this->client->request('GET', $uri);
        $anon = json_decode($this->client->getResponse()->getContent(), true);
        self::assertSame([], $anon['data']['sessions']);
    }
}
