<?php

namespace Tests\Feature;

use App\Models\AccountingEntry;
use App\Models\Branch;
use App\Models\Business;
use App\Models\CapitalInjection;
use App\Models\FinancialAccount;
use App\Models\FinancialAccountTransaction;
use App\Models\FinancialTransfer;
use App\Models\Role;
use App\Models\User;
use App\Services\FinancialAccountingService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class FinancialAccountingFoundationTest extends TestCase
{
    use RefreshDatabase;

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

    public function test_account_opening_balance_posts_asset_and_capital_without_income(): void
    {
        [$admin,$branch]=$this->adminAndBranch();
        $response=$this->actingAs($admin)->post(route('accounts.store'),[
            'branch_id'=>$branch->id,'name'=>'Main Cash','account_type'=>'cash','opening_balance'=>'1000000.00','status'=>'active','reference_details'=>'Front office till',
        ]);
        $account=FinancialAccount::where('name','Main Cash')->firstOrFail();
        $response->assertRedirect(route('accounts.show',$account));
        $this->assertSame('1000000.00',$account->opening_balance);
        $this->assertSame('1000000.00',$account->current_balance);
        $this->assertDatabaseHas('financial_account_transactions',['account_id'=>$account->id,'type'=>'opening_balance','money_in'=>1000000,'money_out'=>0,'running_balance'=>1000000,'related_type'=>FinancialAccount::class,'related_id'=>$account->id]);
        $this->assertDatabaseHas('accounting_entries',['financial_account_id'=>$account->id,'category'=>'assets','event_type'=>'opening_balance','debit'=>1000000,'credit'=>0,'related_type'=>FinancialAccount::class,'related_id'=>$account->id]);
        $this->assertDatabaseHas('accounting_entries',['branch_id'=>$branch->id,'category'=>'capital','event_type'=>'opening_balance','debit'=>0,'credit'=>1000000,'related_type'=>FinancialAccount::class,'related_id'=>$account->id]);
        $this->assertSame(0,AccountingEntry::whereIn('category',['interest','fees','penalties'])->count());
        $this->assertDatabaseHas('audit_logs',['action'=>'financial_account.created','auditable_id'=>$account->id]);
    }

    public function test_capital_injection_increases_funds_and_capital_not_income(): void
    {
        [$admin,$branch]=$this->adminAndBranch();
        $account=$this->createAccount($admin,$branch,'Bank','bank','250000.00');
        $this->actingAs($admin)->post(route('capital-injections.store'),[
            'account_id'=>$account->id,'amount'=>'750000.00','date'=>'2026-08-12','reference'=>'OWNER-CAP-001','notes'=>'Owner capital top-up',
        ])->assertRedirect(route('accounts.show',$account));
        $capital=CapitalInjection::firstOrFail();
        $this->assertMatchesRegularExpression('/^CAP-[A-Z0-9]+$/',$capital->capital_number);
        $this->assertSame($account->id,$capital->account_id);
        $this->assertSame('750000.00',$capital->amount);
        $this->assertSame('1000000.00',$account->fresh()->current_balance);
        $this->assertDatabaseHas('financial_account_transactions',['account_id'=>$account->id,'type'=>'capital_injection','reference'=>'OWNER-CAP-001','money_in'=>750000,'running_balance'=>1000000,'related_type'=>CapitalInjection::class,'related_id'=>$capital->id]);
        $this->assertDatabaseHas('accounting_entries',['category'=>'assets','event_type'=>'capital_injection','debit'=>750000,'credit'=>0,'related_type'=>CapitalInjection::class,'related_id'=>$capital->id]);
        $this->assertDatabaseHas('accounting_entries',['category'=>'capital','event_type'=>'capital_injection','debit'=>0,'credit'=>750000,'related_type'=>CapitalInjection::class,'related_id'=>$capital->id]);
        $this->assertSame(0,AccountingEntry::where('event_type','capital_injection')->whereIn('category',['interest','fees','penalties'])->count());
        $this->assertDatabaseHas('audit_logs',['action'=>'capital.injected','auditable_id'=>$capital->id]);
    }

    public function test_transfer_moves_balances_and_preserves_total_company_funds(): void
    {
        [$admin,$branch]=$this->adminAndBranch();
        $source=$this->createAccount($admin,$branch,'M-Pesa','mobile_money','1000000.00');
        $destination=$this->createAccount($admin,$branch,'CRDB','bank','200000.00');
        $before=$this->minorTotal([$source,$destination]);

        $this->actingAs($admin)->post(route('account-transfers.store'),[
            'source_account_id'=>$source->id,'destination_account_id'=>$destination->id,'amount'=>'300000.00','date'=>'2026-08-12','reference'=>'MOVE-001','notes'=>'Move collections to bank',
        ])->assertRedirect(route('accounts.show',$source));

        $this->assertSame('700000.00',$source->fresh()->current_balance);
        $this->assertSame('500000.00',$destination->fresh()->current_balance);
        $this->assertSame($before,$this->minorTotal([$source->fresh(),$destination->fresh()]));
        $transfer=FinancialTransfer::firstOrFail();
        $this->assertSame('300000.00',$transfer->amount);
        $this->assertSame(2,FinancialAccountTransaction::where('transfer_id',$transfer->id)->count());
        $entries=AccountingEntry::where('event_type','account_transfer')->where('related_id',$transfer->id)->get();
        $this->assertSame(2,$entries->count());
        $this->assertSame('300000.00',number_format((float)$entries->sum('debit'),2,'.',''));
        $this->assertSame('300000.00',number_format((float)$entries->sum('credit'),2,'.',''));
        $this->assertTrue($entries->every(fn($entry)=>$entry->category==='assets'));
        $this->assertDatabaseHas('audit_logs',['action'=>'financial_transfer.posted','auditable_id'=>$transfer->id]);
    }

    public function test_insufficient_transfer_is_atomic_and_does_not_partially_post(): void
    {
        [$admin,$branch]=$this->adminAndBranch();
        $source=$this->createAccount($admin,$branch,'Till','cash','100.00');
        $destination=$this->createAccount($admin,$branch,'Safe','safe','50.00');
        $transactionCount=$this->systemWide(fn () => FinancialAccountTransaction::count());
        $entryCount=$this->systemWide(fn () => AccountingEntry::count());

        $this->actingAs($admin)->from(route('account-transfers.create'))->post(route('account-transfers.store'),[
            'source_account_id'=>$source->id,'destination_account_id'=>$destination->id,'amount'=>'150.00','date'=>'2026-08-12',
        ])->assertRedirect(route('account-transfers.create'))->assertSessionHasErrors('amount');

        $this->assertSame('100.00',$source->fresh()->current_balance);
        $this->assertSame('50.00',$destination->fresh()->current_balance);
        $this->assertSame(0,FinancialTransfer::count());
        $this->assertSame($transactionCount,$this->systemWide(fn () => FinancialAccountTransaction::count()));
        $this->assertSame($entryCount,$this->systemWide(fn () => AccountingEntry::count()));
    }

    public function test_financial_pages_and_operations_are_branch_and_permission_scoped(): void
    {
        [$admin,$branchA]=$this->adminAndBranch();
        $business=Business::firstOrFail();
        $branchB=Branch::create(['business_id'=>$business->id,'name'=>'Arusha Finance','status'=>'active']);
        $accountA=$this->createAccount($admin,$branchA,'Dodoma Cash','cash','1000.00');
        $accountB=$this->createAccount($admin,$branchB,'Arusha Cash','cash','1000.00');

        $accountant=User::factory()->create(['name'=>'Branch Accountant','username'=>'branch_accountant','status'=>'active','primary_branch_id'=>$branchA->id]);
        $accountant->roles()->attach(Role::where('name','accountant')->firstOrFail());
        $accountant->branches()->attach($branchA->id,['is_primary'=>true]);

        $this->actingAs($accountant)->get(route('accounts.index'))->assertOk()->assertSee('Dodoma Cash')->assertDontSee('Arusha Cash');
        $this->actingAs($accountant)->get(route('accounts.show',$accountB))->assertForbidden();
        $this->actingAs($accountant)->get(route('accounting-ledger.index'))->assertOk();
        $this->actingAs($accountant)->post(route('account-transfers.store'),[
            'source_account_id'=>$accountA->id,'destination_account_id'=>$accountB->id,'amount'=>'10.00','date'=>'2026-08-12',
        ])->assertForbidden();
    }

    public function test_step_seven_pages_use_standardized_table_and_form_patterns(): void
    {
        [$admin,$branch]=$this->adminAndBranch();
        $account=$this->createAccount($admin,$branch,'UI Cash','cash','0.00');
        $this->actingAs($admin)->get(route('accounts.index'))->assertOk()->assertSee('data-ui="page-toolbar"',false)->assertSee('data-ui="table-toolbar"',false);
        $this->actingAs($admin)->get(route('accounts.create'))->assertOk()->assertSee('data-ui="form-shell"',false)->assertSee('data-ui="form-section"',false);
        $this->actingAs($admin)->get(route('accounts.show',$account))->assertOk()->assertSee('Account Statement')->assertSee('Related Record');
        $this->actingAs($admin)->get(route('capital-injections.create'))->assertOk()->assertSee('data-ui="form-shell"',false);
        $this->actingAs($admin)->get(route('account-transfers.create'))->assertOk()->assertSee('data-ui="form-shell"',false);
        $this->actingAs($admin)->get(route('accounting-ledger.index'))->assertOk()->assertSee('data-ui="table-toolbar"',false);
    }

    private function adminAndBranch(): array
    {
        $admin=User::where('email','admin@microfinance.local')->firstOrFail();
        $business=Business::firstOrFail();
        $branch=Branch::firstOrCreate(['business_id'=>$business->id,'name'=>'Dodoma Finance'],['status'=>'active']);
        return [$admin,$branch];
    }

    private function createAccount(User $admin, Branch $branch, string $name, string $type, string $opening): FinancialAccount
    {
        return app(FinancialAccountingService::class)->createAccount(['branch_id'=>$branch->id,'name'=>$name,'account_type'=>$type,'opening_balance'=>$opening,'status'=>'active'],$admin);
    }

    private function minorTotal(array $accounts): int
    {
        return collect($accounts)->sum(fn(FinancialAccount $account)=>app(\App\Services\MoneyMath::class)->toMinor((string)$account->current_balance));
    }
}
