<?php

namespace Tests\Feature\CreditScore;

use App\Models\Customer;
use App\Models\CustomerCreditScoreHistory;
use App\Models\Loan;
use App\Models\LoanInstallment;
use App\Models\LoanProduct;
use App\Services\CreditScore\CreditScoreInputBuilder;
use App\Services\CustomerRiskService;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Testing\RefreshDatabase;
use InvalidArgumentException;
use LogicException;
use Tests\TestCase;

class CustomerRiskServiceTest extends TestCase
{
    use RefreshDatabase;

    public function test_refresh_persists_current_score_without_creating_history(): void
    {
        $customer = Customer::factory()->create([
            'monthly_income' => '800000.00',
            'occupation' => 'Retail trader',
        ]);

        $profile = $this->inBranch((int) $customer->branch_id, fn () => app(CustomerRiskService::class)->refresh($customer, 'manual'));

        $this->assertSame($customer->id, $profile->customer_id);
        $this->assertGreaterThanOrEqual(300, (float) $profile->score);
        $this->assertLessThanOrEqual(850, (float) $profile->score);
        $this->assertSame('v1', $profile->score_version);
        $this->assertTrue($profile->thin_file);
        $this->assertFalse($profile->manual_review_required);
        $this->assertSame('manual', $profile->last_trigger);
        $this->assertArrayHasKey('components', $profile->factors);
        $this->assertDatabaseMissing('customer_credit_score_histories', ['customer_id' => $customer->id]);
    }

    public function test_refresh_with_history_updates_current_profile_and_appends_immutable_snapshot(): void
    {
        $customer = Customer::factory()->create([
            'monthly_income' => '600000.00',
            'employer_business' => 'Mlimani Shop',
        ]);

        $history = $this->inBranch(
            (int) $customer->branch_id,
            fn () => app(CustomerRiskService::class)->refreshWithHistory($customer, 'application_submitted'),
        );

        $this->assertInstanceOf(CustomerCreditScoreHistory::class, $history);
        $this->assertSame('application_submitted', $history->trigger);
        $this->assertSame('v1', $history->score_version);
        $this->assertSame($customer->branch_id, $history->branch_id);
        $this->assertArrayHasKey('repayment_behaviour', $history->component_scores);
        $this->assertArrayHasKey('components', $history->factors);

        $profile = $this->inBranch((int) $customer->branch_id, fn () => $customer->riskProfile()->firstOrFail());
        $this->assertSame((float) $history->score, (float) $profile->score);
        $this->assertSame($history->score_band, $profile->score_band);
        $this->assertSame($history->risk_level, $profile->risk_level);
        $this->assertSame('application_submitted', $profile->last_trigger);

        $this->expectException(LogicException::class);
        $history->update(['score' => 300]);
    }

    public function test_preview_for_loan_uses_proposed_burden_without_persisting_score(): void
    {
        $customer = Customer::factory()->create([
            'monthly_income' => '500000.00',
            'occupation' => 'Teacher',
        ]);
        $product = LoanProduct::factory()->create([
            'duration' => 1,
            'duration_unit' => 'months',
            'collateral_required' => false,
            'guarantor_required' => false,
        ]);

        $loan = new Loan([
            'branch_id' => $customer->branch_id,
            'customer_id' => $customer->id,
            'loan_product_id' => $product->id,
            'principal' => '500000.00',
            'total_repayment' => '550000.00',
        ]);
        $loan->setRelation('product', $product);

        $result = $this->inBranch(
            (int) $customer->branch_id,
            fn () => app(CustomerRiskService::class)->previewForLoan($customer, $loan),
        );

        $affordability = $result->components['affordability'];
        $this->assertTrue($affordability->available);
        $this->assertSame(550000.0, (float) $affordability->factors['proposed_monthly_installment']);
        $this->assertGreaterThan(1, (float) $affordability->factors['debt_service_ratio']);
        $this->assertDatabaseMissing('customer_risk_profiles', ['customer_id' => $customer->id]);
        $this->assertDatabaseMissing('customer_credit_score_histories', ['customer_id' => $customer->id]);
    }

