<?php

namespace App\Tests\Patient;

use App\Auth\Entity\User;
use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Entity\ServiceSection;
use App\Doctor\Entity\Doctor;
use App\Insurance\Entity\Insurance;
use App\Insurance\Entity\TenantInsurance;
use App\Insurance\Entity\TenantServiceCoverage;
use App\Insurance\Enum\InsuranceType;
use App\Patient\Entity\PatientRecord;
use App\Tests\ApiTestCase;

/**
 * POST /api/v1/patient/{uuid}/session — the insurance share breakdown persisted on
 * the session. The payment page, the invoice modal and the claims dashboard all read
 * these fields, so they must come out of BillingCalculator and never be re-derived.
 */
class SessionInsuranceShareTest extends ApiTestCase
{
    /** @return array{0: User, 1: Doctor, 2: PatientRecord} */
    private function tenant(): array
    {
        $owner  = $this->createUser(['ROLE_DOCTOR']);
        $doctor = new Doctor($owner, 'دکتر تست');
        $this->em->persist($doctor);
        $this->em->flush();

        $patient = $this->createUser(['ROLE_USER']);
        $record  = new PatientRecord('doctor', $doctor->getId(), $patient, 'doctor', $doctor->getId());
        $this->em->persist($record);
        $this->em->flush();

        return [$owner, $doctor, $record];
    }

    private function serviceItem(Doctor $doctor, int $priceRials, bool $insuranceCovered): ServiceItem
    {
        $section = new ServiceSection('doctor', $doctor->getId(), 'جراحی');
        $this->em->persist($section);

        $item = new ServiceItem($section, 'جراحی بینی', $priceRials);
        $item->setInsuranceCovered($insuranceCovered);
        $this->em->persist($item);
        $this->em->flush();

        return $item;
    }

    /** Active contract covering $coveragePercent of every covered service. */
    private function contract(Doctor $doctor, float $coveragePercent, ?ServiceItem $item = null): Insurance
    {
        $insurance = new Insurance('تامین اجتماعی', InsuranceType::Basic);
        $this->em->persist($insurance);
        $this->em->flush();

        $contract = new TenantInsurance('doctor', $doctor->getId(), $insurance->getId());
        $contract->setCoveragePercent($coveragePercent)->setActive(true);
        $this->em->persist($contract);
        $this->em->flush();

        if ($item !== null) {
            $coverage = new TenantServiceCoverage($contract->getId(), $item->getId());
            $coverage->setCovered(true)->setCoveragePercent($coveragePercent);
            $this->em->persist($coverage);
            $this->em->flush();
        }

        return $insurance;
    }

    public function testSessionStoresInsuranceAndPatientSharesForACoveredService(): void
    {
        [$owner, $doctor, $record] = $this->tenant();
        $item      = $this->serviceItem($doctor, 40_000_000, true);
        $insurance = $this->contract($doctor, 70.0, $item);

        $res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/session', $owner, [
            'visit_price_rials' => 3_000_000,
            'insurance_base_id' => $insurance->getId(),
            'services'          => [['service_item_uuid' => $item->getUuid(), 'quantity' => 1]],
        ]);
        self::assertSame(201, $this->responseCode());

        $session = $res['data'];
        // 70% of both the visit and the service is carried by the insurer.
        self::assertSame(43_000_000, $session['gross_total_rials']);
        self::assertSame(30_100_000, $session['base_insurance_rials']);
        self::assertSame(0, $session['supplementary_insurance_rials']);
        self::assertSame(12_900_000, $session['patient_share_rials']);
        // The payable amount is the patient share, not the gross total.
        self::assertSame(12_900_000, $session['final_price_rials']);
        self::assertSame(12_900_000, $session['remaining_rials']);
    }

    public function testTheBreakdownAlwaysSumsBackToTheGrossTotal(): void
    {
        [$owner, $doctor, $record] = $this->tenant();
        $item      = $this->serviceItem($doctor, 1_234_567, true);
        $insurance = $this->contract($doctor, 33.33, $item);

        $res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/session', $owner, [
            'visit_price_rials' => 987_654,
            'insurance_base_id' => $insurance->getId(),
            'services'          => [['service_item_uuid' => $item->getUuid(), 'quantity' => 3]],
        ]);
        self::assertSame(201, $this->responseCode());

        $s = $res['data'];
        self::assertSame(
            $s['gross_total_rials'],
            $s['base_insurance_rials'] + $s['supplementary_insurance_rials'] + $s['patient_share_rials'],
        );
    }

    public function testAServiceWithoutCoverageLeavesTheWholeAmountToThePatient(): void
    {
        [$owner, $doctor, $record] = $this->tenant();
        $item      = $this->serviceItem($doctor, 40_000_000, false);
        $insurance = $this->contract($doctor, 70.0);

        $res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/session', $owner, [
            'visit_price_rials' => 0,
            'insurance_base_id' => $insurance->getId(),
            'services'          => [['service_item_uuid' => $item->getUuid(), 'quantity' => 1]],
        ]);
        self::assertSame(201, $this->responseCode());

        $s = $res['data'];
        self::assertSame(0, $s['base_insurance_rials']);
        self::assertSame(40_000_000, $s['patient_share_rials']);
        self::assertSame(40_000_000, $s['final_price_rials']);
    }

    public function testSessionWithoutInsuranceKeepsTheFullAmountAsThePatientShare(): void
    {
        [$owner, $doctor, $record] = $this->tenant();
        $item = $this->serviceItem($doctor, 2_400_000, true);

        $res = $this->authJson('POST', '/api/v1/patient/' . $record->getUuid() . '/session', $owner, [
            'visit_price_rials' => 500_000,
            'services'          => [['service_item_uuid' => $item->getUuid(), 'quantity' => 1]],
        ]);
        self::assertSame(201, $this->responseCode());

        $s = $res['data'];
        self::assertSame(2_900_000, $s['gross_total_rials']);
        self::assertSame(0, $s['base_insurance_rials']);
        self::assertSame(2_900_000, $s['patient_share_rials']);
    }
}
