<?php

namespace Tests\Feature;

use App\Domain\Billing\InstallmentService;
use App\Domain\Billing\InvoiceService;
use App\Domain\Billing\PaymentService;
use App\Enums\InstallmentStatus;
use App\Enums\InvoiceStatus;
use App\Enums\PaymentMethod;
use App\Models\Invoice;
use App\Models\Patient;
use App\Models\Tenant;
use App\Models\User;
use App\Support\Tenancy;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PaymentTest extends TestCase
{
    use RefreshDatabase;

    protected Tenant $tenant;
    protected Patient $patient;
    protected Invoice $invoice;

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

        $this->tenant = Tenant::create(['name' => 'Smile', 'slug' => 'smile']);
        $this->tenant->subscription()->create([
            'plan_id' => \App\Models\Plan::first()->id, 'status' => 'trialing',
            'starts_at' => now(), 'current_period_ends_at' => now()->addDays(14),
        ]);
        app(Tenancy::class)->set($this->tenant);

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

    public function test_full_payment_marks_invoice_paid_and_clears_debt(): void
    {
        app(PaymentService::class)->record($this->invoice, 150000, PaymentMethod::Cash);

        $invoice = $this->invoice->fresh();
        $this->assertSame(InvoiceStatus::Paid, $invoice->status);
        $this->assertSame(0, $invoice->remaining());
        $this->assertSame(0, $this->patient->fresh()->outstanding_balance);
        $this->assertSame(150000, $this->patient->fresh()->total_paid);
    }

    public function test_partial_payment_keeps_invoice_open_with_badge_amount(): void
    {
        app(PaymentService::class)->record($this->invoice, 50000, PaymentMethod::Card);

        $this->assertSame(InvoiceStatus::PartiallyPaid, $this->invoice->fresh()->status);
        $this->assertSame(100000, $this->patient->fresh()->outstanding_balance);
    }

    public function test_overpayment_is_rejected(): void
    {
        $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class);
        app(PaymentService::class)->record($this->invoice, 150001, PaymentMethod::Cash);
    }

    public function test_installment_payment_spills_to_next(): void
    {
        app(InstallmentService::class)->createSchedule($this->invoice, months: 3); // 50k × 3
        $invoice = $this->invoice->fresh('installments');
        $first = $invoice->installments[0];

        // 80k aimed at the first 50k installment: fills it, spills 30k to #2.
        app(PaymentService::class)->record($invoice, 80000, PaymentMethod::ZainCash, $first, reference: 'ZC123');

        $parts = $invoice->fresh('installments')->installments;
        $this->assertSame(InstallmentStatus::Paid, $parts[0]->status);
        $this->assertSame(InstallmentStatus::Partial, $parts[1]->status);
        $this->assertSame(30000, $parts[1]->paid_amount);
        $this->assertSame(InstallmentStatus::Pending, $parts[2]->status);
        $this->assertSame(80000, $invoice->fresh()->paid_amount);
    }

    public function test_invoice_level_payment_fills_installments_oldest_first(): void
    {
        app(InstallmentService::class)->createSchedule($this->invoice, months: 3);

        app(PaymentService::class)->record($this->invoice->fresh(), 150000, PaymentMethod::BankTransfer, reference: 'TRF9');

        $parts = $this->invoice->fresh('installments')->installments;
        $this->assertTrue($parts->every(fn ($i) => $i->status === InstallmentStatus::Paid));
        $this->assertSame(InvoiceStatus::Paid, $this->invoice->fresh()->status);
    }

    public function test_quick_pay_endpoint_works_for_receptionist(): void
    {
        $reception = User::withoutEvents(fn () => User::create([
            'tenant_id' => $this->tenant->id, 'name' => 'R', 'email' => 'r@x.com', 'password' => 'password123',
        ]));
        $reception->assignRole('receptionist');
        app(Tenancy::class)->forget();

        $base = 'http://smile.'.config('tenancy.central_domain');
        $this->actingAs($reception)
            ->post($base.'/invoices/'.$this->invoice->id.'/payments', [
                'amount' => 150000, 'method' => 'cash',
            ])->assertRedirect()->assertSessionHas('status');

        app(Tenancy::class)->set($this->tenant);
        $this->assertSame(InvoiceStatus::Paid, $this->invoice->fresh()->status);
    }

    public function test_zaincash_requires_reference_via_endpoint(): void
    {
        $owner = User::withoutEvents(fn () => User::create([
            'tenant_id' => $this->tenant->id, 'name' => 'O', 'email' => 'o@x.com', 'password' => 'password123',
        ]));
        $owner->assignRole('clinic_owner');
        app(Tenancy::class)->forget();

        $base = 'http://smile.'.config('tenancy.central_domain');
        $this->actingAs($owner)
            ->post($base.'/invoices/'.$this->invoice->id.'/payments', [
                'amount' => 10000, 'method' => 'zaincash',
            ])->assertSessionHasErrors('reference');
    }
}
