<?php

namespace App\Tests\Patient;

use App\Appointment\Entity\Appointment;
use App\Appointment\Service\AppointmentInsuranceService;
use App\Billing\Repository\ClaimRepository;
use App\Doctor\Entity\Doctor;
use App\Insurance\Entity\Insurance;
use App\Insurance\Entity\TenantInsurance;
use App\Insurance\Enum\InsuranceType;
use App\Insurance\Enum\ServiceCategory;
use App\Insurance\Service\TenantInsuranceService;
use App\Patient\Service\PatientService;
use App\Tests\ApiTestCase;

/**
 * بیمهٔ تکمیلی هم مثل پایه روی خودِ نوبت انتخاب می‌شود، و در مراجعه‌ای که از قطعی‌کردن
 * ساخته می‌شود زنجیره‌ای اعمال می‌گردد: اول پایه روی کل، بعد تکمیلی روی باقیمانده.
 */
class AppointmentSupplementaryInsuranceTest extends ApiTestCase
{
    private Doctor $doctor;
    private Insurance $basic;
    private Insurance $supplementary;

    protected function setUp(): void
    {
        parent::setUp();

        $this->doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر بیمه تکمیلی');
        $this->em->persist($this->doctor);

        $this->basic         = new Insurance('پایهٔ تست ' . random_int(1000, 9999), InsuranceType::Basic);
        $this->supplementary = new Insurance('تکمیلی تست ' . random_int(1000, 9999), InsuranceType::Supplementary);
        $this->em->persist($this->basic);
        $this->em->persist($this->supplementary);
        $this->em->flush();
    }

    private function contract(Insurance $insurance, float $percent, float $franchise = 0.0): TenantInsurance
    {
        $service  = static::getContainer()->get(TenantInsuranceService::class);
        $contract = $service->activate(
            TenantInsurance::TYPE_DOCTOR,
            (int) $this->doctor->getId(),
            (int) $insurance->getId(),
            $percent,
            $franchise,
        );
        $service->setCategoryCoverages($contract, [
            ['key' => 'outpatient', 'coverage_percent' => $percent],
            ['key' => 'inpatient',  'coverage_percent' => $percent],
        ]);

        return $contract;
    }

    private function appointmentWithVisitPrice(int $rials): Appointment
    {
        $appointment = $this->newAppointment($this->doctor, $this->createUser(['ROLE_USER']), 1_790_000_000, 1_790_001_800);
        $appointment->setVisitPriceRials($rials);
        $appointment->setInsuranceServiceCategory(ServiceCategory::Outpatient);
        $appointment->transitionTo(Appointment::STATUS_CONFIRMED);
        $this->em->persist($appointment);
        $this->em->flush();

        return $appointment;
    }

    public function testTheSessionBillsBaseThenSupplementaryOnTheRemainder(): void
    {
        $this->contract($this->basic, 30.0);
        $this->contract($this->supplementary, 90.0, 10.0);

        $appointment = $this->appointmentWithVisitPrice(10_000_000);
        static::getContainer()->get(AppointmentInsuranceService::class)->apply($appointment, [
            'insurance_base_id'          => $this->basic->getId(),
            'insurance_supplementary_id' => $this->supplementary->getId(),
        ]);
        $this->em->flush();

        $session = static::getContainer()->get(PatientService::class)->autoCreateOnAppointmentConfirm($appointment);

        // پایه ۳۰٪ از ۱۰,۰۰۰,۰۰۰ → ۳,۰۰۰,۰۰۰؛ باقیمانده ۷,۰۰۰,۰۰۰؛
        // تکمیلی ۹۰٪ منهای فرانشیز ۱۰٪ → ۵,۶۰۰,۰۰۰؛ سهم بیمار ۱,۴۰۰,۰۰۰.
        self::assertSame((int) $this->supplementary->getId(), $session->getInsuranceSupplementaryId());
        self::assertSame(3_000_000, $session->getBaseInsuranceRials());
        self::assertSame(5_600_000, $session->getSupplementaryInsuranceRials());
        self::assertSame(1_400_000, $session->getPatientShareRials());
        self::assertSame(
            $session->getGrossTotalRials(),
            $session->getBaseInsuranceRials() + $session->getSupplementaryInsuranceRials() + $session->getPatientShareRials(),
        );
    }

    public function testASupplementaryInsuranceCannotBePickedAsTheBasicOne(): void
    {
        $this->contract($this->supplementary, 90.0);
        $appointment = $this->appointmentWithVisitPrice(1_000_000);

        $this->expectExceptionMessage('اینجا فقط بیمهٔ پایه قابل انتخاب است');
        static::getContainer()->get(AppointmentInsuranceService::class)->apply($appointment, [
            'insurance_base_id' => $this->supplementary->getId(),
        ]);
    }

    public function testABasicInsuranceCannotBePickedAsTheSupplementaryOne(): void
    {
        $this->contract($this->basic, 30.0);
        $appointment = $this->appointmentWithVisitPrice(1_000_000);

        $this->expectExceptionMessage('اینجا فقط بیمهٔ تکمیلی قابل انتخاب است');
        static::getContainer()->get(AppointmentInsuranceService::class)->apply($appointment, [
            'insurance_supplementary_id' => $this->basic->getId(),
        ]);
    }

    public function testAnInsuranceWithoutAnActiveContractIsRefused(): void
    {
        $appointment = $this->appointmentWithVisitPrice(1_000_000);

        $this->expectExceptionMessage('این بیمه برای این پزشک/کلینیک قرارداد فعال ندارد');
        static::getContainer()->get(AppointmentInsuranceService::class)->apply($appointment, [
            'insurance_supplementary_id' => $this->supplementary->getId(),
        ]);
    }

    /** مراجعهٔ تکمیلی‌دار باید مطالبهٔ تکمیلی هم بگیرد، نه فقط پایه. */
    public function testTheConfirmedVisitProducesBothClaims(): void
    {
        $this->contract($this->basic, 30.0);
        $this->contract($this->supplementary, 90.0, 10.0);

        $appointment = $this->appointmentWithVisitPrice(10_000_000);
        static::getContainer()->get(AppointmentInsuranceService::class)->apply($appointment, [
            'insurance_base_id'          => $this->basic->getId(),
            'insurance_supplementary_id' => $this->supplementary->getId(),
        ]);
        $this->em->flush();

        // قطعی‌کردن به‌تنهایی باید صورتحساب و مطالبه بسازد — بدون هیچ گام دستی.
        $session = static::getContainer()->get(PatientService::class)->autoCreateOnAppointmentConfirm($appointment);

        $invoice = static::getContainer()->get(\App\Billing\Repository\InvoiceRepository::class)
            ->findBySession((int) $session->getId());
        self::assertNotNull($invoice, 'مراجعهٔ بیمه‌دارِ آمده از نوبت باید صورتحساب داشته باشد');

        $kinds = static::getContainer()->get(ClaimRepository::class)->kindsForInvoice((int) $invoice->getId());
        sort($kinds);
        self::assertSame(['base', 'supplementary'], $kinds);
    }
}
