<?php

namespace Tests\Unit\CreditScore;

use App\Services\CreditScore\CreditScoreCalculator;
use InvalidArgumentException;
use Tests\TestCase;

class CreditScoreCalculatorTest extends TestCase
{
    public function test_strong_profile_scores_higher_than_risky_profile(): void
    {
        $calculator = new CreditScoreCalculator();

        $strong = $calculator->calculate($this->strongInput());
        $risky = $calculator->calculate($this->riskyInput());

        $this->assertGreaterThan($risky->creditScore, $strong->creditScore);
        $this->assertGreaterThanOrEqual(800, $strong->creditScore);
        $this->assertLessThan(580, $risky->creditScore);
        $this->assertSame('excellent', $strong->band);
        $this->assertSame('poor', $risky->band);
    }

    public function test_score_is_bounded_and_calculation_is_deterministic(): void
    {
        $calculator = new CreditScoreCalculator();
        $input = $this->strongInput();

        $first = $calculator->calculate($input);
        $second = $calculator->calculate($input);

        $this->assertGreaterThanOrEqual(300, $first->creditScore);
        $this->assertLessThanOrEqual(850, $first->creditScore);
        $this->assertGreaterThanOrEqual(0, $first->normalizedScore);
        $this->assertLessThanOrEqual(100, $first->normalizedScore);
        $this->assertSame($first->toArray(), $second->toArray());
    }

    public function test_new_customer_is_thin_file_without_being_treated_as_a_defaulter(): void
    {
        $result = (new CreditScoreCalculator())->calculate($this->thinFileInput());

        $this->assertTrue($result->thinFile);
        $this->assertLessThanOrEqual(55, $result->confidence);
        $this->assertGreaterThanOrEqual(500, $result->creditScore);
        $this->assertFalse($result->components['repayment_behaviour']->available);
        $this->assertSame(50.0, $result->components['repayment_behaviour']->normalizedScore);
        $this->assertFalse($result->components['loan_history']->available);
        $this->assertStringContainsString('Thin-file', $result->explanation);
    }

    public function test_missing_income_caps_confidence_and_requires_manual_review(): void
    {
        $input = $this->strongInput();
        $input['affordability']['monthly_income'] = null;

        $result = (new CreditScoreCalculator())->calculate($input);

        $this->assertTrue($result->manualReviewRequired);
        $this->assertLessThanOrEqual(45, $result->confidence);
        $this->assertFalse($result->components['affordability']->available);
        $this->assertTrue($result->components['affordability']->factors['missing_income']);
        $this->assertContains('missing_income_for_affordability', array_column($result->overlays, 'key'));
    }

    public function test_blacklist_requires_manual_review_and_floors_risk_at_moderate(): void
    {
        $input = $this->strongInput();
        $input['conduct']['active_blacklist'] = true;

        $result = (new CreditScoreCalculator())->calculate($input);

        $this->assertTrue($result->manualReviewRequired);
        $this->assertGreaterThanOrEqual(1, $this->riskSeverity($result->riskLevel));
        $this->assertContains('active_blacklist', array_column($result->overlays, 'key'));
        $this->assertStringContainsString('does not automatically approve or reject', $result->explanation);
    }

    public function test_unresolved_write_off_floors_risk_at_high(): void
    {
        $input = $this->strongInput();
        $input['loan_history']['write_offs'] = 1;
        $input['loan_history']['unresolved_write_offs'] = 1;

        $result = (new CreditScoreCalculator())->calculate($input);

        $this->assertTrue($result->manualReviewRequired);
        $this->assertGreaterThanOrEqual(2, $this->riskSeverity($result->riskLevel));
        $this->assertContains('unresolved_write_off', array_column($result->overlays, 'key'));
    }

    public function test_serious_current_arrears_floors_risk_at_high(): void
    {
        $input = $this->strongInput();
        $input['repayment']['current_days_overdue'] = 90;

        $result = (new CreditScoreCalculator())->calculate($input);

        $this->assertTrue($result->manualReviewRequired);
        $this->assertGreaterThanOrEqual(2, $this->riskSeverity($result->riskLevel));
        $this->assertContains('serious_current_arrears', array_column($result->overlays, 'key'));
    }

    public function test_non_required_collateral_is_neutral_and_does_not_reduce_confidence(): void
    {
        $input = $this->strongInput();
        $input['collateral'] = ['collateral_required' => false];

        $result = (new CreditScoreCalculator())->calculate($input);

        $this->assertFalse($result->components['collateral']->available);
        $this->assertSame(50.0, $result->components['collateral']->normalizedScore);
        $this->assertSame(100.0, $result->confidence);
    }

