<?php

namespace Tests\Feature;

use App\Domain\Billing\InstallmentService;
use App\Domain\Billing\InvoiceService;
use App\Models\Invoice;
use App\Models\Patient;
use App\Models\Tenant;
use App\Support\Tenancy;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class InstallmentTest extends TestCase
{
    use RefreshDatabase;

    protected Tenant $tenant;
    protected Invoice $invoice;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed();

        $this->tenant = Tenant::create(['name' => 'Smile', 'slug' => 'smile']);
        app(Tenancy::class)->set($this->tenant);

        $svc = app(InvoiceService::class);
        $this->invoice = $svc->createCustom(Patient::factory()->create(), [
            ['description' => 'Implant', 'qty' => 1, 'unit_price' => 600000],
        ]);
        $svc->issue($this->invoice);
        $this->invoice->refresh();
    }

    public function test_schedule_with_down_payment_sums_exactly(): void
    {
        app(InstallmentService::class)->createSchedule(
            $this->invoice, months: 6, downPayment: 100000
        );

        $installments = $this->invoice->fresh('installments')->installments;

        $this->assertCount(7, $installments); // down + 6
        $this->assertSame(0, $installments->first()->seq);
        $this->assertSame(100000, $installments->first()->amount);
        $this->assertTrue($installments->first()->due_date->isToday());
        $this->assertSame(600000, (int) $installments->sum('amount'));
        // monthly parts rounded to 250
        $installments->slice(1, 5)->each(
            fn ($i) => $this->assertSame(0, $i->amount % 250)
        );
    }

    public function test_uneven_amount_remainder_lands_on_last_installment(): void
    {
        $svc = app(InvoiceService::class);
        $invoice = $svc->createCustom(Patient::factory()->create(), [
            ['description' => 'Ortho', 'qty' => 1, 'unit_price' => 1000000],
        ]);
        $svc->issue($invoice);

        app(InstallmentService::class)->createSchedule($invoice->fresh(), months: 7);
        $parts = $invoice->fresh('installments')->installments;

        $this->assertCount(7, $parts);
        $this->assertSame(1000000, (int) $parts->sum('amount'));
        $base = $parts->first()->amount;
        $this->assertSame(0, $base % 250);
        $this->assertGreaterThanOrEqual($base, $parts->last()->amount); // remainder absorbed
    }

    public function test_monthly_due_dates_advance(): void
    {
        app(InstallmentService::class)->createSchedule(
            $this->invoice, months: 3, firstDueDate: '2026-07-01'
        );
        $dates = $this->invoice->fresh('installments')->installments->pluck('due_date')
            ->map(fn ($d) => $d->toDateString());

        $this->assertSame(['2026-07-01', '2026-08-01', '2026-09-01'], $dates->all());
    }

    public function test_double_schedule_and_oversized_down_payment_rejected(): void
    {
        $svc = app(InstallmentService::class);
        $svc->createSchedule($this->invoice, months: 6);

        try {
            $svc->createSchedule($this->invoice->fresh(), months: 3);
            $this->fail('expected rejection');
        } catch (\Symfony\Component\HttpKernel\Exception\HttpException) {
        }

        $invoice2 = app(InvoiceService::class)->createCustom(Patient::factory()->create(), [
            ['description' => 'Crown', 'qty' => 1, 'unit_price' => 150000],
        ]);
        app(InvoiceService::class)->issue($invoice2);

        $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
        $svc->createSchedule($invoice2->fresh(), months: 3, downPayment: 150000);
    }

    public function test_overdue_scope_and_widgets(): void
    {
        $svc = app(InstallmentService::class);
        $svc->createSchedule($this->invoice, months: 2, downPayment: 100000, firstDueDate: today()->addDays(3)->toDateString());

        // Force the down payment into the past → overdue
        $down = $this->invoice->fresh('installments')->installments->firstWhere('seq', 0);
        $down->update(['due_date' => today()->subDays(5)]);

        $widgets = $svc->dashboardWidgets();
        $this->assertSame(100000, $widgets['overdue']);
        $this->assertSame(1, $widgets['overdue_count']);
        $this->assertSame(250000, $widgets['due_week']); // first monthly part in 3 days
        $this->assertSame(600000, $widgets['total_outstanding']);
    }
}
