<?php

namespace App\Tests\Doctor;

use App\Clinic\Entity\Clinic;
use App\Doctor\Entity\Doctor;
use App\Doctor\Entity\DoctorAddress;
use App\Doctor\Repository\DoctorRepository;
use App\Location\Entity\City;
use App\Location\Entity\Province;
use App\Tests\ApiTestCase;

/**
 * The public doctor list must expose each doctor's city/state so multi-domain
 * consumers (nobat724_front sitemap) can tell which city domain owns a doctor.
 *
 * Location resolution mirrors the city_id/state_id filter in
 * DoctorRepository::findWithFilters — own address first, clinic address as
 * fallback — otherwise a doctor could match the filter but report no city.
 */
class DoctorListLocationTest extends ApiTestCase
{
    private function makeCity(string $cityName, string $provinceName): City
    {
        $province = new Province($provinceName);
        $this->em->persist($province);
        $city = new City($cityName, $province);
        $this->em->persist($city);

        return $city;
    }

    private function makeDoctor(string $name): Doctor
    {
        $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), $name);
        $this->em->persist($doctor);

        return $doctor;
    }

    private function giveOwnAddress(Doctor $doctor, City $city): void
    {
        $address = DoctorAddress::forDoctor($doctor);
        $address->setCity($city)->setProvince($city->getProvince());
        $this->em->persist($address);
    }

    /** @return array<int, array> map of doctor name => list payload */
    private function fetchListByName(string $name): array
    {
        $this->client->request('GET', '/api/v1/doctors?limit=50&name=' . urlencode($name));
        $this->assertSame(200, $this->responseCode());
        $payload = json_decode($this->client->getResponse()->getContent(), true);

        $byName = [];
        foreach ($payload['data'] as $row) {
            $byName[$row['name']] = $row;
        }

        return $byName;
    }

    public function testDoctorWithOwnAddressReportsItsCity(): void
    {
        $city   = $this->makeCity('یاسوج', 'کهگیلویه و بویراحمد');
        $marker = 'loc-own-' . bin2hex(random_bytes(4));
        $doctor = $this->makeDoctor($marker);
        $this->giveOwnAddress($doctor, $city);
        $this->em->flush();

        $row = $this->fetchListByName($marker)[$marker] ?? null;
        $this->assertNotNull($row, 'doctor missing from list');

        $this->assertCount(1, $row['city']);
        $this->assertSame('یاسوج', $row['city'][0]['name']);
        $this->assertSame((string) $city->getId(), $row['city'][0]['id']);
        $this->assertSame((string) $city->getProvince()->getId(), $row['city'][0]['parent']);

        $this->assertCount(1, $row['state']);
        $this->assertSame('کهگیلویه و بویراحمد', $row['state'][0]['name']);
    }

    public function testDoctorWithoutOwnAddressFallsBackToClinicCity(): void
    {
        $city   = $this->makeCity('تبریز', 'آذربایجان شرقی');
        $marker = 'loc-clinic-' . bin2hex(random_bytes(4));
        $doctor = $this->makeDoctor($marker);

        $clinic = new Clinic($this->createUser(['ROLE_CLINIC']));
        $this->em->persist($clinic);
        $clinic->getDoctors()->add($doctor);
        $this->em->flush();

        // آدرس کلینیک ردیفی از DoctorAddress با doctor NULL و clinicId پرشده است.
        $clinicAddress = DoctorAddress::forClinic($clinic->getId());
        $clinicAddress->setCity($city)->setProvince($city->getProvince());
        $this->em->persist($clinicAddress);
        $this->em->flush();

        $row = $this->fetchListByName($marker)[$marker] ?? null;
        $this->assertNotNull($row, 'doctor missing from list');

        $this->assertCount(1, $row['city'], 'clinic-located doctor must still report a city');
        $this->assertSame('تبریز', $row['city'][0]['name']);
    }

    public function testDoctorWithNoLocationReportsEmptyArrays(): void
    {
        $marker = 'loc-none-' . bin2hex(random_bytes(4));
        $this->makeDoctor($marker);
        $this->em->flush();

        $row = $this->fetchListByName($marker)[$marker] ?? null;
        $this->assertNotNull($row, 'doctor missing from list');

        $this->assertSame([], $row['city']);
        $this->assertSame([], $row['state']);
    }

    public function testReportedCityMatchesCityIdFilter(): void
    {
        $city   = $this->makeCity('یزد', 'یزد');
        $marker = 'loc-filter-' . bin2hex(random_bytes(4));
        $doctor = $this->makeDoctor($marker);
        $this->giveOwnAddress($doctor, $city);
        $this->em->flush();

        $this->client->request('GET', '/api/v1/doctors?limit=50&city_id=' . $city->getId());
        $this->assertSame(200, $this->responseCode());
        $payload = json_decode($this->client->getResponse()->getContent(), true);

        $this->assertNotEmpty($payload['data']);
        foreach ($payload['data'] as $row) {
            $this->assertNotEmpty(
                $row['city'],
                "doctor {$row['name']} matched city_id filter but reports no city"
            );
            $this->assertSame((string) $city->getId(), $row['city'][0]['id']);
        }
    }

    /**
     * سهمِ خودِ حل‌مکان اندازه‌گیری می‌شود، نه کل اندپوینت: پاسخ لیست lazy-loadهای
     * قدیمی (specialties) هم دارد که با تعداد پزشک رشد می‌کنند و ربطی به این تغییر
     * ندارند. پس مستقیم ریپازیتوری تست می‌شود — باید حداکثر ۲ کوئری باشد، ثابت.
     */
    public function testLocationResolutionCostIsConstant(): void
    {
        $city = $this->makeCity('اصفهان', 'اصفهان');
        $repo = static::getContainer()->get(DoctorRepository::class);

        $makeDoctors = function (int $count) use ($city): array {
            $doctors = [];
            for ($i = 0; $i < $count; $i++) {
                $doctor = $this->makeDoctor('loc-cost-' . bin2hex(random_bytes(4)));
                $this->giveOwnAddress($doctor, $city);
                $doctors[] = $doctor;
            }
            $this->em->flush();

            return $doctors;
        };

        $few  = $makeDoctors(2);
        $many = $makeDoctors(12);

        $qFew = $this->countQueries(fn () => $repo->findLocationsByDoctors($few));
        $qMany = $this->countQueries(fn () => $repo->findLocationsByDoctors($many));

        $this->assertSame($qFew, $qMany, "location resolution scales with doctor count: $qFew -> $qMany");
        $this->assertLessThanOrEqual(2, $qMany, 'location resolution must cost at most 2 queries');

        // و همچنان درست کار کند
        $resolved = $repo->findLocationsByDoctors($many);
        $this->assertCount(12, $resolved);
        foreach ($many as $doctor) {
            $this->assertSame('اصفهان', $resolved[$doctor->getId()]['city']['name']);
        }
    }

    public function testPaginationMetaExposesAppliedLimit(): void
    {
        $this->client->request('GET', '/api/v1/doctors?limit=500');
        $this->assertSame(200, $this->responseCode());
        $payload = json_decode($this->client->getResponse()->getContent(), true);

        // سقف ریپازیتوری ۵۰ است؛ meta باید مقدار واقعاً اعمال‌شده را بگوید نه ۵۰۰.
        $this->assertSame(50, $payload['meta']['limit']);
    }
}
