<?php

namespace Tests\Feature\CreditScore;

use App\Models\Branch;
use App\Models\Business;
use App\Models\Customer;
use App\Models\CustomerCreditScoreHistory;
use App\Models\CustomerRiskProfile;
use App\Models\Loan;
use App\Models\LoanProduct;
use App\Models\User;
use App\Services\CreditScore\CreditScoreCalibrationReportService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class CreditScoreCalibrationMonitoringTest extends TestCase
{
    use RefreshDatabase;

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

    public function test_calibration_report_uses_immutable_application_scores_and_selected_branch_outcomes(): void
    {
        $business = Business::query()->firstOrFail();
        $branchA = Branch::factory()->create(['business_id' => $business->id, 'name' => 'Calibration A']);
        $branchB = Branch::factory()->create(['business_id' => $business->id, 'name' => 'Calibration B']);
        $customerA = $this->customer($branchA);
        $customerB = $this->customer($branchB);
        $product = LoanProduct::factory()->create(['business_id' => $business->id]);

        $this->profile($customerA, 760, 'very_good', 'v1');
        $this->profile($customerB, 500, 'poor', 'v1');

        $approvedScore = $this->snapshot($customerA, 700, 'good', 'v1', now()->subDays(20));
        $rejectedScore = $this->snapshot($customerA, 680, 'good', 'v1', now()->subDays(15));
        $hiddenScore = $this->snapshot($customerB, 520, 'poor', 'v1', now()->subDays(10));

        Loan::factory()->create([
            'branch_id' => $branchA->id,
            'customer_id' => $customerA->id,
            'loan_product_id' => $product->id,
            'application_credit_score_history_id' => $approvedScore->id,
            'status' => 'active',
            'approved_at' => now()->subDays(18),
            'activated_at' => now()->subDays(17),
            'principal' => 600000,
            'total_outstanding' => 500000,
            'total_overdue' => 50000,
            'days_overdue' => 35,
        ]);

        Loan::factory()->create([
            'branch_id' => $branchA->id,
            'customer_id' => $customerA->id,
            'loan_product_id' => $product->id,
            'application_credit_score_history_id' => $rejectedScore->id,
            'status' => 'rejected',
            'principal' => 300000,
            'total_outstanding' => 0,
            'total_overdue' => 0,
            'days_overdue' => 0,
        ]);

        Loan::factory()->create([
            'branch_id' => $branchB->id,
            'customer_id' => $customerB->id,
            'loan_product_id' => $product->id,
            'application_credit_score_history_id' => $hiddenScore->id,
            'status' => 'written_off',
            'approved_at' => now()->subDays(9),
            'activated_at' => now()->subDays(8),
            'written_off_at' => now()->subDay(),
            'principal' => 900000,
            'total_outstanding' => 0,
            'total_overdue' => 0,
            'days_overdue' => 120,
        ]);

        $historyCount = CustomerCreditScoreHistory::withoutGlobalScopes()->count();
        $approvedSnapshot = CustomerCreditScoreHistory::withoutGlobalScopes()->findOrFail($approvedScore->id);
        $snapshotUpdatedAt = $approvedSnapshot->updated_at?->copy();

        $data = app(CreditScoreCalibrationReportService::class)->generate([$branchA->id], 'v1');

        $this->assertSame(2, $data['summary']['applications']);
        $this->assertSame(1, $data['summary']['approved']);
        $this->assertSame(1, $data['summary']['rejected']);
        $this->assertSame(50.0, $data['summary']['approval_rate']);
        $this->assertSame(1, $data['summary']['observed_loans']);
        $this->assertSame(1, $data['summary']['serious_arrears_loans']);
        $this->assertSame(0, $data['summary']['written_off_loans']);
        $this->assertSame(500000.0, $data['summary']['total_outstanding']);
        $this->assertSame(50000.0, $data['summary']['total_overdue']);
        $this->assertSame(10.0, $data['summary']['arrears_rate']);

        $good = collect($data['bands'])->firstWhere('key', 'good');
        $this->assertNotNull($good);
        $this->assertSame(2, $good['applications']);
        $this->assertSame(1, $good['serious_arrears_loans']);
        $this->assertSame(0, $good['written_off_loans']);

        $this->assertCount(1, $data['branches']);
        $this->assertSame($branchA->id, $data['branches'][0]['branch_id']);
        $this->assertSame(2, $data['migration']['overall']['comparable']);
        $this->assertSame(2, $data['migration']['overall']['improved_band']);
        $this->assertSame(70.0, $data['migration']['overall']['average_score_change']);

        $this->assertSame($historyCount, CustomerCreditScoreHistory::withoutGlobalScopes()->count());
        $this->assertEquals(
            $snapshotUpdatedAt,
            CustomerCreditScoreHistory::withoutGlobalScopes()->findOrFail($approvedScore->id)->updated_at,
        );
    }

    public function test_version_monitoring_keeps_cross_version_migration_separate(): void
    {
        $business = Business::query()->firstOrFail();
        $branch = Branch::factory()->create(['business_id' => $business->id]);
        $customer = $this->customer($branch);
        $product = LoanProduct::factory()->create(['business_id' => $business->id]);
        $this->profile($customer, 745, 'very_good', 'v2');

        $v1 = $this->snapshot($customer, 690, 'good', 'v1', now()->subDays(40));
        $v2 = $this->snapshot($customer, 735, 'good', 'v2', now()->subDays(5));

        foreach ([$v1, $v2] as $snapshot) {
            Loan::factory()->create([
                'branch_id' => $branch->id,
                'customer_id' => $customer->id,
                'loan_product_id' => $product->id,
                'application_credit_score_history_id' => $snapshot->id,
                'status' => 'active',
                'approved_at' => now(),
                'activated_at' => now(),
                'principal' => 200000,
                'total_outstanding' => 100000,
                'total_overdue' => 0,
                'days_overdue' => 0,
            ]);
        }

        $v1Data = app(CreditScoreCalibrationReportService::class)->generate([$branch->id], 'v1');

        $this->assertSame(['v1', 'v2'], $v1Data['available_score_versions']);
        $this->assertCount(2, $v1Data['versions']);
        $this->assertSame(0, $v1Data['migration']['overall']['comparable']);
        $this->assertSame(1, $v1Data['migration']['overall']['cross_version']);

        $v2Data = app(CreditScoreCalibrationReportService::class)->generate([$branch->id], 'v2');
        $this->assertSame(1, $v2Data['migration']['overall']['comparable']);
        $this->assertSame(0, $v2Data['migration']['overall']['cross_version']);
    }

    public function test_drift_monitor_warns_only_when_both_windows_have_enough_data(): void
    {
        config()->set('credit_score.monitoring.recent_window_days', 30);
        config()->set('credit_score.monitoring.minimum_sample_size', 1);
        config()->set('credit_score.monitoring.average_score_shift_warning_points', 25);
        config()->set('credit_score.monitoring.band_share_shift_warning_percentage_points', 100);

        $business = Business::query()->firstOrFail();
        $branch = Branch::factory()->create(['business_id' => $business->id]);
        $customer = $this->customer($branch);
        $product = LoanProduct::factory()->create(['business_id' => $business->id]);

        $previous = $this->snapshot($customer, 600, 'fair', 'v1', now()->subDays(45));
        $recent = $this->snapshot($customer, 700, 'good', 'v1', now()->subDays(10));

        foreach ([$previous, $recent] as $snapshot) {
            Loan::factory()->create([
                'branch_id' => $branch->id,
                'customer_id' => $customer->id,
                'loan_product_id' => $product->id,
                'application_credit_score_history_id' => $snapshot->id,
                'status' => 'rejected',
                'principal' => 100000,
            ]);
        }

        $data = app(CreditScoreCalibrationReportService::class)->generate([$branch->id], 'v1');
        $this->assertSame('warning', $data['drift']['overall']['status']);
        $this->assertSame(100.0, $data['drift']['overall']['average_score_change']);
        $this->assertTrue($data['drift']['overall']['score_shift_warning']);

        config()->set('credit_score.monitoring.minimum_sample_size', 2);
        $insufficient = app(CreditScoreCalibrationReportService::class)->generate([$branch->id], 'v1');
        $this->assertSame('insufficient_data', $insufficient['drift']['overall']['status']);
        $this->assertFalse($insufficient['drift']['overall']['score_shift_warning']);
        foreach ($insufficient['drift']['overall']['band_shifts'] as $band) {
            $this->assertFalse($band['warning']);
        }
    }

    public function test_calibration_route_is_permissioned_and_rejects_inaccessible_branches(): void
    {
        $business = Business::query()->firstOrFail();
        $branchA = Branch::factory()->create(['business_id' => $business->id, 'name' => 'Visible Calibration Branch']);
        $branchB = Branch::factory()->create(['business_id' => $business->id, 'name' => 'Hidden Calibration Branch']);
        $this->customer($branchA);
        $this->customer($branchB);

        $user = User::factory()->create(['primary_branch_id' => $branchA->id]);
        $user->branches()->sync([$branchA->id]);
        $user->assignRole('branch-manager');

        $this->actingAs($user);

        $this->get(route('credit-score-reports.calibration'))
            ->assertOk()
            ->assertSee('Credit Score Calibration')
            ->assertSee('Visible Calibration Branch')
            ->assertDontSee('Hidden Calibration Branch')
            ->assertSee('Monitoring only');

        $this->get(route('credit-score-reports.calibration', [
            'branch_ids' => [$branchB->id],
        ]))->assertForbidden();
    }

    private function customer(Branch $branch): Customer
    {
        return Customer::factory()->create([
            'branch_id' => $branch->id,
            'monthly_income' => 500000,
            'occupation' => 'Trader',
        ]);
    }

    private function snapshot(Customer $customer, int $score, string $band, string $version, $calculatedAt): CustomerCreditScoreHistory
    {
        return CustomerCreditScoreHistory::withoutGlobalScopes()->create([
            'customer_id' => $customer->id,
            'branch_id' => $customer->branch_id,
            'score' => $score,
            'score_band' => $band,
            'risk_level' => $band === 'poor' ? 'very_high' : ($band === 'fair' ? 'high' : 'moderate'),
            'confidence' => 80,
            'thin_file' => false,
            'manual_review_required' => false,
            'score_version' => $version,
            'trigger' => 'test_application_snapshot',
            'component_scores' => [],
            'factors' => [],
            'explanation' => 'Calibration fixture.',
            'calculated_at' => $calculatedAt,
        ]);
    }

    private function profile(Customer $customer, int $score, string $band, string $version): CustomerRiskProfile
    {
        return CustomerRiskProfile::withoutGlobalScopes()->create([
            'customer_id' => $customer->id,
            'branch_id' => $customer->branch_id,
            'score' => $score,
            'risk_level' => $band === 'poor' ? 'very_high' : ($band === 'fair' ? 'high' : ($band === 'good' ? 'moderate' : 'low')),
            'score_band' => $band,
            'score_version' => $version,
            'confidence' => 85,
            'thin_file' => false,
            'manual_review_required' => false,
            'calculated_at' => now(),
            'last_trigger' => 'test_fixture',
        ]);
    }
}
