<?php

namespace App\Tests\Appointment;

use App\Appointment\Entity\Appointment;
use App\Appointment\Entity\WeeklySchedule;
use App\Clinic\Entity\Clinic;
use App\Doctor\Entity\Doctor;
use App\Shared\Context\EntityContext;
use App\Tests\ApiTestCase;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;

/**
 * Phase 2 of tenant marking: appointments, weekly schedules and date overrides
 * carry their owning environment as the (entity_type, entity_id) pair rather
 * than leaving it implicit in a nullable clinic_id.
 */
class BookingTenantTest extends ApiTestCase
{
    private function makeDoctor(): Doctor
    {
        $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر تست محیط');
        $this->em->persist($doctor);
        $this->em->flush();

        return $doctor;
    }

    private function makeClinic(): Clinic
    {
        $clinic = new Clinic($this->createUser(['ROLE_CLINIC']));
        $clinic->setName('کلینیک تست محیط');
        $this->em->persist($clinic);
        $this->em->flush();

        return $clinic;
    }

    private function uniqueSlot(): int
    {
        return strtotime('+90 days') + random_int(0, 500_000) * 7;
    }

    public function testPersonalBookingIsOwnedByTheDoctor(): void
    {
        $doctor = $this->makeDoctor();
        $start  = $this->uniqueSlot();

        $appointment = $this->newAppointment($doctor, $this->createUser(), $start, $start + 900);
        $this->em->persist($appointment);
        $this->em->flush();

        self::assertSame('doctor', $appointment->getEntityType());
        self::assertSame($doctor->getId(), $appointment->getEntityId());
    }

    public function testClinicBookingIsOwnedByTheClinic(): void
    {
        $doctor = $this->makeDoctor();
        $clinic = $this->makeClinic();
        $clinic->getDoctors()->add($doctor);
        $this->em->flush();

        $start       = $this->uniqueSlot();
        $appointment = $this->newAppointment($doctor, $this->createUser(), $start, $start + 900, $clinic);
        $this->em->persist($appointment);
        $this->em->flush();

        self::assertSame('clinic', $appointment->getEntityType());
        self::assertSame($clinic->getId(), $appointment->getEntityId());
    }

    /** یک پزشک در دو محیط = دو مالک متفاوت برای دو نوبت. */
    public function testSameDoctorGetsDifferentOwnersInDifferentEnvironments(): void
    {
        $doctor = $this->makeDoctor();
        $clinic = $this->makeClinic();
        $clinic->getDoctors()->add($doctor);
        $this->em->flush();

        $personal = $this->newAppointment($doctor, $this->createUser(), $this->uniqueSlot(), $this->uniqueSlot() + 900);
        $inClinic = $this->newAppointment($doctor, $this->createUser(), $this->uniqueSlot(), $this->uniqueSlot() + 900, $clinic);
        $this->em->persist($personal);
        $this->em->persist($inClinic);
        $this->em->flush();

        self::assertSame(['doctor', $doctor->getId()], [$personal->getEntityType(), $personal->getEntityId()]);
        self::assertSame(['clinic', $clinic->getId()], [$inClinic->getEntityType(), $inClinic->getEntityId()]);
    }

    /** ❌ محیط حل‌نشده هرگز ذخیره نمی‌شود — نه با شناسهٔ صفر، نه بی‌صدا. */
    public function testAssigningAnUnresolvedContextIsRejected(): void
    {
        $doctor      = $this->makeDoctor();
        $start       = $this->uniqueSlot();
        $appointment = new Appointment($doctor, $this->createUser(), $start, $start + 900);

        $this->expectException(\InvalidArgumentException::class);
        $appointment->assignTenant(EntityContext::unknown());
    }

    /**
     * ⚠️ حالت مرزی: دو پزشکِ متفاوت در یک کلینیک، هر کدام برنامهٔ هفتگی خودشان —
     * کلید یکتا باید doctor_id را هم داشته باشد وگرنه پزشک دوم رد می‌شود.
     */
    public function testTwoDoctorsInOneClinicEachKeepTheirOwnSchedule(): void
    {
        $clinic  = $this->makeClinic();
        $doctorA = $this->makeDoctor();
        $doctorB = $this->makeDoctor();
        $clinic->getDoctors()->add($doctorA);
        $clinic->getDoctors()->add($doctorB);
        $this->em->flush();

        $this->em->persist($this->newWeeklySchedule($doctorA, [], $clinic));
        $this->em->persist($this->newWeeklySchedule($doctorB, [], $clinic));
        $this->em->flush();

        $rows = $this->em->getRepository(WeeklySchedule::class)
            ->findBy(['entityType' => 'clinic', 'entityId' => $clinic->getId()]);

        self::assertCount(2, $rows);
    }

    /** همان پزشک، همان محیط، دو برنامه — هنوز غیرممکن است. */
    public function testOneSchedulePerEnvironmentPerDoctorIsStillEnforced(): void
    {
        $doctor = $this->makeDoctor();

        $this->em->persist($this->newWeeklySchedule($doctor, []));
        $this->em->flush();

        $this->em->persist($this->newWeeklySchedule($doctor, []));
        $this->expectException(UniqueConstraintViolationException::class);
        $this->em->flush();
    }
}
