<?php

namespace App\Tests\Appointment;

use App\Appointment\Entity\WeeklySchedule;
use App\Clinic\Entity\Clinic;
use App\Clinic\Repository\ClinicDoctorPermissionRepository;
use App\Doctor\Entity\Doctor;
use App\Doctor\Entity\DoctorAddress;
use App\Tests\ApiTestCase;

/**
 * تنظیمات نوبت‌دهی per-context است: مطب شخصی پزشک (بدون clinic_uuid) فقط برای خود
 * پزشک و ادمین باز است، و کلینیک با clinic_uuid فقط برنامهٔ همان کلینیک را
 * می‌بیند/می‌نویسد. این دو برنامهٔ جدا هستند و روی هم اثر نمی‌گذارند.
 */
class ClinicOwnerScheduleAccessTest extends ApiTestCase
{
    private function makeDoctor(string $name): Doctor
    {
        $user   = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
        $doctor = new Doctor($user, $name);
        $doctor->setMobileNumber($user->getMobileNumber());
        $this->em->persist($doctor);
        $this->em->flush();

        return $doctor;
    }

    private function makeClinicWith(Doctor ...$doctors): array
    {
        $owner  = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']);
        $clinic = new Clinic($owner);
        $clinic->setName('کلینیک تست');
        foreach ($doctors as $d) {
            $clinic->getDoctors()->add($d);
        }
        $this->em->persist($clinic);
        $this->em->flush();

        return [$owner, $clinic];
    }

    private function addressFor(Doctor $doctor): DoctorAddress
    {
        $address = DoctorAddress::forDoctor($doctor);
        $this->em->persist($address);
        $this->em->flush();

        return $address;
    }

    private function clinicAddress(Clinic $clinic): DoctorAddress
    {
        $address = DoctorAddress::forClinic($clinic->getId());
        $this->em->persist($address);
        $this->em->flush();

        return $address;
    }

    private function schedulePayload(Doctor $doctor, int $locationId, string $start, ?Clinic $clinic = null): array
    {
        return array_filter([
            'doctor_uuid' => $doctor->getUuid(),
            'clinic_uuid' => $clinic?->getUuid(),
            'schedule'    => [
                ['day' => 'saturday', 'sessions' => [
                    ['active' => true, 'location_id' => $locationId, 'start' => $start, 'end' => '12:00'],
                ]],
            ],
        ], fn($v) => $v !== null);
    }

    public function testClinicOwnerCanReadAndWriteMemberDoctorScheduleInClinicContext(): void
    {
        $doctor           = $this->makeDoctor('دکتر عضو');
        [$owner, $clinic] = $this->makeClinicWith($doctor);
        $address          = $this->clinicAddress($clinic);

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, $this->schedulePayload($doctor, $address->getId(), '09:00', $clinic));
        self::assertSame(201, $this->responseCode());

        $this->authJson('GET', "/api/v1/appointment-settings/weekly-schedule/{$doctor->getUuid()}?clinic_uuid={$clinic->getUuid()}", $owner);
        self::assertSame(200, $this->responseCode());

