<?php

namespace Tests\Feature\Simulation;

use App\Models\Branch;
use App\Models\Customer;
use App\Models\FinancialAccount;
use App\Models\LoanProduct;
use App\Models\User;
use App\Services\BranchAccessService;
use App\Services\LoanCalculationService;
use App\Support\Simulation\SimulationExpectations;
use Carbon\CarbonImmutable;
use Database\Seeders\SimulationSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\File;
use Tests\TestCase;

class SimulationFoundationTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        config([
            'simulation.enabled' => true,
            'simulation.phase' => 'foundation',
            'simulation.anchor_date' => SimulationExpectations::ANCHOR_DATE,
            'simulation.as_of_date' => '2026-08-15',
            'simulation.user_password' => 'Simulation@2026',
        ]);
    }

    public function test_simulation_foundation_is_idempotent_and_matches_control_counts(): void
    {
        $this->seed(SimulationSeeder::class);
        $this->seed(SimulationSeeder::class);

        $this->assertSame(
            count(SimulationExpectations::branches()),
            Branch::query()->where('code', 'like', 'SIM-%')->count(),
        );
        $this->assertSame(
            count(SimulationExpectations::staff()),
            User::query()->where('username', 'like', 'sim.%')->count(),
        );
        $this->assertSame(
            SimulationExpectations::accountCount(),
            $this->systemWide(fn () => FinancialAccount::query()->where('name', 'like', 'SIM %')->count()),
        );
        $this->assertSame(
            count(SimulationExpectations::customers()),
            $this->systemWide(fn () => Customer::query()->where('customer_number', 'like', 'SIM-CUS-%')->count()),
        );
        $this->assertSame(
            count(SimulationExpectations::loanProducts()),
            LoanProduct::query()->where('name', 'like', 'SIM %')->count(),
        );
    }

    public function test_opening_financial_account_control_is_exact(): void
    {
        $this->seed(SimulationSeeder::class);

        $accounts = $this->systemWide(
            fn () => FinancialAccount::query()->where('name', 'like', 'SIM %')->get()
        );
        $opening = $accounts->sum(fn (FinancialAccount $account) => (float) $account->opening_balance);
        $current = $accounts->sum(fn (FinancialAccount $account) => (float) $account->current_balance);

        $this->assertSame(SimulationExpectations::openingAccountTotal(), number_format($opening, 2, '.', ''));
        $this->assertSame(SimulationExpectations::openingAccountTotal(), number_format($current, 2, '.', ''));
    }

    public function test_staff_use_canonical_roles_and_branch_assignments(): void
    {
        $this->seed(SimulationSeeder::class);

        $dar = Branch::query()->where('code', 'SIM-DAR')->firstOrFail();
        $mwanza = Branch::query()->where('code', 'SIM-MZA')->firstOrFail();
        $arusha = Branch::query()->where('code', 'SIM-ARU')->firstOrFail();
        $loanOfficer = User::query()->where('username', 'sim.dar.loan')->firstOrFail();
        $auditor = User::query()->where('username', 'sim.auditor')->firstOrFail();
        $access = app(BranchAccessService::class);

        $this->assertTrue($loanOfficer->hasRole('loan-officer'));
        $this->assertSame([$dar->id], $access->accessibleBranchIds($loanOfficer));
        $this->assertFalse($access->canAccessBranch($loanOfficer, $mwanza->id));

        $this->assertTrue($auditor->hasRole('auditor'));
        $this->assertEqualsCanonicalizing(
            [$dar->id, $mwanza->id, $arusha->id],
            $access->accessibleBranchIds($auditor),
        );

        $this->assertSame($dar->id, $loanOfficer->primary_branch_id);
        $this->assertSame($dar->id, $auditor->primary_branch_id);
        $this->assertSame(0, $loanOfficer->permissions()->count(), 'Simulation staff must inherit permissions through roles, not direct user grants.');
    }

    public function test_customer_identity_modes_and_branch_distribution_are_controlled(): void
    {
        $this->seed(SimulationSeeder::class);

        $customers = $this->systemWide(
            fn () => Customer::query()->where('customer_number', 'like', 'SIM-CUS-%')->get()
        );

        $this->assertSame(12, $customers->count());
        $this->assertSame(6, $customers->where('identity_mode', 'with_nin')->count());
        $this->assertSame(6, $customers->where('identity_mode', 'without_nin')->count());
        $this->assertSame(1, $customers->where('status', 'inactive')->count());

        foreach (['SIM-DAR', 'SIM-MZA', 'SIM-ARU'] as $branchCode) {
            $branch = Branch::query()->where('code', $branchCode)->firstOrFail();
            $this->assertSame(4, $customers->where('branch_id', $branch->id)->count());
        }
    }

    public function test_seeded_loan_products_cover_supported_calculation_modes(): void
    {
        $this->seed(SimulationSeeder::class);

        $calculator = app(LoanCalculationService::class);
        $start = CarbonImmutable::parse(SimulationExpectations::ANCHOR_DATE);
        $products = LoanProduct::query()->where('name', 'like', 'SIM %')->get()->keyBy('name');

        $flat = $calculator->calculate($products->get('SIM Flat Monthly'), '1000000.00', $start);
        $reducing = $calculator->calculate($products->get('SIM Reducing Weekly'), '1000000.00', $start);
        $fixed = $calculator->calculate($products->get('SIM Fixed Lump Sum'), '1000000.00', $start);
        $secured = $calculator->calculate($products->get('SIM Biweekly Secured'), '1000000.00', $start);

        $this->assertSame('flat', $flat['interest_method']);
        $this->assertSame(3, $flat['installment_count']);
        $this->assertSame('reducing_balance', $reducing['interest_method']);
        $this->assertSame(8, $reducing['installment_count']);
        $this->assertSame('fixed_amount', $fixed['interest_method']);
        $this->assertSame(1, $fixed['installment_count']);
        $this->assertSame('flat', $secured['interest_method']);
        $this->assertSame(6, $secured['installment_count']);
    }

    public function test_normal_database_seeder_never_loads_simulation_data(): void
    {
        $source = File::get(database_path('seeders/DatabaseSeeder.php'));

        $this->assertStringNotContainsString('SimulationSeeder', $source);
        $this->assertStringNotContainsString('SimulationFoundationSeeder', $source);
    }
}
