<?php

namespace App\Tests\Appointment;

use App\Appointment\Entity\Appointment;
use App\Appointment\Repository\SlotTakenException;
use App\Doctor\Entity\Doctor;
use App\Tests\ApiTestCase;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;

/**
 * A doctor's (doctor_id, slot_start) can be held by at most one live booking.
 * The unique active_slot_key is the race backstop behind bookAtomically:
 * even if two concurrent transactions both pass the isSlotTaken re-check, only
 * one INSERT can win. Cancelling/expiring a booking releases the slot.
 */
class SlotUniquenessTest extends ApiTestCase
{
    private function makeDoctor(): Doctor
    {
        $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر تست');
        $this->em->persist($doctor);
        $this->em->flush();

        return $doctor;
    }

    private function newBooking(Doctor $doctor, int $start): Appointment
    {
        return new Appointment($doctor, $this->createUser(), $start, $start + 1_800);
    }

    public function testTwoLiveBookingsOnSameSlotViolateUniqueKey(): void
    {
        $doctor = $this->makeDoctor();
        $start  = time() + 86_400;

        $this->em->persist($this->newBooking($doctor, $start));
        $this->em->flush();

        $this->em->persist($this->newBooking($doctor, $start));
        $this->expectException(UniqueConstraintViolationException::class);
        $this->em->flush();
    }

    public function testCancelledBookingFreesTheSlot(): void
    {
        $doctor = $this->makeDoctor();
        $start  = time() + 86_400;

        $first = $this->newBooking($doctor, $start);
        $this->em->persist($first);
        $this->em->flush();

        $first->transitionTo(Appointment::STATUS_CANCELLED_BY_USER);
        $this->em->flush();

        $second = $this->newBooking($doctor, $start);
        $this->em->persist($second);
        $this->em->flush();

        $this->assertNotNull($second->getId());
    }

    public function testBookAtomicallyRejectsAlreadyTakenSlot(): void
    {
        $doctor = $this->makeDoctor();
        $start  = time() + 86_400;
        $repo   = $this->em->getRepository(Appointment::class);

        $first = $this->newBooking($doctor, $start);
        $first->markPendingWithTtl(Appointment::PAYMENT_TTL);
        $repo->bookAtomically($first);

        $this->expectException(SlotTakenException::class);
        $repo->bookAtomically($this->newBooking($doctor, $start));
    }
}
