<?php

namespace App\Appointment\Service;

use App\Appointment\Entity\Appointment;
use App\Appointment\Booking\Repository\AppointmentHoldRepository;
use App\Appointment\Booking\Service\BookingService;
use App\Appointment\Repository\AppointmentRepository;
use App\Payment\Entity\Payment;
use App\Payment\Repository\PaymentRepository;

class AppointmentExpiryService
{
    public function __construct(
        private readonly AppointmentRepository $appointmentRepo,
        private readonly PaymentRepository $paymentRepo,
        private readonly AppointmentHoldRepository $holdRepo,
        private readonly BookingService $booking,
    ) {}

    /**
     * Expire pending bookings whose payment window has lapsed or whose slot
     * time has already passed. Idempotent — safe to run repeatedly.
     *
     * @return int number of appointments expired
     */
    /**
     * رزروهای موقتی که مهلتشان گذشته و ثبت نهایی نشده‌اند.
     *
     * ردیف اشغال `released` می‌شود (تاریخچه می‌ماند) ولی سطل‌های یکتایی حذف می‌شوند،
     * وگرنه کلید یکتا آن بازه را برای همیشه نگه می‌دارد.
     */
    private function expireHolds(int $now): int
    {
        $holds = $this->holdRepo->findExpired($now);
        $count = 0;

        foreach ($holds as $hold) {
            $this->booking->releaseHold($hold);
            $count++;
        }

        return $count;
    }

    public function expireStale(): int
    {
        $now = time();

        $expired = [];
        foreach ([...$this->appointmentRepo->findPaymentExpired($now), ...$this->appointmentRepo->findExpiredPending($now)] as $appointment) {
            $expired[$appointment->getUuid()] = $appointment;
        }

        // Fetch all pending payments for the expiring appointments in one query
        // instead of one lookup per appointment (was N+1 in this scheduler loop).
        $pendingByAppointment = $this->paymentRepo->findPendingByAppointments(array_values($expired));

        $count = 0;
        foreach ($expired as $appointment) {
            $appointment->transitionTo(Appointment::STATUS_EXPIRED);
            $this->appointmentRepo->save($appointment, false);

            $payment = $pendingByAppointment[$appointment->getId()] ?? null;
            if ($payment !== null) {
                $payment->setStatus(Payment::STATUS_CANCELED);
                $this->paymentRepo->save($payment, false);
            }

            $count++;
        }

        // رزروهای موقتِ منقضی هم همین‌جا آزاد می‌شوند: بدونش، صندلیِ گرفته‌شده تا ابد
        // قفل می‌ماند و آن زمان هرگز دوباره پیشنهاد نمی‌شود.
        $count += $this->expireHolds($now);

        // شرط روی `$expired` است نه `$count`: از وقتی رزروهای موقت هم شمرده می‌شوند،
        // `$count` می‌تواند مثبت باشد در حالی که هیچ نوبتی منقضی نشده — و آن‌وقت
        // `reset([])` مقدار `false` به save می‌داد. (آزادسازی رزروها خودش flush دارد.)
        if ($expired !== []) {
            $this->appointmentRepo->save(reset($expired)); // flush once
        }

        return $count;
    }
}
