<?php

namespace Tests\Feature;

use App\Models\AccountingEntry;
use App\Models\Branch;
use App\Models\Business;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\FinancialAccount;
use App\Models\FinancialAccountTransaction;
use App\Models\PayrollEntry;
use App\Models\PayrollPeriod;
use App\Models\Role;
use App\Models\User;
use App\Services\FinancialReportService;
use Carbon\CarbonImmutable;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class FinancialReportsWorkflowTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        CarbonImmutable::setTestNow('2026-08-12 13:30:00');
        $this->seed(DatabaseSeeder::class);
    }

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

    public function test_profit_and_loss_uses_actual_income_and_expense_and_excludes_principal_recovery(): void
    {
        [$branch] = $this->fixture('pnl');
        $this->entry($branch, 'interest', '0.00', '100.00', 'interest');
        $this->entry($branch, 'fees', '0.00', '20.00', 'fees');
        $this->entry($branch, 'penalties', '0.00', '5.00', 'penalties');
        $this->entry($branch, 'write_off_recoveries', '0.00', '10.00', 'recovery');
        $this->entry($branch, 'principal', '0.00', '500.00', 'principal');
        $this->entry($branch, 'expenses', '40.00', '0.00', 'expense');
        $this->entry($branch, 'payroll', '30.00', '0.00', 'payroll');
        $this->entry($branch, 'write_offs', '10.00', '0.00', 'writeoff');

        $report = app(FinancialReportService::class)->generate('profit-loss', [$branch->id], '2026-08-01', '2026-08-31');

        $this->assertSame('135.00', $report['total_income']);
        $this->assertSame('80.00', $report['total_expenses']);
        $this->assertSame('55.00', $report['net_profit']);
        $this->assertSame('500.00', $report['principal_activity_excluded']);
    }

    public function test_cash_flow_and_account_statement_reconcile_from_transactions_not_mutable_account_balance(): void
    {
        [$branch, $account] = $this->fixture('statement');
        $this->transaction($branch, $account, '2026-07-31 09:00:00', 'opening_balance', '1000.00', '0.00');
        $this->transaction($branch, $account, '2026-08-02 09:00:00', 'loan_payment', '200.00', '0.00');
        $this->transaction($branch, $account, '2026-08-03 09:00:00', 'loan_disbursement', '0.00', '100.00');
        $this->transaction($branch, $account, '2026-08-04 09:00:00', 'expense', '0.00', '50.00');
        $account->update(['current_balance' => '9999.99']);

        $service = app(FinancialReportService::class);
        $cashFlow = $service->generate('cash-flow', [$branch->id], '2026-08-01', '2026-08-31');
        $statement = $service->generate('account-statement', [$branch->id], '2026-08-01', '2026-08-31', $account->id);

        $this->assertSame('200.00', $cashFlow['money_in']);
        $this->assertSame('150.00', $cashFlow['money_out']);
        $this->assertSame('50.00', $cashFlow['net_cash_flow']);
        $this->assertSame('1000.00', $statement['summaries'][0]['opening_balance']);
        $this->assertSame('200.00', $statement['summaries'][0]['money_in']);
        $this->assertSame('150.00', $statement['summaries'][0]['money_out']);
        $this->assertSame('1050.00', $statement['summaries'][0]['closing_balance']);
        $this->assertSame('1050.00', $statement['rows'][2]['report_balance']);
    }

    public function test_capital_expense_and_payroll_reports_reconcile_to_posted_records(): void
    {
        [$branch, $account, $business] = $this->fixture('operating');
        $actor = $this->staff('accountant', $branch, 'reports_operating_accountant');
        $this->transaction($branch, $account, '2026-08-01 08:00:00', 'opening_balance', '300.00', '0.00');
        $this->transaction($branch, $account, '2026-08-05 08:00:00', 'capital_injection', '200.00', '0.00');

        $category = ExpenseCategory::where('business_id', $business->id)->firstOrFail();
        Expense::create([
            'expense_number' => 'EXP-REPORT-001', 'branch_id' => $branch->id, 'expense_category_id' => $category->id,
            'financial_account_id' => $account->id, 'description' => 'Office utilities', 'amount' => '75.00',
            'expense_date' => '2026-08-06', 'status' => 'approved', 'entered_by' => $actor->id,
            'approved_by' => $actor->id, 'approved_at' => '2026-08-06 10:00:00',
        ]);

        $employee = User::factory()->create(['name'=>'Payroll Report Employee','username'=>'payroll_report_employee','status'=>'active','primary_branch_id'=>$branch->id]);
        $employee->branches()->attach($branch->id, ['is_primary'=>true]);
        $period = PayrollPeriod::create([
            'period_number'=>'PAY-REPORT-001','business_id'=>$business->id,'branch_id'=>$branch->id,'name'=>'Report Payroll',
            'start_date'=>'2026-08-01','end_date'=>'2026-08-31','pay_date'=>'2026-08-10','status'=>'paid',
            'total_gross'=>'100.00','total_deductions'=>'10.00','total_net'=>'90.00','created_by'=>$actor->id,
            'approved_by'=>$actor->id,'approved_at'=>'2026-08-10 08:00:00','paid_from_account_id'=>$account->id,'paid_by'=>$actor->id,'paid_at'=>'2026-08-10 09:00:00',
        ]);
        PayrollEntry::create([
            'payroll_period_id'=>$period->id,'branch_id'=>$branch->id,'user_id'=>$employee->id,'payslip_number'=>'PSL-REPORT-001',
            'employee_name_snapshot'=>$employee->name,'basic_salary'=>'100.00','gross'=>'100.00','total_deductions'=>'10.00','net'=>'90.00',
            'deductions'=>'10.00','status'=>'paid','financial_account_id'=>$account->id,'paid_by'=>$actor->id,'paid_at'=>'2026-08-10 09:00:00',
        ]);

        $service = app(FinancialReportService::class);
        $capital = $service->generate('capital', [$branch->id], '2026-08-01', '2026-08-31');
        $expenses = $service->generate('expenses', [$branch->id], '2026-08-01', '2026-08-31');
        $payroll = $service->generate('payroll', [$branch->id], '2026-08-01', '2026-08-31');

        $this->assertSame('500.00', $capital['total_capital']);
        $this->assertSame('75.00', $expenses['total_expenses']);
        $this->assertSame('100.00', $payroll['total_gross']);
        $this->assertSame('10.00', $payroll['total_deductions']);
        $this->assertSame('90.00', $payroll['total_net']);
    }

    public function test_collection_and_disbursement_reports_reconcile_channels_and_transaction_types(): void
    {
        [$branch, $cash] = $this->fixture('movement');
        $mobile = FinancialAccount::create(['branch_id'=>$branch->id,'name'=>'Mobile Money','account_type'=>'mobile_money','opening_balance'=>'0.00','current_balance'=>'0.00','status'=>'active']);
        $bank = FinancialAccount::create(['branch_id'=>$branch->id,'name'=>'Bank','account_type'=>'bank','opening_balance'=>'0.00','current_balance'=>'0.00','status'=>'active']);
        $this->transaction($branch, $cash, '2026-08-02 08:00:00', 'loan_payment', '100.00', '0.00');
        $this->transaction($branch, $mobile, '2026-08-03 08:00:00', 'loan_early_settlement', '200.00', '0.00');
        $this->transaction($branch, $bank, '2026-08-04 08:00:00', 'loan_write_off_recovery', '50.00', '0.00');
        $this->transaction($branch, $bank, '2026-08-05 08:00:00', 'loan_disbursement', '0.00', '300.00');
        $this->transaction($branch, $cash, '2026-08-06 08:00:00', 'loan_top_up', '0.00', '25.00');

        $service = app(FinancialReportService::class);
        $collections = $service->generate('collections', [$branch->id], '2026-08-01', '2026-08-31');
        $disbursements = $service->generate('disbursements', [$branch->id], '2026-08-01', '2026-08-31');

        $this->assertSame('350.00', $collections['total_collections']);
        $this->assertCount(3, $collections['channels']);
        $this->assertSame('325.00', $disbursements['total_disbursements']);
        $this->assertCount(2, $disbursements['by_type']);
    }

    public function test_date_multi_branch_and_company_scopes_are_exact_and_branch_access_is_enforced(): void
    {
        [$branchA] = $this->fixture('scope-a');
        [$branchB] = $this->fixture('scope-b');
        $this->entry($branchA, 'interest', '0.00', '10.00', 'scope-a-current', '2026-08-05');
        $this->entry($branchA, 'interest', '0.00', '90.00', 'scope-a-old', '2026-07-05');
        $this->entry($branchB, 'interest', '0.00', '20.00', 'scope-b-current', '2026-08-05');

        $service = app(FinancialReportService::class);
        $single = $service->generate('profit-loss', [$branchA->id], '2026-08-01', '2026-08-31');
        $multiple = $service->generate('profit-loss', [$branchA->id, $branchB->id], '2026-08-01', '2026-08-31');
        $this->assertSame('10.00', $single['total_income']);
        $this->assertSame('30.00', $multiple['total_income']);

        $auditorA = $this->staff('auditor', $branchA, 'reports_auditor_a');
        $this->actingAs($auditorA)->get(route('reports.index', ['report'=>'profit-loss','branch_ids'=>[$branchB->id],'from'=>'2026-08-01','to'=>'2026-08-31']))->assertForbidden();
        $this->actingAs($auditorA)->get(route('reports.index', ['report'=>'profit-loss','from'=>'2026-08-01','to'=>'2026-08-31']))->assertOk()->assertSee('10.00')->assertDontSee('30.00');
    }

    public function test_report_routes_navigation_permission_and_all_eight_reports_render(): void
    {
        [$branch] = $this->fixture('ui');
        $auditor = $this->staff('auditor', $branch, 'reports_ui_auditor');
        foreach (array_keys(FinancialReportService::REPORTS) as $slug) {
            $response = $this->actingAs($auditor)->get(route('reports.index', ['report'=>$slug,'from'=>'2026-08-01','to'=>'2026-08-31']));
            $response->assertOk()->assertSee(FinancialReportService::REPORTS[$slug]);
        }
        $this->actingAs($auditor)->get(route('reports.index'))->assertOk()->assertSee('Financial Reports')->assertSee('Profit &amp; Loss', false);

        $cashier = $this->staff('cashier', $branch, 'reports_ui_cashier');
        $this->actingAs($cashier)->get(route('reports.index'))->assertForbidden();
    }

    private function fixture(string $tag): array
    {
        $business = Business::firstOrFail();
        $branch = Branch::create(['business_id'=>$business->id,'name'=>'Reports '.str($tag)->headline(),'status'=>'active']);
        $account = FinancialAccount::create([
            'branch_id'=>$branch->id,'name'=>'Cash '.str($tag)->headline(),'account_type'=>'cash',
            'opening_balance'=>'0.00','current_balance'=>'0.00','status'=>'active',
        ]);
        return [$branch, $account, $business];
    }

    private function entry(Branch $branch, string $category, string $debit, string $credit, string $tag, string $date = '2026-08-05'): AccountingEntry
    {
        $sequence = $this->systemWide(fn () => AccountingEntry::count());
        return AccountingEntry::create([
            'journal_number'=>'JRN-REPORT-'.strtoupper(str_replace('_','-', $tag)).'-'.$sequence,
            'branch_id'=>$branch->id,'entry_date'=>$date,'reference'=>'REF-'.$tag,'category'=>$category,'event_type'=>'report_test',
            'description'=>'Report test '.$tag,'debit'=>$debit,'credit'=>$credit,'user_id'=>null,
        ]);
    }

    private function transaction(Branch $branch, FinancialAccount $account, string $date, string $type, string $moneyIn, string $moneyOut): FinancialAccountTransaction
    {
        $sequence = $this->systemWide(fn () => FinancialAccountTransaction::count()) + 1;
        $running = (float) $account->current_balance + (float) $moneyIn - (float) $moneyOut;
        $transaction = FinancialAccountTransaction::create([
            'transaction_number'=>'FTX-REPORT-'.str_pad((string)$sequence,6,'0',STR_PAD_LEFT),
            'account_id'=>$account->id,'branch_id'=>$branch->id,'transaction_date'=>$date,'reference'=>'REPORT-'.$sequence,
            'type'=>$type,'description'=>'Report test '.$type,'money_in'=>$moneyIn,'money_out'=>$moneyOut,
            'running_balance'=>number_format($running,2,'.',''),'user_id'=>null,
        ]);
        $account->update(['current_balance'=>number_format($running,2,'.','')]);
        return $transaction;
    }

    private function staff(string $roleName, Branch $branch, string $username): User
    {
        $user = User::factory()->create(['name'=>str($username)->headline()->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()->id);
        return $user;
    }
}