    public function test_input_builder_aggregates_active_credit_data_and_ignores_other_branch_rows(): void
    {
        $customer = Customer::factory()->create(['monthly_income' => '1000000.00']);
        $otherCustomer = Customer::factory()->create();
        $product = LoanProduct::factory()->create();

        $loan = Loan::create([
            'branch_id' => $customer->branch_id,
            'customer_id' => $customer->id,
            'loan_product_id' => $product->id,
            'sequence_number' => 1,
            'sequence_year' => 2026,
            'loan_number' => 'LN-SCORE-0001',
            'principal' => '100000.00',
            'interest' => '10000.00',
            'total_repayment' => '110000.00',
            'principal_outstanding' => '100000.00',
            'interest_outstanding' => '10000.00',
            'total_outstanding' => '110000.00',
            'total_paid' => '0.00',
            'total_overdue' => '20000.00',
            'days_overdue' => 20,
            'purpose' => 'Working capital',
            'schedule_start_date' => now()->subMonth()->toDateString(),
            'status' => 'active',
        ]);

        LoanInstallment::create([
            'loan_id' => $loan->id,
            'schedule_version' => 1,
            'is_active' => true,
            'installment_number' => 1,
            'principal' => '100000.00',
            'interest' => '10000.00',
            'fees' => '0.00',
            'amount_due' => '110000.00',
            'due_date' => now()->subDay()->toDateString(),
            'amount_paid' => '0.00',
            'remaining' => '110000.00',
            'status' => 'overdue',
        ]);

        Loan::create([
            'branch_id' => $otherCustomer->branch_id,
            'customer_id' => $customer->id,
            'loan_product_id' => $product->id,
            'sequence_number' => 2,
            'sequence_year' => 2026,
            'loan_number' => 'LN-SCORE-CROSS-BRANCH',
            'principal' => '900000.00',
            'interest' => '90000.00',
            'total_repayment' => '990000.00',
            'total_outstanding' => '990000.00',
            'total_paid' => '0.00',
            'purpose' => 'Isolation test',
            'schedule_start_date' => now()->toDateString(),
            'status' => 'active',
        ]);

        $input = $this->inBranch(
            (int) $customer->branch_id,
            fn () => app(CreditScoreInputBuilder::class)->build($customer),
        );

        $this->assertSame(1, $input['loan_history']['total_loans']);
        $this->assertSame(1, $input['affordability']['active_loan_count']);
        $this->assertSame(110000.0, (float) $input['affordability']['active_total_outstanding']);
        $this->assertSame(1, $input['repayment']['total_installments']);
        $this->assertSame(1, $input['repayment']['missed_installments']);
        $this->assertSame(20, $input['repayment']['current_days_overdue']);
    }

    public function test_preview_rejects_a_loan_from_another_customer_or_branch(): void
    {
        $customer = Customer::factory()->create(['monthly_income' => '500000.00']);
        $otherCustomer = Customer::factory()->create();
        $product = LoanProduct::factory()->create();

        $loan = new Loan([
            'branch_id' => $otherCustomer->branch_id,
            'customer_id' => $otherCustomer->id,
            'loan_product_id' => $product->id,
            'principal' => '100000.00',
            'total_repayment' => '110000.00',
        ]);
        $loan->setRelation('product', $product);

        $this->expectException(InvalidArgumentException::class);
        $this->inBranch(
            (int) $customer->branch_id,
            fn () => app(CustomerRiskService::class)->previewForLoan($customer, $loan),
        );
    }

    public function test_refresh_rejects_customer_outside_current_branch_context(): void
    {
        $customer = Customer::factory()->create(['monthly_income' => '500000.00']);
        $otherCustomer = Customer::factory()->create();

        $this->expectException(ModelNotFoundException::class);
        $this->inBranch(
            (int) $otherCustomer->branch_id,
            fn () => app(CustomerRiskService::class)->refresh($customer, 'manual'),
        );
    }
}
