<?php

namespace App\Tests\Payment;

use App\Appointment\Entity\Appointment;
use App\Clinic\Entity\Clinic;
use App\Config\Entity\SiteConfig;
use App\Doctor\Entity\Doctor;
use App\Patient\Entity\PatientRecord;
use App\Patient\Entity\PatientSession;
use App\Payment\Entity\Payment;
use App\Tests\ApiTestCase;

/**
 * پرداخت آنلاین نوبت را **قطعی نمی‌کند**: نوبت در «ثبت شده» می‌ماند تا پزشک/منشی
 * تأییدش کند. پرداخت فقط پنجرهٔ انقضای درگاه را برمی‌دارد تا نوبتِ پرداخت‌شده منقضی
 * نشود. پرونده/مراجعه و تقسیم مالی در لحظهٔ تأیید ساخته می‌شوند.
 */
class AppointmentPaidConfirmFilesSessionTest extends ApiTestCase
{
    private function enableTestMode(): void
    {
        $cfg = $this->em->getRepository(SiteConfig::class)->findOneBy(['configKey' => 'payment_test_mode']);
        if ($cfg === null) {
            $this->em->persist(new SiteConfig('payment_test_mode', '1'));
        } else {
            $cfg->setValue('1');
        }
        $this->em->flush();
    }

    /** @return array{0: Payment, 1: Appointment} */
    private function pendingPaidBooking(?callable $withClinic = null): array
    {
        $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر پرداخت');
        $this->em->persist($doctor);
        $this->em->flush();

        $slotStart   = strtotime('+30 days') + random_int(0, 500_000) * 7;
        $appointment = $this->newAppointment($doctor, $this->createUser(['ROLE_USER']), $slotStart, $slotStart + 1_800);
        // رزرو آنلاین با پنجرهٔ پرداخت ثبت می‌شود؛ پرداخت باید همین را پاک کند.
        $appointment->markPendingWithTtl(Appointment::PAYMENT_TTL);
        if ($withClinic !== null) {
            $appointment->setClinic($withClinic($doctor));
        }
        $this->em->persist($appointment);

        $payment = new Payment($appointment->getUser(), 50_000, 'mock', Payment::TYPE_APPOINTMENT);
        $payment->setAppointment($appointment);
        $this->stampTenant($payment);
        $this->em->persist($payment);
        $this->em->flush();

        return [$payment, $appointment];
    }

    private function fireCallback(Payment $payment): void
    {
        $this->client->request('POST', '/api/v1/payment/callback/mock?' . http_build_query([
            'order_id'    => $payment->getOrderId(),
            'mock'        => '1',
            'ResCode'     => '0',
            'mock_amount' => '50000',
        ]));
    }

    private function reload(Appointment $appointment): Appointment
    {
        $this->em->clear();

        return $this->em->getRepository(Appointment::class)->find($appointment->getId());
    }

    public function testPaidBookingStaysPendingUntilSomeoneConfirmsIt(): void
    {
        $this->enableTestMode();
        [$payment, $appointment] = $this->pendingPaidBooking();

        $this->fireCallback($payment);
        $reloaded = $this->reload($appointment);

        self::assertSame(Appointment::STATUS_PENDING, $reloaded->getStatus());
        // پنجرهٔ پرداخت برداشته می‌شود، وگرنه cronِ انقضا نوبتِ پرداخت‌شده را می‌کشت.
        self::assertNull($reloaded->getExpiresAt());
        self::assertCount(0, $this->em->getRepository(PatientSession::class)->findBy(['appointment' => $reloaded]));
    }

    public function testConfirmingThePaidBookingFilesTheSession(): void
    {
        $this->enableTestMode();
        [$payment, $appointment] = $this->pendingPaidBooking();
        $this->fireCallback($payment);
        $reloaded = $this->reload($appointment);

        $doctorUser = $reloaded->getDoctor()->getUser();
        $this->authJson('POST', '/api/v1/appointment/' . $reloaded->getUuid() . '/confirm', $doctorUser, []);
        self::assertSame(200, $this->responseCode());

        $confirmed = $this->reload($reloaded);
        self::assertSame(Appointment::STATUS_CONFIRMED, $confirmed->getStatus());

        $sessions = $this->em->getRepository(PatientSession::class)->findBy(['appointment' => $confirmed]);
        self::assertCount(1, $sessions, 'تأیید نوبت باید پرونده بسازد');
        self::assertSame('doctor', $sessions[0]->getRecord()->getEntityType());
    }

    /** تقسیم مالی هم مثل پرونده به لحظهٔ تأیید منتقل شده است. */
    public function testFinancialSplitHappensOnConfirmNotOnPayment(): void
    {
        $this->enableTestMode();
        $breakdowns = static::getContainer()->get(\App\Settlement\Repository\FinancialBreakdownRepository::class);
        [$payment, $appointment] = $this->pendingPaidBooking();

        $this->fireCallback($payment);
        $paidPayment = $this->em->getRepository(Payment::class)->find($payment->getId());
        self::assertFalse($breakdowns->existsForPayment($paidPayment), 'پرداخت به‌تنهایی نباید تقسیم مالی بسازد');

        $reloaded = $this->reload($appointment);
        $this->authJson('POST', '/api/v1/appointment/' . $reloaded->getUuid() . '/confirm', $reloaded->getDoctor()->getUser(), []);

        // بدون نماینده و منشیِ سهم‌بر چیزی برای تقسیم نیست؛ صرفاً نباید خطا بدهد.
        self::assertSame(200, $this->responseCode());
    }

    public function testConfirmedClinicBookingIsFiledUnderTheClinic(): void
    {
        $this->enableTestMode();
        [$payment, $appointment] = $this->pendingPaidBooking(function (Doctor $doctor): Clinic {
            $clinic = new Clinic($this->createUser(['ROLE_CLINIC']));
            $clinic->setName('کلینیک پرداخت');
            $clinic->getDoctors()->add($doctor);
            $this->em->persist($clinic);
            $this->em->flush();

            return $clinic;
        });

        $this->fireCallback($payment);
        $reloaded = $this->reload($appointment);

        $this->authJson('POST', '/api/v1/appointment/' . $reloaded->getUuid() . '/confirm', $reloaded->getDoctor()->getUser(), []);
        self::assertSame(200, $this->responseCode());

        $confirmed = $this->reload($reloaded);
        $records   = $this->em->getRepository(PatientRecord::class)->findBy(['user' => $confirmed->getUser()]);

        self::assertCount(1, $records, 'یک نوبت، یک پرونده — نه یکی برای پزشک و یکی برای کلینیک');
        self::assertSame('clinic', $records[0]->getEntityType());
    }
}