        $this->authJson('PATCH', "/api/v1/appointment-settings/weekly-schedule/{$doctor->getUuid()}", $owner, [
            'clinic_uuid' => $clinic->getUuid(),
            'schedule'    => [
                ['day' => 'saturday', 'sessions' => [
                    ['active' => true, 'location_id' => $address->getId(), 'start' => '10:00', 'end' => '13:00'],
                ]],
            ],
        ]);
        self::assertSame(200, $this->responseCode());
    }

    public function testClinicOwnerCannotTouchDoctorPersonalSchedule(): void
    {
        $doctor  = $this->makeDoctor('دکتر عضو');
        [$owner] = $this->makeClinicWith($doctor);
        $address = $this->addressFor($doctor);

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, $this->schedulePayload($doctor, $address->getId(), '09:00'));

        self::assertSame(403, $this->responseCode(), 'مطب شخصی پزشک از دسترس کلینیک خارج است');
    }

    public function testClinicContextRejectsPersonalAddress(): void
    {
        $doctor           = $this->makeDoctor('دکتر عضو');
        [$owner, $clinic] = $this->makeClinicWith($doctor);
        $personal         = $this->addressFor($doctor);

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, $this->schedulePayload($doctor, $personal->getId(), '09:00', $clinic));

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

    public function testPersonalContextRejectsClinicAddress(): void
    {
        $doctor        = $this->makeDoctor('دکتر عضو');
        [, $clinic]    = $this->makeClinicWith($doctor);
        $clinicAddress = $this->clinicAddress($clinic);

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $doctor->getUser(), $this->schedulePayload($doctor, $clinicAddress->getId(), '09:00'));

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

    public function testStrangerClinicUuidIsRejected(): void
    {
        $doctor      = $this->makeDoctor('دکتر مستقل');
        $outsider    = $this->makeDoctor('دکتر دیگر');
        [, $clinic]  = $this->makeClinicWith($outsider);
        $address     = $this->addressFor($doctor);

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $doctor->getUser(), $this->schedulePayload($doctor, $address->getId(), '09:00', $clinic));

        self::assertSame(422, $this->responseCode(), 'پزشک عضو این کلینیک نیست');
    }

    public function testPersonalAndClinicSchedulesCoexistIndependently(): void
    {
        $doctor           = $this->makeDoctor('دکتر دو-محیطی');
        [$owner, $clinic] = $this->makeClinicWith($doctor);
        $personal         = $this->addressFor($doctor);
        $inClinic         = $this->clinicAddress($clinic);

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $doctor->getUser(), $this->schedulePayload($doctor, $personal->getId(), '08:00'));
        self::assertSame(201, $this->responseCode());

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, $this->schedulePayload($doctor, $inClinic->getId(), '16:00', $clinic));
        self::assertSame(201, $this->responseCode());

        $this->em->clear();
        $reloadedDoctor = $this->em->getRepository(Doctor::class)->find($doctor->getId());
        $schedules      = $this->em->getRepository(WeeklySchedule::class)->findAllByDoctor($reloadedDoctor);

        self::assertCount(2, $schedules, 'یک برنامه به ازای هر محیط');

        $byContext = [];
        foreach ($schedules as $schedule) {
            $byContext[$schedule->getClinic() === null ? 'personal' : 'clinic'] = $schedule->getSetting()[0]['sessions'][0]['start'];
        }

        self::assertSame('08:00', $byContext['personal']);
        self::assertSame('16:00', $byContext['clinic']);
    }

    public function testDoctorKeepsFullAccessToOwnSchedule(): void
    {
        $doctor  = $this->makeDoctor('دکتر مستقل');
        $address = $this->addressFor($doctor);

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $doctor->getUser(), $this->schedulePayload($doctor, $address->getId(), '09:00'));
        self::assertSame(201, $this->responseCode());

        $this->authJson('GET', "/api/v1/appointment-settings/weekly-schedule/{$doctor->getUuid()}", $doctor->getUser());
        self::assertSame(200, $this->responseCode());
    }

    public function testDoctorCannotTouchAnotherDoctorSchedule(): void
    {
        $mine    = $this->makeDoctor('دکتر یک');
        $theirs  = $this->makeDoctor('دکتر دو');
        $address = $this->addressFor($theirs);

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $mine->getUser(), $this->schedulePayload($theirs, $address->getId(), '09:00'));

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

    public function testAdminCanWriteAnyDoctorSchedule(): void
    {
        $doctor  = $this->makeDoctor('دکتر هدف');
        $admin   = $this->createUser(['ROLE_USER', 'ROLE_ADMIN']);
        $address = $this->addressFor($doctor);

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $admin, $this->schedulePayload($doctor, $address->getId(), '09:00'));

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

    public function testMemberDoctorLosesAccessWhenPermissionRevoked(): void
    {
        $doctor     = $this->makeDoctor('دکتر عضو');
        $other      = $this->makeDoctor('دکتر دیگر');
        [, $clinic] = $this->makeClinicWith($doctor, $other);
        $address    = $this->clinicAddress($clinic);

        $perm = static::getContainer()->get(ClinicDoctorPermissionRepository::class)->getOrCreate($clinic, $doctor);
        $perm->mergePermissions(['resources' => ['appointment_settings' => ['update' => false, 'view' => false]]]);
        $this->em->flush();

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $doctor->getUser(), $this->schedulePayload($other, $address->getId(), '09:00', $clinic));

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

    public function testEditingOneDoctorDoesNotAffectAnother(): void
    {
        $first            = $this->makeDoctor('دکتر اول');
        $second           = $this->makeDoctor('دکتر دوم');
        [$owner, $clinic] = $this->makeClinicWith($first, $second);
        $address          = $this->clinicAddress($clinic);

        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, $this->schedulePayload($first, $address->getId(), '08:00', $clinic));
        $this->authJson('POST', '/api/v1/appointment-settings/weekly-schedule', $owner, $this->schedulePayload($second, $address->getId(), '16:00', $clinic));

        $this->authJson('PATCH', "/api/v1/appointment-settings/weekly-schedule/{$first->getUuid()}", $owner, [
            'clinic_uuid' => $clinic->getUuid(),
            'schedule'    => [
                ['day' => 'saturday', 'sessions' => [
                    ['active' => true, 'location_id' => $address->getId(), 'start' => '11:00', 'end' => '15:00'],
                ]],
            ],
        ]);
        self::assertSame(200, $this->responseCode());

        $this->em->clear();
        $reloaded = $this->em->getRepository(WeeklySchedule::class)->findByDoctorAndClinic(
            $this->em->getRepository(Doctor::class)->find($second->getId()),
            $this->em->getRepository(Clinic::class)->find($clinic->getId()),
        );

        self::assertSame('16:00', $reloaded->getSetting()[0]['sessions'][0]['start'], "the other doctor's schedule is untouched");
    }
}
