<?php

namespace App\Tests\Appointment;

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

/**
 * `next_available_at` scans ahead for the first free slot per location.
 *
 * The scan prefetches the schedule, holidays, overrides and taken appointments
 * once per location and resolves the rest in memory, so its cost must not grow
 * with the number of days walked or slots inspected — only with the number of
 * locations, by a small constant.
 */
class BookingLocationsScanTest extends ApiTestCase
{
    private function weekOfSessions(int $locationId, string $start, string $end): array
    {
        $day = ['sessions' => [[
            'active'               => true,
            'location_id'          => $locationId,
            'start_time'           => $start,
            'end_time'             => $end,
            'duration_per_patient' => 20,
        ]]];

        return array_fill_keys(array_map('strval', range(0, 6)), $day);
    }

    private function makeDoctorWithSchedules(int $clinicCount): Doctor
    {
        $doctorUser = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
        $doctor     = new Doctor($doctorUser, 'دکتر پرمحل');
        $doctor->setMobileNumber($doctorUser->getMobileNumber());
        $this->em->persist($doctor);
        $this->em->flush();

        $personalAddress = DoctorAddress::forDoctor($doctor);
        $this->em->persist($personalAddress);
        $this->em->flush();

        $this->em->persist($this->newWeeklySchedule(
            $doctor,
            $this->weekOfSessions($personalAddress->getId(), '09:00', '13:00')
        ));

        for ($i = 0; $i < $clinicCount; $i++) {
            $clinic = new Clinic($this->createUser(['ROLE_USER', 'ROLE_CLINIC']));
            $clinic->setName("کلینیک $i");
            $clinic->getDoctors()->add($doctor);
            $this->em->persist($clinic);
            $this->em->flush();

            $address = DoctorAddress::forClinic($clinic->getId());
            $this->em->persist($address);
            $this->em->flush();

            $this->em->persist($this->newWeeklySchedule(
                $doctor,
                $this->weekOfSessions($address->getId(), '16:00', '20:00'),
                $clinic
            ));
        }

        $this->em->flush();

        return $doctor;
    }

    public function testQueryCountGrowsOnlyPerLocation(): void
    {
        // Keep one kernel so the shared query logger stays consistent.
        $this->client->disableReboot();

        $few  = $this->makeDoctorWithSchedules(1);
        $many = $this->makeDoctorWithSchedules(5);

        $qFew = $this->countQueries(fn () => $this->client->request(
            'GET', '/api/v1/appointment-booking-locations/' . $few->getUuid()
        ));
        self::assertSame(200, $this->responseCode());

        $qMany = $this->countQueries(fn () => $this->client->request(
            'GET', '/api/v1/appointment-booking-locations/' . $many->getUuid()
        ));
        self::assertSame(200, $this->responseCode());

        // 2 locations -> 6 locations. Each extra one costs a fixed handful of
        // queries (its schedule, holidays, overrides, blocking intervals,
        // address). The regression this guards against is a per-day or per-slot
        // query, which on a schedule active every day would add hundreds.
        $extraLocations = 4;
        $budgetEach     = 8;
        self::assertLessThanOrEqual(
            $qFew + $extraLocations * $budgetEach,
            $qMany,
            "next_available_at scales badly: $qFew queries for 2 locations, $qMany for 6"
        );
    }

    public function testNextAvailableIsReportedPerLocation(): void
    {
        $doctor = $this->makeDoctorWithSchedules(1);

        $body = $this->authJson('GET', '/api/v1/appointment-booking-locations/' . $doctor->getUuid(), $doctor->getUser());
        self::assertSame(200, $this->responseCode());

        $locations = $body['data']['booking_locations'] ?? [];
        self::assertCount(2, $locations);

        foreach ($locations as $location) {
            self::assertNotNull(
                $location['next_available_at'],
                'a schedule active every day must expose a next free slot'
            );
            self::assertGreaterThanOrEqual(time(), $location['next_available_at']);
        }

        // Sorted by earliest opening — the site relies on booking_locations[0].
        $starts = array_column($locations, 'next_available_at');
        $sorted = $starts;
        sort($sorted);
        self::assertSame($sorted, $starts);
    }
}
