<?php

namespace Tests\Feature;

use App\Models\AccountingEntry;
use App\Models\Branch;
use App\Models\Business;
use App\Models\FinancialAccount;
use App\Models\FixedAsset;
use App\Models\Loan;
use App\Models\LoanProduct;
use App\Models\Setting;
use App\Models\User;
use App\Services\AdvancedFinancialReportService;
use App\Services\FinancialAccountingService;
use App\Services\FixedAssetService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;

class FixedAssetsAndFinancialReportsModuleTest extends TestCase
{
    use RefreshDatabase;

    protected User $admin;
    protected Business $business;
    protected Branch $branch;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed(DatabaseSeeder::class);
        $this->admin = User::where('username', 'admin')->firstOrFail();
        $this->business = Business::firstOrFail();
        $this->branch = Branch::create([
            'business_id' => $this->business->id,
            'name' => 'Asset Test Branch',
            'location' => 'Dodoma',
            'status' => 'active',
        ]);
    }

    public function test_system_settings_can_disable_modules_without_deleting_data(): void
    {
        $asset = $this->openingAsset();

        $this->actingAs($this->admin)->get(route('admin.system-settings.edit'))
            ->assertOk()
            ->assertSee('Modules')
            ->assertSee('Fixed Assets')
            ->assertSee('Financial Reports');

        $this->actingAs($this->admin)->put(route('admin.system-settings.update'), [
            'timezone' => 'Africa/Dar_es_Salaam',
            'date_format' => 'd/m/Y',
            'session_timeout_minutes' => 120,
            'require_2fa' => 0,
            'modules_present' => 1,
            'modules' => ['financial_reports' => 1],
        ])->assertRedirect();

        $this->assertSame('0', Setting::where('group', 'modules')->where('key', 'fixed_assets')->value('value'));
        $this->assertSame('1', Setting::where('group', 'modules')->where('key', 'financial_reports')->value('value'));
        $this->assertDatabaseHas('fixed_assets', ['id' => $asset->id, 'asset_number' => $asset->asset_number]);

        $this->actingAs($this->admin)->get(route('fixed-assets.index'))->assertNotFound();
        $this->actingAs($this->admin)->get(route('reports.index', ['report' => 'trial-balance']))->assertOk();
    }

    public function test_legacy_system_settings_update_preserves_existing_module_switches(): void
    {
        Setting::updateOrCreate(
            ['business_id' => $this->business->id, 'group' => 'modules', 'key' => 'fixed_assets'],
            ['value' => '0', 'type' => 'boolean'],
        );

        $this->actingAs($this->admin)->put(route('admin.system-settings.update'), [
            'timezone' => 'Africa/Dar_es_Salaam',
            'date_format' => 'Y-m-d',
            'session_timeout_minutes' => 90,
            'require_2fa' => 0,
        ])->assertRedirect();

        $this->assertSame('0', Setting::where('group', 'modules')->where('key', 'fixed_assets')->value('value'));
    }

    public function test_purchased_asset_reduces_account_and_posts_balanced_acquisition(): void
    {
        $account = app(FinancialAccountingService::class)->createAccount([
            'branch_id' => $this->branch->id,
            'name' => 'Asset Purchase Bank',
            'account_type' => 'bank',
            'opening_balance' => '5000.00',
            'status' => 'active',
        ], $this->admin);

        $asset = app(FixedAssetService::class)->create([
            'branch_id' => $this->branch->id,
            'name' => 'Office Laptop',
            'category' => 'Computer Equipment',
            'acquisition_source' => 'financial_account',
            'acquisition_date' => '2026-08-10',
            'acquisition_cost' => '1200.00',
            'residual_value' => '0.00',
            'depreciation_method' => 'straight_line',
            'useful_life_months' => 12,
            'depreciation_start_date' => '2026-08-10',
            'financial_account_id' => $account->id,
            'status' => 'active',
        ], $this->admin);

        $this->assertSame('3800.00', $account->fresh()->current_balance);
        $this->assertSame('1200.00', $asset->book_value);
        $this->assertDatabaseHas('financial_account_transactions', [
            'account_id' => $account->id,
            'type' => 'fixed_asset_purchase',
            'money_out' => '1200.00',
            'related_id' => $asset->id,
        ]);

        $journal = $this->inBranch((int) $this->branch->id, fn () => AccountingEntry::where('related_type', FixedAsset::class)->where('related_id', $asset->id)->get());
        $this->assertSame(1200.0, (float) $journal->sum('debit'));
        $this->assertSame(1200.0, (float) $journal->sum('credit'));
        $this->assertTrue($journal->contains(fn ($row) => $row->category === 'fixed_assets' && (float) $row->debit === 1200.0));
        $this->assertTrue($journal->contains(fn ($row) => $row->category === 'assets' && (float) $row->credit === 1200.0));
    }

    public function test_opening_asset_uses_capital_instead_of_false_income(): void
    {
        $asset = $this->openingAsset();
        $entries = $this->inBranch((int) $this->branch->id, fn () => AccountingEntry::where('related_type', FixedAsset::class)->where('related_id', $asset->id)->get());

        $this->assertTrue($entries->contains(fn ($row) => $row->category === 'fixed_assets' && (float) $row->debit === 1200.0));
        $this->assertTrue($entries->contains(fn ($row) => $row->category === 'capital' && (float) $row->credit === 1200.0));
        $this->assertFalse($entries->contains(fn ($row) => in_array($row->category, ['interest','fees','penalties','service_income','other_income'], true)));
    }

    public function test_straight_line_depreciation_is_exact_and_cannot_duplicate_a_month(): void
    {
        $asset = $this->openingAsset();
        $service = app(FixedAssetService::class);

        $posting = $service->postDepreciation($asset, '2026-08', 'August depreciation', $this->admin);
        $this->assertSame('100.00', $posting->amount);
        $this->assertSame('100.00', $asset->fresh()->accumulated_depreciation);
        $this->assertSame('1100.00', $asset->fresh()->book_value);
        $this->assertDatabaseHas('accounting_entries', ['journal_number' => $posting->journal_number, 'category' => 'depreciation_expense', 'debit' => '100.00']);
        $this->assertDatabaseHas('accounting_entries', ['journal_number' => $posting->journal_number, 'category' => 'accumulated_depreciation', 'credit' => '100.00']);

        $this->expectException(ValidationException::class);
        $service->postDepreciation($asset->fresh(), '2026-08', null, $this->admin);
    }

    public function test_paid_maintenance_reduces_cash_and_posts_maintenance_expense(): void
    {
        $asset = $this->openingAsset();
        $account = app(FinancialAccountingService::class)->createAccount([
            'branch_id' => $this->branch->id,
            'name' => 'Maintenance Cash',
            'account_type' => 'cash',
            'opening_balance' => '500.00',
            'status' => 'active',
        ], $this->admin);

        $maintenance = app(FixedAssetService::class)->recordMaintenance($asset, [
            'maintenance_date' => '2026-08-12',
            'maintenance_type' => 'service',
            'description' => 'Annual service',
            'supplier' => 'Dodoma Tech',
            'cost' => '50.00',
            'financial_account_id' => $account->id,
            'reference' => 'SRV-001',
            'next_due_date' => '2027-08-12',
        ], $this->admin);

        $this->assertSame('450.00', $account->fresh()->current_balance);
        $this->assertSame('2027-08-12', $asset->fresh()->next_maintenance_date->toDateString());
        $this->assertDatabaseHas('financial_account_transactions', ['type' => 'fixed_asset_maintenance', 'money_out' => '50.00', 'related_id' => $maintenance->id]);
        $this->assertDatabaseHas('accounting_entries', ['journal_number' => $maintenance->journal_number, 'category' => 'asset_maintenance', 'debit' => '50.00']);
        $this->assertDatabaseHas('accounting_entries', ['journal_number' => $maintenance->journal_number, 'category' => 'assets', 'credit' => '50.00']);
    }

    public function test_trial_balance_and_financial_position_reconcile_fixed_assets_depreciation_and_maintenance(): void
    {
        $account = app(FinancialAccountingService::class)->createAccount([
            'branch_id' => $this->branch->id,
            'name' => 'Main Bank',
            'account_type' => 'bank',
            'opening_balance' => '5000.00',
            'status' => 'active',
        ], $this->admin);
        $asset = app(FixedAssetService::class)->create([
            'branch_id' => $this->branch->id,
            'name' => 'Finance Laptop',
            'category' => 'Computer Equipment',
            'acquisition_source' => 'financial_account',
            'acquisition_date' => '2026-08-10',
            'acquisition_cost' => '1200.00',
            'residual_value' => '0.00',
            'depreciation_method' => 'straight_line',
            'useful_life_months' => 12,
            'depreciation_start_date' => '2026-08-10',
            'financial_account_id' => $account->id,
            'status' => 'active',
        ], $this->admin);
        app(FixedAssetService::class)->postDepreciation($asset, '2026-08', null, $this->admin);
        app(FixedAssetService::class)->recordMaintenance($asset->fresh(), [
            'maintenance_date' => '2026-08-12',
            'maintenance_type' => 'repair',
            'description' => 'Keyboard repair',
            'cost' => '50.00',
            'financial_account_id' => $account->id,
        ], $this->admin);

        $reports = app(AdvancedFinancialReportService::class);
        $trial = $reports->generate('trial-balance', [$this->branch->id], '2026-08-01', '2026-08-31');
        $this->assertTrue($trial['balanced']);
        $this->assertSame('0.00', $trial['difference']);

        $income = $reports->generate('income-statement', [$this->branch->id], '2026-08-01', '2026-08-31');
        $this->assertSame('150.00', $income['total_expenses']);
        $this->assertSame('-150.00', $income['net_profit']);

        $position = $reports->generate('statement-of-financial-position', [$this->branch->id], '2026-08-01', '2026-08-31');
        $this->assertTrue($position['balanced']);
        $this->assertSame('4850.00', $position['total_assets']);
        $this->assertSame('4850.00', $position['total_equity']);
    }

    public function test_portfolio_summary_uses_current_loan_balance_snapshot(): void
    {
        $product = LoanProduct::create([
            'business_id' => $this->business->id,
            'name' => 'Portfolio Test',
            'minimum_amount' => '100.00','maximum_amount' => '10000.00','duration' => 1,'duration_unit' => 'months',
            'interest_rate' => '0.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',
            'processing_fee_payment_mode' => 'financed','insurance_fee_type' => 'fixed','insurance_fee_value' => '0.000000','other_charges' => '0.00',
            'collateral_required' => false,'guarantor_required' => false,'status' => 'active',
        ]);
        $customerId = \DB::table('customers')->insertGetId([
            'branch_id' => $this->branch->id,'customer_number' => 'CUS-PORT-1','identity_mode' => 'without_nin','first_name' => 'Portfolio','last_name' => 'Customer',
            'phone' => '0712345000','status' => 'active','created_at' => now(),'updated_at' => now(),
        ]);
        Loan::create([
            'branch_id' => $this->branch->id,'customer_id' => $customerId,'loan_product_id' => $product->id,'sequence_number' => 1,'sequence_year' => 2026,
            'loan_number' => 'LN-PORT-2026-000001','principal' => '1000.00','interest' => '100.00','processing_fee' => '0.00','processing_fee_payment_mode' => 'financed',
            'insurance_fee' => '0.00','other_charges' => '0.00','fees' => '0.00','total_repayment' => '1100.00','principal_outstanding' => '800.00',
            'interest_outstanding' => '80.00','fees_outstanding' => '0.00','penalties_outstanding' => '20.00','total_outstanding' => '900.00','total_paid' => '200.00',
            'overdue_principal' => '200.00','overdue_interest' => '20.00','overdue_fees' => '0.00','overdue_penalties' => '5.00','total_overdue' => '225.00','days_overdue' => 5,
            'purpose' => 'Stock','schedule_start_date' => '2026-08-01','status' => 'active','created_by' => $this->admin->id,'updated_by' => $this->admin->id,
        ]);

        $data = app(AdvancedFinancialReportService::class)->generate('portfolio-summary', [$this->branch->id], '2026-08-01', '2026-08-31');
        $this->assertSame(1, $data['active_loans']);
        $this->assertSame(1, $data['overdue_loans']);
        $this->assertSame('800.00', $data['principal_outstanding']);
        $this->assertSame('900.00', $data['total_outstanding']);
        $this->assertSame('225.00', $data['total_overdue']);
        $this->assertSame('25.00', $data['portfolio_at_risk_percent']);
    }

    private function openingAsset(): FixedAsset
    {
        return app(FixedAssetService::class)->create([
            'branch_id' => $this->branch->id,
            'name' => 'Opening Office Desk',
            'category' => 'Furniture',
            'acquisition_source' => 'opening',
            'acquisition_date' => '2026-08-01',
            'acquisition_cost' => '1200.00',
            'residual_value' => '0.00',
            'depreciation_method' => 'straight_line',
            'useful_life_months' => 12,
            'depreciation_start_date' => '2026-08-01',
            'status' => 'active',
        ], $this->admin);
    }
}
