<?php

namespace App\Tests\Appointment;

use App\Appointment\Entity\Appointment;
use App\Appointment\Entity\WeeklySchedule;
use App\Doctor\Entity\Doctor;
use App\Tests\ApiTestCase;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;

/**
 * `app:appointment:backfill-service-duration` — نوبت‌های آیندهٔ محیط‌های سرویسی که
 * پیش از افزودن ستون‌های مدت ثبت شده‌اند.
 *
 * مقدار از **خودِ نوبت** می‌آید نه از بازمحاسبهٔ سرویس‌ها: نوبت موجود ممکن است با مدت
 * دستی ثبت شده باشد و بازمحاسبه یعنی تغییرِ گذشته.
 */
class BackfillServiceDurationTest extends ApiTestCase
{
    private function tester(): CommandTester
    {
        $application = new Application(static::$kernel);

        return new CommandTester($application->find('app:appointment:backfill-service-duration'));
    }

    private function doctorInMode(string $mode, int $buffer = 0): Doctor
    {
        $owner  = $this->createUser(['ROLE_DOCTOR']);
        $doctor = new Doctor($owner, 'دکتر backfill');
        $this->em->persist($doctor);
        $this->em->flush();

        $schedule = $this->newWeeklySchedule($doctor, [
            '0' => ['sessions' => [[
                'active' => true, 'start_time' => '09:00', 'end_time' => '18:00',
                'duration_per_patient' => 20, 'location_id' => 1,
            ]]],
        ]);
        $schedule->setMeta(['booking_mode' => $mode, 'buffer_minutes' => $buffer]);
        $this->em->persist($schedule);
        $this->em->flush();

        return $doctor;
    }

    private function appointment(
        Doctor $doctor,
        string $when,
        int $minutes,
        string $status = Appointment::STATUS_CONFIRMED,
        bool $reserve = false,
    ): Appointment {
        $patient = $this->createUser(['ROLE_USER']);
        $start   = (int) strtotime($when);
        $appt    = $this->newAppointment($doctor, $patient, $start, $start + $minutes * 60);
        if ($status !== Appointment::STATUS_PENDING) {
            $appt->transitionTo($status);
        }
        if ($reserve) {
            $appt->rescheduleTo($start, $start, true);
        }
        $this->em->persist($appt);
        $this->em->flush();

        return $appt;
    }

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

    public function testForceFillsDurationFromTheAppointmentItself(): void
    {
        $doctor = $this->doctorInMode(WeeklySchedule::MODE_SERVICE, buffer: 10);
        // مدت دستیِ ۵۰ دقیقه — نباید با مدت سرویس‌ها بازمحاسبه شود.
        $appt = $this->appointment($doctor, '+6 days 11:00', 50);

        $this->tester()->execute(['--force' => true]);
        $this->em->refresh($appt);

        self::assertSame(50, $appt->getServiceTotalMinutes(), 'مدت از slot_end - slot_start');
        self::assertSame(10, $appt->getServiceBufferMinutes(), 'بافر از meta برنامه');
    }

    public function testDryRunWritesNothing(): void
    {
        $doctor = $this->doctorInMode(WeeklySchedule::MODE_SERVICE, buffer: 5);
        $appt   = $this->appointment($doctor, '+6 days 12:00', 30);

        $tester = $this->tester();
        $tester->execute([]);
        $this->em->refresh($appt);

        self::assertNull($appt->getServiceTotalMinutes(), 'dry-run پیش‌فرض است');
        self::assertStringContainsString('--force', $tester->getDisplay());
    }

    public function testRunningTwiceChangesNothingTheSecondTime(): void
    {
        $doctor = $this->doctorInMode(WeeklySchedule::MODE_SERVICE, buffer: 5);
        $appt   = $this->appointment($doctor, '+6 days 13:00', 40);

        $first = $this->tester();
        $first->execute(['--force' => true]);
        $this->em->refresh($appt);
        self::assertStringContainsString($appt->getUuid(), $first->getDisplay(), 'بار اول باید کاندید باشد');

        $second = $this->tester();
        $second->execute(['--force' => true]);
        $this->em->refresh($appt);

        self::assertSame(40, $appt->getServiceTotalMinutes(), 'مقدار عوض نشد');
        // کوئری با `serviceTotalMinutes IS NULL` فیلتر می‌کند، پس ردیفِ پرشده دیگر
        // کاندید نیست و در خروجی اجرای دوم نمی‌آید.
        self::assertStringNotContainsString($appt->getUuid(), $second->getDisplay(), 'idempotent');
    }

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

    public function testSlotModeAppointmentIsSkipped(): void
    {
        $doctor = $this->doctorInMode(WeeklySchedule::MODE_SLOT);
        $appt   = $this->appointment($doctor, '+6 days 14:00', 20);

        $this->tester()->execute(['--force' => true]);
        $this->em->refresh($appt);

        self::assertNull($appt->getServiceTotalMinutes(), 'حالت اسلاتی نباید ستون سرویسی بگیرد');
    }

    public function testPastAppointmentIsSkipped(): void
    {
        $doctor = $this->doctorInMode(WeeklySchedule::MODE_SERVICE);
        $appt   = $this->appointment($doctor, '-3 days 10:00', 30);

        $this->tester()->execute(['--force' => true]);
        $this->em->refresh($appt);

        self::assertNull($appt->getServiceTotalMinutes(), 'گذشته بازنویسی نمی‌شود');
    }

    public function testReserveEntryIsSkipped(): void
    {
        $doctor = $this->doctorInMode(WeeklySchedule::MODE_SERVICE);
        $appt   = $this->appointment($doctor, '+6 days 00:00', 0, reserve: true);

        $this->tester()->execute(['--force' => true]);
        $this->em->refresh($appt);

        self::assertNull($appt->getServiceTotalMinutes(), 'رزرو مدتی برای استنتاج ندارد');
    }

    public function testCancelledAppointmentIsSkipped(): void
    {
        $doctor = $this->doctorInMode(WeeklySchedule::MODE_SERVICE);
        $appt   = $this->appointment($doctor, '+6 days 15:00', 30, Appointment::STATUS_CANCELLED_BY_DOCTOR);

        $this->tester()->execute(['--force' => true]);
        $this->em->refresh($appt);

        self::assertNull($appt->getServiceTotalMinutes());
    }
}
