<?php

namespace App\Tests\Appointment;

use App\Appointment\Entity\Appointment;
use App\Appointment\Entity\WeeklySchedule;
use App\Appointment\Repository\AppointmentRepository;
use App\Appointment\Service\SlotCalculatorService;
use App\Doctor\Entity\Doctor;
use App\Tests\ApiTestCase;

/**
 * جابه‌جایی خودِ یک نوبت: بازهٔ همان نوبت نباید اشغال حساب شود، وگرنه زمان فعلی‌اش
 * هرگز در فهرست زمان‌های ممکن نمی‌آید و کاربر نمی‌تواند «همان ساعت، سرویس متفاوت» را
 * ثبت کند.
 */
class ServiceSlotExcludeSelfTest extends ApiTestCase
{
    /** @return array{0:Doctor,1:string,2:int} */
    private function serviceDoctor(): array
    {
        $owner  = $this->createUser(['ROLE_DOCTOR']);
        $doctor = new Doctor($owner, 'دکتر جابه‌جایی');
        $this->em->persist($doctor);

        $date   = date('Y-m-d', strtotime('+2 days'));
        $dayKey = (string) (((int) date('w', strtotime($date)) + 1) % 7);

        $schedule = $this->newWeeklySchedule($doctor, [
            $dayKey => ['sessions' => [[
                'active' => true, 'start_time' => '15:00', 'end_time' => '17:00',
                'duration_per_patient' => 20, 'location_id' => 1,
            ]]],
        ]);
        $schedule->setMeta(['booking_mode' => WeeklySchedule::MODE_SERVICE, 'buffer_minutes' => 0]);
        $this->em->persist($schedule);
        $this->em->flush();

        return [$doctor, $date, (int) strtotime($date . ' 15:00')];
    }

    private function confirmedAppointment(Doctor $doctor, int $start, int $minutes): Appointment
    {
        $patient = $this->createUser(['ROLE_USER']);
        $appt    = $this->newAppointment($doctor, $patient, $start, $start + $minutes * 60);
        $appt->transitionTo(Appointment::STATUS_CONFIRMED);
        $this->em->persist($appt);
        $this->em->flush();

        return $appt;
    }

    private function calc(): SlotCalculatorService
    {
        return static::getContainer()->get(SlotCalculatorService::class);
    }

    // ── ✅ موفق ──────────────────────────────────────────────────────────────

    public function testOwnIntervalReappearsWhenExcluded(): void
    {
        [$doctor, $date, $start] = $this->serviceDoctor();
        $appt = $this->confirmedAppointment($doctor, $start, 30);

        $without = array_column($this->calc()->getServiceStartTimes($doctor, $date, 30), 'start_time');
        self::assertNotContains('15:00', $without, 'بدون exclude، نوبت خودش را اشغال می‌بیند');

        $with = array_column(
            $this->calc()->getServiceStartTimes($doctor, $date, 30, null, false, $appt->getId()),
            'start_time',
        );
        self::assertContains('15:00', $with, 'با exclude، زمان فعلیِ خود نوبت در فهرست می‌آید');
    }

    public function testExcludingAllowsALongerDurationAtTheSameStart(): void
    {
        [$doctor, $date, $start] = $this->serviceDoctor();
        $appt = $this->confirmedAppointment($doctor, $start, 30);

        // همان ساعت ۱۵:۰۰ ولی سرویس بلندتر (۶۰ دقیقه) — سناریوی «سرویس را عوض کردم».
        $times = array_column(
            $this->calc()->getServiceStartTimes($doctor, $date, 60, null, false, $appt->getId()),
            'start_time',
        );

        self::assertContains('15:00', $times);
    }

    // ── ❌ خطا / مسیر نادرست ─────────────────────────────────────────────────

    public function testOtherAppointmentsStayBlockingWhenOneIsExcluded(): void
    {
        [$doctor, $date, $start] = $this->serviceDoctor();
        $mine   = $this->confirmedAppointment($doctor, $start, 30);
        $others = $this->confirmedAppointment($doctor, $start + 30 * 60, 30);   // ۱۵:۳۰

        $times = array_column(
            $this->calc()->getServiceStartTimes($doctor, $date, 30, null, false, $mine->getId()),
            'start_time',
        );

        self::assertContains('15:00', $times, 'نوبت خودم آزاد شد');
        self::assertNotContains('15:30', $times, 'نوبت دیگری همچنان اشغال است');
        self::assertNotNull($others->getId());
    }

    // ── ⚠️ مرزی ──────────────────────────────────────────────────────────────

    public function testNullExcludeIdBehavesExactlyLikeBefore(): void
    {
        [$doctor, $date, $start] = $this->serviceDoctor();
        $this->confirmedAppointment($doctor, $start, 30);

        $implicit = $this->calc()->getServiceStartTimes($doctor, $date, 30);
        $explicit = $this->calc()->getServiceStartTimes($doctor, $date, 30, null, false, null);

        self::assertSame($implicit, $explicit, 'پیش‌فرض null نباید رفتار موجود را عوض کند');
    }

    public function testRepositoryExcludeIdFiltersOnlyThatRow(): void
    {
        [$doctor, , $start] = $this->serviceDoctor();
        $appt = $this->confirmedAppointment($doctor, $start, 30);

        $repo = static::getContainer()->get(AppointmentRepository::class);
        $from = $start - 3600;
        $to   = $start + 7200;

        self::assertNotEmpty($repo->findBusyIntervals($doctor, $from, $to));
        self::assertSame([], $repo->findBusyIntervals($doctor, $from, $to, $appt->getId()));
    }

    public function testExcludingAReserveEntryChangesNothing(): void
    {
        [$doctor, $date, $start] = $this->serviceDoctor();

        // نوبت رزرو هیچ بازه‌ای اشغال نمی‌کند، پس exclude کردنش هم بی‌اثر است.
        $patient = $this->createUser(['ROLE_USER']);
        $dayMid  = (int) strtotime($date . ' 00:00:00');
        $reserve = $this->newAppointment($doctor, $patient, $dayMid, $dayMid);
        $reserve->rescheduleTo($dayMid, $dayMid, true);
        $this->em->persist($reserve);
        $this->em->flush();

        $before = $this->calc()->getServiceStartTimes($doctor, $date, 30);
        $after  = $this->calc()->getServiceStartTimes($doctor, $date, 30, null, false, $reserve->getId());

        self::assertSame($before, $after);
        self::assertSame($start, (int) strtotime($date . ' 15:00'));
    }
}