    public function test_invalid_weight_configuration_is_rejected(): void
    {
        $configuration = config('credit_score');
        $configuration['weights']['repayment_behaviour'] = 41;

        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('weights must total 100');

        new CreditScoreCalculator($configuration);
    }

    public function test_scoring_source_does_not_reference_prohibited_profile_fields(): void
    {
        $files = array_merge(
            glob(app_path('Services/CreditScore/*.php')) ?: [],
            glob(app_path('Services/CreditScore/Components/*.php')) ?: [],
        );
        $prohibited = ['gender', 'marital_status', 'date_of_birth', 'region', 'district', 'ward', 'nin'];

        $this->assertNotEmpty($files);
        foreach ($files as $file) {
            $source = file_get_contents($file);
            foreach ($prohibited as $field) {
                $this->assertStringNotContainsString("'{$field}'", $source, "Prohibited scoring field {$field} referenced in {$file}");
                $this->assertStringNotContainsString('"'.$field.'"', $source, "Prohibited scoring field {$field} referenced in {$file}");
            }
        }
    }

    private function strongInput(): array
    {
        return [
            'repayment' => [
                'total_installments' => 12,
                'completed_installments' => 12,
                'on_time_installments' => 12,
                'within_grace_installments' => 0,
                'missed_installments' => 0,
                'current_days_overdue' => 0,
                'max_days_overdue' => 0,
                'overdue_ratio' => 0,
                'late_penalty_count' => 0,
                'serious_arrears_loans' => 0,
            ],
            'loan_history' => [
                'total_loans' => 4,
                'completed_loans' => 3,
                'active_good_loans' => 1,
                'write_offs' => 0,
                'unresolved_write_offs' => 0,
                'restructurings' => 0,
                'write_off_recovery_ratio' => 0,
            ],
            'affordability' => [
                'monthly_income' => 1000000,
                'monthly_debt_service' => 100000,
                'proposed_monthly_installment' => 50000,
                'active_total_outstanding' => 800000,
                'active_loan_count' => 1,
            ],
            'collateral' => [
                'collateral_required' => true,
                'accepted_collateral_value' => 1600000,
                'relevant_principal' => 1000000,
                'valid_collateral' => true,
            ],
            'profile' => [
                'occupation_or_business_present' => true,
                'monthly_income_present' => true,
                'address_present' => true,
                'primary_next_of_kin_present' => true,
                'required_documents_complete' => true,
                'guarantor_required' => false,
            ],
            'conduct' => [
                'active_blacklist' => false,
                'active_watchlist' => false,
                'serious_collection_escalations' => 0,
                'unresolved_control_flags' => 0,
            ],
        ];
    }

    private function riskyInput(): array
    {
        return [
            'repayment' => [
                'total_installments' => 10,
                'completed_installments' => 10,
                'on_time_installments' => 1,
                'within_grace_installments' => 1,
                'missed_installments' => 8,
                'current_days_overdue' => 120,
                'max_days_overdue' => 180,
                'overdue_ratio' => 0.8,
                'late_penalty_count' => 8,
                'serious_arrears_loans' => 2,
            ],
            'loan_history' => [
                'total_loans' => 3,
                'completed_loans' => 0,
                'active_good_loans' => 0,
                'write_offs' => 2,
                'unresolved_write_offs' => 1,
                'restructurings' => 2,
                'write_off_recovery_ratio' => 0,
            ],
            'affordability' => [
                'monthly_income' => 300000,
                'monthly_debt_service' => 180000,
                'proposed_monthly_installment' => 100000,
                'active_total_outstanding' => 2500000,
                'active_loan_count' => 3,
            ],
            'collateral' => [
                'collateral_required' => true,
                'accepted_collateral_value' => 200000,
                'relevant_principal' => 1000000,
                'valid_collateral' => false,
            ],
            'profile' => [
                'occupation_or_business_present' => false,
                'monthly_income_present' => true,
                'address_present' => false,
                'primary_next_of_kin_present' => false,
                'required_documents_complete' => false,
                'guarantor_required' => true,
                'active_guarantor_present' => false,
            ],
            'conduct' => [
                'active_blacklist' => true,
                'active_watchlist' => true,
                'serious_collection_escalations' => 3,
                'unresolved_control_flags' => 2,
            ],
        ];
    }

    private function thinFileInput(): array
    {
        $input = $this->strongInput();
        $input['repayment'] = ['total_installments' => 0, 'completed_installments' => 0];
        $input['loan_history'] = ['total_loans' => 0, 'completed_loans' => 0];
        $input['collateral'] = ['collateral_required' => false];

        return $input;
    }

    private function riskSeverity(string $risk): int
    {
        return ['unrated' => -1, 'low' => 0, 'moderate' => 1, 'high' => 2, 'very_high' => 3][$risk] ?? -1;
    }
}
