<?php

namespace App\Tests\Billing;

use App\Billing\Service\BillingCalculator;
use App\Billing\ValueObject\Money;
use App\Insurance\ValueObject\CoverageRule;
use PHPUnit\Framework\TestCase;

class BillingCalculatorTest extends TestCase
{
    private BillingCalculator $calc;

    protected function setUp(): void
    {
        $this->calc = new BillingCalculator();
    }

    public function testNoInsuranceAllPatient(): void
    {
        $b = $this->calc->calculateItem(new Money(600_000), null, null);
        $this->assertSame(600_000, $b->patientRials);
        $this->assertSame(0, $b->baseInsuranceRials);
        $this->assertSame(0, $b->supplementaryRials);
    }

    public function testBaseAndSupplementary(): void
    {
        // کل 600,000؛ پایه 70% → 420,000؛ مکمل روی 180,000 با ~66.667% → 120,000؛ بیمار 60,000
        $base = new CoverageRule(coveragePercent: 70, franchisePercent: 0, ceilingRials: null);
        $supp = new CoverageRule(coveragePercent: 66.6667, franchisePercent: 0, ceilingRials: null);

        $b = $this->calc->calculateItem(new Money(600_000), $base, $supp);

        $this->assertSame(420_000, $b->baseInsuranceRials);
        $this->assertSame(120_000, $b->supplementaryRials);
        $this->assertSame(60_000, $b->patientRials);
        $this->assertSame(
            $b->totalRials,
            $b->baseInsuranceRials + $b->supplementaryRials + $b->patientRials
        );
    }

    public function testBaseOnly(): void
    {
        $base = new CoverageRule(coveragePercent: 70, franchisePercent: 0, ceilingRials: null);
        $b = $this->calc->calculateItem(new Money(600_000), $base, null);
        $this->assertSame(420_000, $b->baseInsuranceRials);
        $this->assertSame(0, $b->supplementaryRials);
        $this->assertSame(180_000, $b->patientRials);
    }

    public function testBaseCeilingCapsShare(): void
    {
        // پایه 70% = 420,000 ولی سقف 300,000 → بیمار باقی را می‌دهد
        $base = new CoverageRule(coveragePercent: 70, franchisePercent: 0, ceilingRials: 300_000);
        $b = $this->calc->calculateItem(new Money(600_000), $base, null);
        $this->assertSame(300_000, $b->baseInsuranceRials);
        $this->assertSame(300_000, $b->patientRials);
    }

    /** سناریوی مرجع کاربر: ویزیت 5,952,000 ریال با پوشش پایهٔ ۳۰٪ (بستری). */
    public function testReferenceScenarioBasePercentOnly(): void
    {
        $base = new CoverageRule(coveragePercent: 30.0, franchisePercent: 0, ceilingRials: null);

        $b = $this->calc->calculateItem(new Money(5_952_000), $base, null);

        $this->assertSame(1_785_600, $b->baseInsuranceRials);
        $this->assertSame(4_166_400, $b->patientRials);
        $this->assertSame($b->totalRials, $b->baseInsuranceRials + $b->patientRials);
    }

    public function testBaseFranchiseDoesNotChargePatient(): void
    {
        // فرانشیز در بیمهٔ پایه بی‌اثر است: پایه 100% → سهم بیمار صفر.
        $base = new CoverageRule(coveragePercent: 100, franchisePercent: 50, ceilingRials: null);
        $b = $this->calc->calculateItem(new Money(600_000), $base, null);
        $this->assertSame(0, $b->patientRials);
        $this->assertSame(600_000, $b->baseInsuranceRials);
    }

    public function testSupplementaryFranchiseIsDeductedFromTheInsurerShare(): void
    {
        // پایه 70% → 420,000؛ باقیمانده 180,000؛ تکمیلی 100% منهای فرانشیز 10% → 162,000.
        $base = new CoverageRule(coveragePercent: 70, franchisePercent: 50, ceilingRials: null);
        $supp = new CoverageRule(coveragePercent: 100, franchisePercent: 10, ceilingRials: null);

        $b = $this->calc->calculateItem(new Money(600_000), $base, $supp);

        $this->assertSame(420_000, $b->baseInsuranceRials);
        $this->assertSame(162_000, $b->supplementaryRials);
        $this->assertSame(18_000, $b->patientRials);
        $this->assertSame(
            $b->totalRials,
            $b->baseInsuranceRials + $b->supplementaryRials + $b->patientRials,
            'فرانشیز نباید جمع سهم‌ها را از کل بیشتر کند'
        );
    }

    /** سناریوی مرجع: بدون بیمهٔ پایه، تکمیلی ۹۰٪ با فرانشیز ۱۰٪. */
    public function testSupplementaryOnlyWithFranchise(): void
    {
        $supp = new CoverageRule(coveragePercent: 90, franchisePercent: 10, ceilingRials: null);

        $b = $this->calc->calculateItem(new Money(1_000_000), null, $supp);

        $this->assertSame(800_000, $b->supplementaryRials);
        $this->assertSame(200_000, $b->patientRials);
        $this->assertSame($b->totalRials, $b->supplementaryRials + $b->patientRials);
    }

    public function testFullFranchiseLeavesNothingForTheSupplementary(): void
    {
        $supp = new CoverageRule(coveragePercent: 100, franchisePercent: 100, ceilingRials: null);

        $b = $this->calc->calculateItem(new Money(600_000), null, $supp);

        $this->assertSame(0, $b->supplementaryRials);
        $this->assertSame(600_000, $b->patientRials);
    }

    public function testFranchiseNeverPushesTheInsurerShareBelowZero(): void
    {
        // فرانشیز بزرگ‌تر از تعهد: سهم بیمه صفر می‌شود، نه منفی.
        $supp = new CoverageRule(coveragePercent: 20, franchisePercent: 80, ceilingRials: null);

        $b = $this->calc->calculateItem(new Money(600_000), null, $supp);

        $this->assertSame(0, $b->supplementaryRials);
        $this->assertSame(600_000, $b->patientRials);
    }

    public function testZeroPercentLeavesEverythingToPatient(): void
    {
        $base = new CoverageRule(coveragePercent: 0, franchisePercent: 0, ceilingRials: null);
        $b = $this->calc->calculateItem(new Money(600_000), $base, null);
        $this->assertSame(0, $b->baseInsuranceRials);
        $this->assertSame(600_000, $b->patientRials);
    }

    public function testFullPercentLeavesNothingToPatient(): void
    {
        $base = new CoverageRule(coveragePercent: 100, franchisePercent: 0, ceilingRials: null);
        $b = $this->calc->calculateItem(new Money(600_000), $base, null);
        $this->assertSame(600_000, $b->baseInsuranceRials);
        $this->assertSame(0, $b->patientRials);
    }

    public function testNotCoveredRule(): void
    {
        $b = $this->calc->calculateItem(new Money(600_000), CoverageRule::notCovered(), CoverageRule::notCovered());
        $this->assertSame(600_000, $b->patientRials);
        $this->assertSame(0, $b->baseInsuranceRials);
    }
}
