<?php

namespace Tests\Feature;

use App\Models\AccountingEntry;
use App\Models\Branch;
use App\Models\Business;
use App\Models\Customer;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\FinancialAccount;
use App\Models\FinancialAccountTransaction;
use App\Models\Loan;
use App\Models\LoanPayment;
use App\Models\LoanProduct;
use App\Models\LoanWriteOff;
use App\Models\LoanWriteOffRecovery;
use App\Models\Role;
use App\Models\User;
use App\Services\FinancialReportService;
use App\Services\LoanBranchReportService;
use App\Services\ManagementDashboardService;
use App\Services\MoneyMath;
use Carbon\CarbonImmutable;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ManagementDashboardWorkflowTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        CarbonImmutable::setTestNow('2026-08-12 14:00:00');
        $this->seed(DatabaseSeeder::class);
        $this->actingAs(User::where('username','admin')->firstOrFail());
    }

    protected function tearDown(): void
    {
        CarbonImmutable::setTestNow();
        parent::tearDown();
    }

    public function test_dashboard_kpis_reconcile_to_step_19_and_step_20_reports(): void
    {
        [$branch] = $this->fixture('reconcile');
        $dashboard = app(ManagementDashboardService::class)->generate([$branch->id], '2026-08-12', '2026-08-12');
        $financial = app(FinancialReportService::class);
        $loans = app(LoanBranchReportService::class);

        $collections = $financial->generate('collections', [$branch->id], '2026-08-12', '2026-08-12');
        $disbursements = $financial->generate('disbursements', [$branch->id], '2026-08-12', '2026-08-12');
        $expenses = $financial->generate('expenses', [$branch->id], '2026-08-12', '2026-08-12');
        $principal = $loans->generate('outstanding-principal', [$branch->id], '2026-08-12', '2026-08-12');
        $interest = $loans->generate('outstanding-interest', [$branch->id], '2026-08-12', '2026-08-12');
        $overdue = $loans->generate('overdue-loans', [$branch->id], '2026-08-12', '2026-08-12');

        $this->assertSame($collections['total_collections'], $dashboard['metrics']['collections']);
        $this->assertSame($disbursements['total_disbursements'], $dashboard['metrics']['disbursements']);
        $this->assertSame($expenses['total_expenses'], $dashboard['metrics']['expenses']);
        $this->assertSame($this->summary($principal, 'Outstanding principal'), $dashboard['metrics']['outstanding_principal']);
        $this->assertSame($this->summary($interest, 'Outstanding interest'), $dashboard['metrics']['expected_interest']);
        $this->assertSame($this->summary($overdue, 'Total overdue'), $dashboard['metrics']['overdue_amount']);
        $this->assertSame('700.00', $dashboard['metrics']['outstanding_principal']);
        $this->assertSame('70.00', $dashboard['metrics']['expected_interest']);
        $this->assertSame('150.00', $dashboard['metrics']['collections']);
        $this->assertSame('300.00', $dashboard['metrics']['disbursements']);
        $this->assertSame('40.00', $dashboard['metrics']['expenses']);
        $this->assertSame('-15.00', $dashboard['metrics']['profit_this_month']);
    }

    public function test_available_funds_and_account_groups_are_reconstructed_from_transactions_not_mutable_balance(): void
    {
        [$branch, $account] = $this->fixture('balances');
        $account->update(['current_balance' => '999999.99']);

        $dashboard = app(ManagementDashboardService::class)->generate([$branch->id], '2026-08-12', '2026-08-12');

        $this->assertSame('810.00', $dashboard['metrics']['available_funds']);
        $this->assertSame('810.00', $dashboard['account_balances']['cash']);
        $this->assertSame('0.00', $dashboard['account_balances']['mobile_money']);
        $this->assertSame('0.00', $dashboard['account_balances']['bank']);
        $this->assertSame('0.00', $dashboard['account_balances']['other']);
        $this->assertSame('810.00', $dashboard['account_summaries'][0]['closing_balance']);
        $this->assertSame('100.00', $dashboard['metrics']['written_off_portfolio']);
        $this->assertSame('1', $dashboard['metrics']['completed_loans']);
    }

    public function test_chart_series_and_branch_performance_reconcile_to_period_sources(): void
    {
        [$branch] = $this->fixture('charts');
        $dashboard = app(ManagementDashboardService::class)->generate([$branch->id], '2026-08-12', '2026-08-12');
        $money = app(MoneyMath::class);

        $flowCollections = 0;
        $flowDisbursements = 0;
        foreach ($dashboard['flow_series'] as $row) {
            $flowCollections += $money->toMinor($row['collections']);
            $flowDisbursements += $money->toMinor($row['disbursements']);
        }
        $income = 0;
        $expense = 0;
        foreach ($dashboard['income_expense_series'] as $row) {
            $income += $money->toMinor($row['income']);
            $expense += $money->toMinor($row['expenses']);
        }

        $this->assertSame($money->toMinor($dashboard['metrics']['collections']), $flowCollections);
        $this->assertSame($money->toMinor($dashboard['metrics']['disbursements']), $flowDisbursements);
        $this->assertSame('25.00', $money->fromMinor($income));
        $this->assertSame('40.00', $money->fromMinor($expense));
        $this->assertSame('-15.00', $dashboard['period_pnl']['net_profit']);
        $this->assertCount(1, $dashboard['branch_performance']);
        $this->assertSame($branch->name, $dashboard['branch_performance'][0]['branch']);
        $this->assertSame('150.00', $dashboard['branch_performance'][0]['collections']);
        $this->assertSame('1000.00', $dashboard['branch_performance'][0]['disbursed_amount']);
        $this->assertSame('50.00', $dashboard['portfolio_performance']['collection_rate']);
    }

    public function test_dashboard_period_presets_custom_range_and_multi_branch_scope_render(): void
    {
        [$branchA] = $this->fixture('period-a');
        [$branchB] = $this->fixture('period-b');
        $admin = User::where('username', 'admin')->firstOrFail();

        foreach (['today','yesterday','this-week','this-month','this-year'] as $period) {
            $this->actingAs($admin)->get(route('dashboard', ['period'=>$period,'branch_ids'=>[$branchA->id,$branchB->id]]))
                ->assertOk()->assertSee('Management Dashboard')->assertSee($branchA->name)->assertSee($branchB->name);
        }

        $this->actingAs($admin)->get(route('dashboard', [
            'period'=>'custom','from'=>'2026-08-01','to'=>'2026-08-12','branch_ids'=>[$branchA->id,$branchB->id],
        ]))->assertOk()->assertSee('01 Aug 2026')->assertSee('12 Aug 2026');

        $this->actingAs($admin)->get(route('dashboard', [
            'period'=>'custom','from'=>'2026-08-12','to'=>'2026-08-01','branch_ids'=>[$branchA->id],
        ]))->assertSessionHasErrors('to');
    }

    public function test_management_dashboard_branch_scope_is_backend_enforced(): void
    {
        [$branchA] = $this->fixture('scope-a');
        [$branchB] = $this->fixture('scope-b');
        $manager = $this->staff('branch-manager', $branchA, 'dashboard_manager_a');

        $this->actingAs($manager)->get(route('dashboard'))->assertOk()->assertSee('Management Dashboard')->assertSee($branchA->name)->assertDontSee($branchB->name);
        $this->actingAs($manager)->get(route('dashboard', ['branch_ids'=>[$branchB->id]]))->assertForbidden();
    }

    public function test_non_report_staff_get_operational_workspace_without_management_figures(): void
    {
        [$branch] = $this->fixture('restricted');
        $cashier = $this->staff('cashier', $branch, 'dashboard_cashier');

        $this->actingAs($cashier)->get(route('dashboard'))
            ->assertOk()
            ->assertSee('Workspace')
            ->assertSee('Available operations')
            ->assertDontSee('Total Available Funds')
            ->assertDontSee('Written-Off Portfolio');
    }

    private function fixture(string $tag): array
    {
        $business = Business::firstOrFail();
        $branch = Branch::create(['business_id'=>$business->id,'name'=>'Dashboard '.str($tag)->headline(),'status'=>'active']);
        $account = FinancialAccount::create([
            'branch_id'=>$branch->id,'name'=>'Cash '.$tag,'account_type'=>'cash','opening_balance'=>'0.00','current_balance'=>'0.00','status'=>'active',
        ]);
        $officer = $this->staff('loan-officer', $branch, 'dashboard_officer_'.$tag);
        $customer = Customer::create([
            'branch_id'=>$branch->id,'customer_number'=>'CUS-DASH-'.strtoupper(substr(md5($tag),0,8)),'identity_mode'=>'without_nin',
            'first_name'=>'Asha','last_name'=>str($tag)->headline()->toString(),'phone'=>'076'.str_pad((string)$branch->id,7,'0',STR_PAD_LEFT),'status'=>'active',
        ]);
        $product = LoanProduct::create([
            'business_id'=>$business->id,'name'=>'Dashboard Product '.$tag,'minimum_amount'=>'100.00','maximum_amount'=>'5000.00','duration'=>2,
            'duration_unit'=>'months','interest_rate'=>'10.000000','interest_calculation_method'=>'flat','repayment_frequency'=>'monthly','grace_period_days'=>0,
            'late_payment_penalty_type'=>'none','late_payment_penalty_value'=>'0.000000','processing_fee_type'=>'fixed','processing_fee_value'=>'0.000000',
            'insurance_fee_type'=>'fixed','insurance_fee_value'=>'0.000000','other_charges'=>'0.00','collateral_required'=>false,'guarantor_required'=>false,'status'=>'active',
        ]);

        $active = $this->loan($branch,$customer,$product,$officer,'ACTIVE-'.$tag,'active','1000.00','100.00','700.00','70.00');
        $active->update([
            'total_paid'=>'330.00','overdue_principal'=>'100.00','overdue_interest'=>'10.00','overdue_fees'=>'0.00','overdue_penalties'=>'0.00',
            'total_overdue'=>'110.00','days_overdue'=>5,'arrears_bucket'=>'1-7 days','disbursed_at'=>'2026-08-12 09:00:00','activated_at'=>'2026-08-12 09:00:00',
        ]);
        $active->installments()->create([
            'schedule_version'=>1,'is_active'=>true,'installment_number'=>1,'principal'=>'180.00','interest'=>'20.00','fees'=>'0.00','amount_due'=>'200.00','due_date'=>'2026-08-12',
            'amount_paid'=>'0.00','remaining'=>'200.00','status'=>'upcoming','principal_paid'=>'0.00','interest_paid'=>'0.00','fees_paid'=>'0.00','penalty_due'=>'0.00','penalty_paid'=>'0.00',
        ]);

        $completed = $this->loan($branch,$customer,$product,$officer,'COMPLETED-'.$tag,'completed','500.00','50.00','0.00','0.00');
        $completed->update(['total_paid'=>'550.00','completed_at'=>'2026-08-10 10:00:00','disbursed_at'=>'2026-07-01 09:00:00']);

        $writtenOffLoan = $this->loan($branch,$customer,$product,$officer,'WRITEOFF-'.$tag,'written_off','300.00','30.00','150.00','0.00');
        $writtenOffLoan->update(['written_off_at'=>'2026-08-09 11:00:00','disbursed_at'=>'2026-06-01 09:00:00']);
        $writeOff = LoanWriteOff::create([
            'write_off_number'=>'WO-DASH-'.strtoupper(substr(md5($tag),0,8)),'loan_id'=>$writtenOffLoan->id,'customer_id'=>$customer->id,'branch_id'=>$branch->id,
            'original_principal'=>'300.00','original_total_repayment'=>'330.00','principal_outstanding'=>'150.00','interest_outstanding'=>'0.00','fees_outstanding'=>'0.00','penalties_outstanding'=>'0.00',
            'write_off_amount'=>'150.00','recovered_amount'=>'50.00','recoverable_balance'=>'100.00','reason'=>'Dashboard test write-off','status'=>'approved',
            'requested_by'=>$officer->id,'requested_at'=>'2026-08-08 09:00:00','approved_by'=>$officer->id,'approved_at'=>'2026-08-09 11:00:00',
        ]);
        LoanWriteOffRecovery::create([
            'recovery_number'=>'WOR-DASH-'.strtoupper(substr(md5($tag),0,8)),'loan_write_off_id'=>$writeOff->id,'loan_id'=>$writtenOffLoan->id,'customer_id'=>$customer->id,
            'branch_id'=>$branch->id,'financial_account_id'=>$account->id,'amount'=>'50.00','recovery_date'=>'2026-08-12 11:00:00','method'=>'cash','reference'=>'REC-'.$tag,'received_by'=>$officer->id,
        ]);

        LoanPayment::create([
            'receipt_number'=>'REC-DASH-'.strtoupper(substr(md5($tag),0,8)),'loan_id'=>$active->id,'customer_id'=>$customer->id,'branch_id'=>$branch->id,
            'financial_account_id'=>$account->id,'amount'=>'100.00','principal_amount'=>'80.00','interest_amount'=>'20.00','payment_date'=>'2026-08-12 10:00:00',
            'method'=>'cash','reference'=>'PAY-'.$tag,'received_by'=>$officer->id,
        ]);

        $this->transaction($branch,$account,'2026-08-01 08:00:00','opening_balance','1000.00','0.00');
        $this->transaction($branch,$account,'2026-08-12 10:00:00','loan_payment','100.00','0.00');
        $this->transaction($branch,$account,'2026-08-12 11:00:00','loan_write_off_recovery','50.00','0.00');
        $this->transaction($branch,$account,'2026-08-12 09:00:00','loan_disbursement','0.00','300.00');
        $this->transaction($branch,$account,'2026-08-12 12:00:00','expense','0.00','40.00');

        $category = ExpenseCategory::where('business_id',$business->id)->firstOrFail();
        Expense::create([
            'expense_number'=>'EXP-DASH-'.strtoupper(substr(md5($tag),0,8)),'branch_id'=>$branch->id,'expense_category_id'=>$category->id,'financial_account_id'=>$account->id,
            'description'=>'Dashboard expense','amount'=>'40.00','expense_date'=>'2026-08-12','status'=>'approved','entered_by'=>$officer->id,'approved_by'=>$officer->id,'approved_at'=>'2026-08-12 12:00:00',
        ]);

        $this->entry($branch,'interest','0.00','20.00','INT-'.$tag);
        $this->entry($branch,'fees','0.00','5.00','FEE-'.$tag);
        $this->entry($branch,'expenses','40.00','0.00','EXP-'.$tag);

        return [$branch,$account,$active,$completed,$writtenOffLoan];
    }

    private function loan(Branch $branch, Customer $customer, LoanProduct $product, User $officer, string $tag, string $status, string $principal, string $interest, string $principalOutstanding, string $interestOutstanding): Loan
    {
        $total = app(MoneyMath::class)->fromMinor(app(MoneyMath::class)->toMinor($principal) + app(MoneyMath::class)->toMinor($interest));
        $outstanding = app(MoneyMath::class)->fromMinor(app(MoneyMath::class)->toMinor($principalOutstanding) + app(MoneyMath::class)->toMinor($interestOutstanding));
        return Loan::create([
            'branch_id'=>$branch->id,'customer_id'=>$customer->id,'loan_product_id'=>$product->id,'sequence_number'=>Loan::where('branch_id',$branch->id)->count()+1,'sequence_year'=>2026,
            'loan_number'=>'LN-DASH-'.strtoupper(substr(md5($tag),0,10)),'principal'=>$principal,'interest'=>$interest,'processing_fee'=>'0.00','insurance_fee'=>'0.00','other_charges'=>'0.00','fees'=>'0.00',
            'total_repayment'=>$total,'principal_outstanding'=>$principalOutstanding,'interest_outstanding'=>$interestOutstanding,'fees_outstanding'=>'0.00','penalties_outstanding'=>'0.00','total_outstanding'=>$outstanding,
            'total_paid'=>'0.00','overdue_principal'=>'0.00','overdue_interest'=>'0.00','overdue_fees'=>'0.00','overdue_penalties'=>'0.00','total_overdue'=>'0.00','days_overdue'=>0,
            'purpose'=>'Dashboard test','schedule_start_date'=>'2026-08-01','status'=>$status,'created_by'=>$officer->id,
        ]);
    }

    private function transaction(Branch $branch, FinancialAccount $account, string $date, string $type, string $in, string $out): FinancialAccountTransaction
    {
        $number = FinancialAccountTransaction::count()+1;
        return FinancialAccountTransaction::create([
            'transaction_number'=>'FTX-DASH-'.str_pad((string)$number,6,'0',STR_PAD_LEFT),'account_id'=>$account->id,'branch_id'=>$branch->id,'transaction_date'=>$date,
            'reference'=>'DASH-'.$number,'type'=>$type,'description'=>'Dashboard test '.$type,'money_in'=>$in,'money_out'=>$out,'running_balance'=>'0.00',
        ]);
    }

    private function entry(Branch $branch, string $category, string $debit, string $credit, string $tag): AccountingEntry
    {
        return AccountingEntry::create([
            'journal_number'=>'JRN-DASH-'.$tag.'-'.$branch->id,'branch_id'=>$branch->id,'entry_date'=>'2026-08-12','reference'=>'DASH-'.$tag,
            'category'=>$category,'event_type'=>'dashboard_test','description'=>'Dashboard test '.$tag,'debit'=>$debit,'credit'=>$credit,
        ]);
    }

    private function staff(string $roleName, Branch $branch, string $username): User
    {
        $user = User::factory()->create(['name'=>str($username)->replace('_',' ')->title()->toString(),'username'=>$username,'status'=>'active','primary_branch_id'=>$branch->id]);
        $user->branches()->attach($branch->id,['is_primary'=>true]);
        $user->roles()->attach(Role::where('name',$roleName)->firstOrFail());
        return $user;
    }

    private function summary(array $report, string $label): string
    {
        return (string) collect($report['summary'])->firstWhere('label',$label)['value'];
    }
}
