<?php

namespace Database\Seeders\Simulation;

use App\Models\Branch;
use App\Models\CapitalInjection;
use App\Models\DailyClosing;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\FinancialAccount;
use App\Models\FinancialTransfer;
use App\Models\FixedAsset;
use App\Models\FixedAssetDepreciation;
use App\Models\FixedAssetMaintenance;
use App\Models\PayrollPeriod;
use App\Models\RecurringExpense;
use App\Models\User;
use App\Services\DailyClosingService;
use App\Services\ExpenseService;
use App\Services\FinancialAccountingService;
use App\Services\FixedAssetService;
use App\Services\PayrollService;
use App\Services\RecurringExpenseService;
use App\Support\Simulation\SimulationOperationalFinanceExpectations;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class SimulationOperationalFinanceSeeder extends Seeder
{
    public function run(
        FinancialAccountingService $accounting,
        ExpenseService $expenses,
        RecurringExpenseService $recurringExpenses,
        PayrollService $payroll,
        FixedAssetService $fixedAssets,
        DailyClosingService $closing,
    ): void {
        $asOf = CarbonImmutable::parse(
            (string) config('simulation.as_of_date', SimulationOperationalFinanceExpectations::AS_OF_DATE)
        )->setTime(16, 0);
        Carbon::setTestNow($asOf);

        try {
            DB::transaction(function () use ($accounting,$expenses,$recurringExpenses,$payroll,$fixedAssets,$closing,$asOf): void {
                $this->seedCapitalAndTransfer($accounting);
                $this->seedExpenses($expenses,$recurringExpenses,$asOf);
                $this->seedPayroll($payroll);
                $this->seedFixedAsset($fixedAssets);
                $this->seedDarClosing($closing,$asOf);
            });
        } finally {
            Carbon::setTestNow();
        }
    }

    private function seedCapitalAndTransfer(FinancialAccountingService $accounting): void
    {
        $accountant = $this->user('sim.dar.accountant');
        $darBank = $this->account('SIM Dar Bank');
        $darCash = $this->account('SIM Dar Cash');

        if (! CapitalInjection::query()->where('reference','SIM-OP-CAPITAL')->exists()) {
            $accounting->injectCapital(
                $darBank,
                SimulationOperationalFinanceExpectations::aggregateControls()['capital_injection'],
                CarbonImmutable::parse('2026-08-05'),
                'SIM-OP-CAPITAL',
                'Simulation operating-capital injection.',
                $accountant,
            );
        }

        if (! FinancialTransfer::query()->where('reference','SIM-OP-TRANSFER')->exists()) {
            $accounting->transfer(
                $darBank->fresh(),
                $darCash->fresh(),
                SimulationOperationalFinanceExpectations::aggregateControls()['internal_transfer'],
                CarbonImmutable::parse('2026-08-06'),
                'SIM-OP-TRANSFER',
                'Simulation bank-to-cash working-funds transfer.',
                $accountant,
            );
        }
    }

    private function seedExpenses(
        ExpenseService $expenses,
        RecurringExpenseService $recurringExpenses,
        CarbonImmutable $asOf,
    ): void {
        $branch = $this->branch('SIM-DAR');
        $category = ExpenseCategory::query()
            ->where('business_id',$branch->business_id)
            ->where('name','Internet')
            ->firstOrFail();
        $cash = $this->account('SIM Dar Cash');
        $manager = $this->user('sim.dar.manager');
        $accountant = $this->user('sim.dar.accountant');
        $controls = SimulationOperationalFinanceExpectations::aggregateControls();

        $approved = Expense::query()->where('description','SIM-OP-APPROVED-EXPENSE')->first();
        if (! $approved) {
            $approved = $expenses->createPending([
                'branch_id'=>$branch->id,
                'expense_category_id'=>$category->id,
                'financial_account_id'=>$cash->id,
                'description'=>'SIM-OP-APPROVED-EXPENSE',
                'amount'=>$controls['approved_expense'],
                'expense_date'=>CarbonImmutable::parse('2026-08-07'),
                'supplier'=>'Simulation ISP',
                'notes'=>'Deterministic approved operating expense.',
            ],$manager);
        }
        if ($approved->status === 'pending') {
            $expenses->approve($approved,$accountant,'Simulation approved-expense control.');
        }

        $rejected = Expense::query()->where('description','SIM-OP-REJECTED-EXPENSE')->first();
        if (! $rejected) {
            $rejected = $expenses->createPending([
                'branch_id'=>$branch->id,
                'expense_category_id'=>$category->id,
                'financial_account_id'=>$cash->id,
                'description'=>'SIM-OP-REJECTED-EXPENSE',
                'amount'=>$controls['rejected_expense'],
                'expense_date'=>CarbonImmutable::parse('2026-08-08'),
                'supplier'=>'Simulation ISP',
                'notes'=>'Deterministic rejected expense.',
            ],$manager);
        }
        if ($rejected->status === 'pending') {
            $expenses->reject($rejected,$accountant,'Simulation rejection control: duplicate quotation.');
        }

        $schedule = RecurringExpense::query()->where('description','SIM-OP-RECURRING-INTERNET')->first();
        if (! $schedule) {
            $schedule = RecurringExpense::create([
                'business_id'=>$branch->business_id,
                'branch_id'=>$branch->id,
                'expense_category_id'=>$category->id,
                'financial_account_id'=>$cash->id,
                'description'=>'SIM-OP-RECURRING-INTERNET',
                'amount'=>$controls['recurring_pending_expense'],
                'supplier'=>'Simulation ISP',
                'start_date'=>'2026-08-15',
                'next_due_date'=>'2026-08-15',
                'end_date'=>null,
                'recurrence'=>'monthly',
                'interval_days'=>null,
                'reminder_days_before'=>3,
                'status'=>'active',
                'notes'=>'Simulation recurring-expense generation control.',
                'created_by'=>$manager->id,
                'updated_by'=>$manager->id,
            ]);
        }

        $recurringExpenses->process($asOf->toDateString());
    }

    private function seedPayroll(PayrollService $payroll): void
    {
        $hr = $this->user('sim.hr');
        $manager = $this->user('sim.dar.manager');
        $accountant = $this->user('sim.dar.accountant');
        $branch = $this->branch('SIM-DAR');

        foreach (SimulationOperationalFinanceExpectations::payrollCompensation() as $username=>$components) {
            $payroll->configureCompensation(
                $this->user($username),
                $components + [
                    'allowances'=>'0.00','bonus'=>'0.00','commission'=>'0.00','overtime'=>'0.00',
                    'deductions'=>'0.00','advances'=>'0.00','other_deductions'=>'0.00',
                    'effective_from'=>'2026-08-01','status'=>'active','notes'=>'Simulation payroll control.',
                ],
                $hr,
            );
        }

        $period = PayrollPeriod::query()->where('name','SIM August 2026 Payroll')->first();
        if (! $period) {
            $period = $payroll->createPeriod([
                'branch_id'=>$branch->id,
                'name'=>'SIM August 2026 Payroll',
                'start_date'=>'2026-08-01',
                'end_date'=>'2026-08-31',
                'pay_date'=>'2026-08-15',
            ],$hr);
        }

        if ($period->status === 'draft') {
            $period = $payroll->approve($period,$manager,'Simulation payroll approved independently.');
        }
        if ($period->status === 'approved') {
            $payroll->pay($period,$this->account('SIM Dar Bank'),$accountant);
        }
    }

    private function seedFixedAsset(FixedAssetService $fixedAssets): void
    {
        $manager = $this->user('sim.arusha.manager');
        $bank = $this->account('SIM Arusha Bank');
        $cash = $this->account('SIM Arusha Cash');
        $branch = $this->branch('SIM-ARU');
        $controls = SimulationOperationalFinanceExpectations::aggregateControls();

        $asset = FixedAsset::query()->where('name','SIM Arusha Office Equipment')->first();
        if (! $asset) {
            $asset = $fixedAssets->create([
                'branch_id'=>$branch->id,
                'name'=>'SIM Arusha Office Equipment',
                'category'=>'office_equipment',
                'description'=>'Deterministic fixed-asset purchase for report validation.',
                'serial_number'=>'SIM-AST-ARU-001',
                'supplier'=>'Simulation Office Supplies',
                'purchase_reference'=>'SIM-OP-ASSET-PURCHASE',
                'acquisition_source'=>'financial_account',
                'acquisition_date'=>'2026-08-10',
                'depreciation_start_date'=>'2026-08-01',
                'acquisition_cost'=>$controls['fixed_asset_purchase'],
                'residual_value'=>'100000.00',
                'depreciation_method'=>'straight_line',
                'useful_life_months'=>24,
                'financial_account_id'=>$bank->id,
                'location'=>'Simulation Arusha branch office',
                'custodian_user_id'=>$manager->id,
                'status'=>'active',
                'next_maintenance_date'=>'2026-08-15',
                'notes'=>'Simulation fixed-asset control.',
            ],$manager);
        }

        if (! FixedAssetDepreciation::query()->where('fixed_asset_id',$asset->id)->whereDate('period_date','2026-08-01')->exists()) {
            $fixedAssets->postDepreciation($asset,'2026-08','Simulation August depreciation control.',$manager);
        }

        if (! FixedAssetMaintenance::query()->where('fixed_asset_id',$asset->id)->where('reference','SIM-OP-MAINTENANCE')->exists()) {
            $fixedAssets->recordMaintenance($asset,[
                'maintenance_date'=>'2026-08-15',
                'maintenance_type'=>'preventive',
                'description'=>'Simulation preventive maintenance.',
                'supplier'=>'Simulation Technical Services',
                'cost'=>$controls['fixed_asset_maintenance'],
                'financial_account_id'=>$cash->id,
                'reference'=>'SIM-OP-MAINTENANCE',
                'next_due_date'=>'2026-11-15',
                'notes'=>'Simulation maintenance-cost control.',
            ],$manager);
        }
    }

    private function seedDarClosing(DailyClosingService $closing, CarbonImmutable $asOf): void
    {
        $branch = $this->branch('SIM-DAR');
        $day = $asOf->toDateString();
        $existing = DailyClosing::query()->where('branch_id',$branch->id)->whereDate('closing_date',$day)->first();
        if ($existing?->status === 'confirmed') return;

        $accountant = $this->user('sim.dar.accountant');
        foreach (FinancialAccount::query()
            ->where('branch_id',$branch->id)
            ->whereIn('account_type',DailyClosingService::CASH_ACCOUNT_TYPES)
            ->orderBy('id')
            ->get() as $account) {
            $closing->createReconciliation(
                $branch,
                $account,
                $day,
                (string) $account->current_balance,
                null,
                $accountant,
            );
        }

        $prepared = $closing->prepareOrRefresh($branch,$day,$accountant);
        $closing->confirm($prepared,$this->user('sim.dar.manager'),'Simulation manager confirmation after exact cash reconciliation.');
    }

    private function user(string $username): User
    {
        return User::query()->where('username',$username)->firstOrFail();
    }

    private function account(string $name): FinancialAccount
    {
        return FinancialAccount::query()->where('name',$name)->firstOrFail();
    }

    private function branch(string $code): Branch
    {
        return Branch::query()->where('code',$code)->firstOrFail();
    }
}
