<?php

namespace App\Appointment\Service;

use App\Appointment\Entity\Appointment;
use App\Appointment\Repository\AppointmentRepository;
use App\Auth\Entity\User;
use App\Patient\Entity\PatientSession;
use App\Patient\Service\PatientService;
use App\Payment\Repository\PaymentRepository;
use App\Representation\Service\DomainContextResolver;
use App\Settlement\Service\CommissionService;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Exception\AppException;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;

/**
 * عوارض جانبیِ قطعی‌شدن نوبت، در یک نقطه.
 *
 * قطعی‌شدن پنج مسیر دارد (پرداخت آنلاین، دو مسیر PATCH، رزرو پنل، رزرو ادمین) و
 * تا امروز فقط دو تای آن‌ها پرونده می‌ساختند — نوبت‌های سایت عمومی که با پرداخت
 * قطعی می‌شوند هیچ‌وقت پرونده نداشتند. هر مسیر جدیدی هم باید همین را صدا بزند.
 */
class AppointmentConfirmationService
{
    public function __construct(
        private readonly PatientService        $patientService,
        private readonly AppointmentRepository $appointmentRepo,
        private readonly PaymentRepository     $paymentRepo,
        private readonly CommissionService     $commissionService,
        private readonly DomainContextResolver $domainResolver,
        private readonly EntityManagerInterface $em,
        private readonly LoggerInterface       $logger,
    ) {}

    /**
     * تقسیم مالیِ نوبت آنلاین در لحظهٔ **تأیید** انجام می‌شود، نه لحظهٔ پرداخت: تا وقتی
     * نوبت قطعی نشده، پورسانت نماینده و سهم منشی هم اعتبار نمی‌شوند. برای نوبتی که
     * پرداخت آنلاین ندارد (ثبت‌شده در پنل) کاری انجام نمی‌شود. ثبت idempotent است.
     */
    private function splitPaymentShares(Appointment $appointment): void
    {
        $payment = $this->paymentRepo->findSuccessfulByAppointment($appointment);
        if ($payment === null) {
            return;
        }

        $doctor = $appointment->getDoctor();
        $this->commissionService->processAppointment(
            $payment,
            $doctor->getRepresentationId(),
            $this->domainResolver->resolve($payment->getFrontendAddress())->representationId(),
            $doctor->getId(),
        );
    }

    /**
     * idempotent: فراخوانی دوباره برای همان نوبت چیزی نمی‌سازد.
     *
     * شکست ساخت پرونده نباید قطعی‌شدن نوبت یا تأیید پرداخت را برگرداند — نوبت
     * رزرو شده و پول پرداخت شده است؛ پرونده را می‌شود با
     * `app:appointment:backfill-sessions` ساخت، ولی رول‌بکِ پرداخت برگشت‌ناپذیر است.
     */
    public function onConfirmed(Appointment $appointment): ?PatientSession
    {
        // نوبت رزروِ روز-محور اسلات و ساعت مشخص ندارد؛ مراجعهٔ زمان‌دار برایش معنا ندارد.
        if ($appointment->isReserve()) {
            return null;
        }

        $this->splitPaymentShares($appointment);

        try {
            return $this->patientService->autoCreateOnAppointmentConfirm($appointment);
        } catch (\Throwable $e) {
            $this->logger->error('Auto-creating the patient record on confirm failed', [
                'appointment_uuid' => $appointment->getUuid(),
                'exception'        => $e,
            ]);

            return null;
        }
    }

    /**
     * قطعی‌کردنِ صریح از پنل: انتقال وضعیت، ثبت پرونده/مراجعه و ثبت پرداخت‌ها — همه
     * در یک تراکنش. برخلاف onConfirmed اینجا شکست خاموش نمی‌ماند: کاربر روبه‌روی
     * مودالی ایستاده که مبلغ نشان داده و منتظر تأیید است؛ «قطعی شد ولی پول ثبت نشد»
     * بدترین خروجیِ ممکن است.
     *
     * @param  array<int, array{method: string, amount_rials: int, payment_method_uuid?: ?string, reference?: ?string}> $payments
     * @return PatientSession|null null یعنی این tenant قابلیت پرونده را ندارد
     *                             (فقط وقتی مجاز است که پرداختی هم ارسال نشده باشد)
     */
    public function confirmWithPayments(
        Appointment $appointment,
        int $expectedVersion,
        array $payments,
        User $actor,
    ): ?PatientSession {
        return $this->em->wrapInTransaction(function () use ($appointment, $expectedVersion, $payments, $actor) {
            $appointment->transitionTo(Appointment::STATUS_CONFIRMED);
            $this->appointmentRepo->saveWithLock($appointment, $expectedVersion);

            $this->splitPaymentShares($appointment);

            $session = $this->patientService->autoCreateOnAppointmentConfirm($appointment);

            if ($session === null) {
                if ($payments !== []) {
                    throw new AppException(ErrorCodes::ERR_SUBSCRIPTION_REQUIRED, null, 403);
                }

                return null;
            }

            foreach ($payments as $payment) {
                $this->patientService->addSessionPayment(
                    $session,
                    $payment['method'],
                    $payment['amount_rials'],
                    null,
                    $actor,
                    $payment['payment_method_uuid'] ?? null,
                    $payment['reference'] ?? null,
                );
            }

            return $session;
        });
    }
}
