<?php

namespace Database\Seeders\Simulation;

use App\Models\Branch;
use App\Models\Customer;
use App\Models\FinancialAccount;
use App\Models\Loan;
use App\Models\LoanProduct;
use App\Models\LoanWriteOffDocument;
use App\Models\User;
use App\Services\BranchContext;
use App\Services\LoanApplicationService;
use App\Services\LoanApprovalService;
use App\Services\LoanDisbursementService;
use App\Services\LoanEarlySettlementService;
use App\Services\LoanRestructuringService;
use App\Services\LoanTopUpService;
use App\Services\LoanWriteOffRecoveryService;
use App\Services\LoanWriteOffService;
use App\Support\Simulation\SimulationLoanAdjustmentExpectations;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

class SimulationLoanAdjustmentSeeder extends Seeder
{
    public function run(
        BranchContext $context,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        LoanTopUpService $topUps,
        LoanEarlySettlementService $settlements,
        LoanRestructuringService $restructurings,
        LoanWriteOffService $writeOffs,
        LoanWriteOffRecoveryService $recoveries,
    ): void {
        $asOf = CarbonImmutable::parse(
            (string) config('simulation.as_of_date', SimulationLoanAdjustmentExpectations::AS_OF_DATE)
        )->setTime(14, 0);
        Carbon::setTestNow($asOf);

        try {
            DB::transaction(function () use (
                $context,$applications,$approvals,$disbursements,$topUps,$settlements,$restructurings,$writeOffs,$recoveries,$asOf
            ): void {
                $scenarios = SimulationLoanAdjustmentExpectations::scenarios();

                $this->runBranchScenario($context, $scenarios['top_up']['branch'], function () use (
                    $scenarios,$applications,$approvals,$disbursements,$topUps,$asOf
                ): void {
                    $this->seedTopUp($scenarios['top_up'],$applications,$approvals,$disbursements,$topUps,$asOf);
                });

                $this->runBranchScenario($context, $scenarios['early_settlement']['branch'], function () use (
                    $scenarios,$applications,$approvals,$disbursements,$settlements,$asOf
                ): void {
                    $this->seedEarlySettlement($scenarios['early_settlement'],$applications,$approvals,$disbursements,$settlements,$asOf);
                });

                $this->runBranchScenario($context, $scenarios['restructure_approved']['branch'], function () use (
                    $scenarios,$applications,$approvals,$disbursements,$restructurings,$asOf
                ): void {
                    $this->seedApprovedRestructuring($scenarios['restructure_approved'],$applications,$approvals,$disbursements,$restructurings,$asOf);
                    $this->seedRejectedRestructuring($scenarios['restructure_rejected'],$applications,$approvals,$disbursements,$restructurings,$asOf);
                });

                $this->runBranchScenario($context, $scenarios['write_off_recovery']['branch'], function () use (
                    $scenarios,$applications,$approvals,$disbursements,$writeOffs,$recoveries,$asOf
                ): void {
                    $this->seedWriteOffRecovery($scenarios['write_off_recovery'],$applications,$approvals,$disbursements,$writeOffs,$recoveries,$asOf);
                });

                $this->runBranchScenario($context, $scenarios['write_off_rejected']['branch'], function () use (
                    $scenarios,$applications,$approvals,$disbursements,$writeOffs,$asOf
                ): void {
                    $this->seedRejectedWriteOff($scenarios['write_off_rejected'],$applications,$approvals,$disbursements,$writeOffs,$asOf);
                });
            });
        } finally {
            Carbon::setTestNow();
        }
    }

    private function seedTopUp(
        array $scenario,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        LoanTopUpService $topUps,
        CarbonImmutable $asOf,
    ): void {
        if (Loan::query()->where('purpose',$scenario['base_purpose'])->exists()) return;

        $base = $this->createActiveLoan(
            $scenario['base_purpose'],$scenario['customer'],$scenario['base_principal'],$scenario['account'],
            'sim.dar.loan','sim.inspector','sim.dar.cashier',$applications,$approvals,$disbursements,$asOf,
            'SIM-ADJ-DSB-TOPUP-BASE'
        );

        $manager = $this->user('sim.dar.manager');
        $product = $this->product();
        $replacement = $topUps->initiate($base,[
            'principal'=>$scenario['new_principal'],
            'purpose'=>$scenario['replacement_purpose'],
            'schedule_start_date'=>'2026-09-15',
            'override_eligibility'=>true,
            'override_reason'=>'Deterministic simulation of an authorized top-up eligibility override.',
            'guarantor_id'=>null,
            'collateral_ids'=>[],
        ],$product,$manager);

        $replacement = $applications->transition($replacement,'application_submitted',$manager,'Simulation top-up replacement submitted.');
        $replacement = $approvals->submit($replacement,$manager,'Simulation top-up approval review.');
        $replacement = $approvals->decide(
            $replacement,$this->user('sim.inspector'),'approve','Simulation top-up replacement approved.'
        );

        $topUps->execute(
            $replacement,$this->account($scenario['account']),$asOf,'SIM-ADJ-TOPUP-EXECUTE',
            'Simulation top-up execution control.',$this->user('sim.dar.cashier')
        );
    }

    private function seedEarlySettlement(
        array $scenario,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        LoanEarlySettlementService $settlements,
        CarbonImmutable $asOf,
    ): void {
        if (Loan::query()->where('purpose',$scenario['purpose'])->exists()) return;

        $loan = $this->createActiveLoan(
            $scenario['purpose'],$scenario['customer'],$scenario['principal'],$scenario['disbursement_account'],
            'sim.arusha.loan','sim.inspector','sim.arusha.manager',$applications,$approvals,$disbursements,$asOf,
            'SIM-ADJ-DSB-EARLY-SETTLEMENT'
        );

        $settlements->execute(
            $loan,$this->account($scenario['settlement_account']),$asOf,$scenario['adjustment'],
            'Simulation approved early-settlement reduction within interest and fee balance.',
            'SIM-ADJ-EARLY-SETTLEMENT','Simulation adjusted early settlement.',$this->user('sim.arusha.manager')
        );
    }

    private function seedApprovedRestructuring(
        array $scenario,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        LoanRestructuringService $restructurings,
        CarbonImmutable $asOf,
    ): void {
        if (Loan::query()->where('purpose',$scenario['purpose'])->exists()) return;

        $loan = $this->createActiveLoan(
            $scenario['purpose'],$scenario['customer'],$scenario['principal'],$scenario['account'],
            'sim.mwanza.loan','sim.inspector','sim.mwanza.cashier',$applications,$approvals,$disbursements,$asOf,
            'SIM-ADJ-DSB-RESTRUCTURE-APPROVED'
        );
        $request = $restructurings->request(
            $loan,$scenario['new_schedule'],'Simulation approved restructuring control case.',$this->user('sim.mwanza.loan')
        );
        $restructurings->approve($request,$this->user('sim.inspector'),'Simulation restructuring approved.');
    }

    private function seedRejectedRestructuring(
        array $scenario,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        LoanRestructuringService $restructurings,
        CarbonImmutable $asOf,
    ): void {
        if (Loan::query()->where('purpose',$scenario['purpose'])->exists()) return;

        $loan = $this->createActiveLoan(
            $scenario['purpose'],$scenario['customer'],$scenario['principal'],$scenario['account'],
            'sim.mwanza.loan','sim.inspector','sim.mwanza.cashier',$applications,$approvals,$disbursements,$asOf,
            'SIM-ADJ-DSB-RESTRUCTURE-REJECTED'
        );
        $request = $restructurings->request(
            $loan,$scenario['new_schedule'],'Simulation rejected restructuring control case.',$this->user('sim.mwanza.loan')
        );
        $restructurings->reject($request,$this->user('sim.inspector'),'Simulation restructuring rejected for control coverage.');
    }

    private function seedWriteOffRecovery(
        array $scenario,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        LoanWriteOffService $writeOffs,
        LoanWriteOffRecoveryService $recoveries,
        CarbonImmutable $asOf,
    ): void {
        if (Loan::query()->where('purpose',$scenario['purpose'])->exists()) return;

        $loan = $this->createActiveLoan(
            $scenario['purpose'],$scenario['customer'],$scenario['principal'],$scenario['disbursement_account'],
            'sim.dar.loan','sim.inspector','sim.dar.cashier',$applications,$approvals,$disbursements,$asOf,
            'SIM-ADJ-DSB-WRITEOFF-RECOVERY'
        );
        $manager = $this->user('sim.dar.manager');
        $writeOff = $writeOffs->request(
            $loan,$scenario['write_off_amount'],'Simulation approved write-off control case.',$manager
        );
        $this->attachWriteOffEvidence($writeOff->id,$manager,'approved');
        $writeOff = $writeOffs->approve(
            $writeOff,$this->superAdministrator(),'Simulation write-off approved with independent maker/checker.'
        );
        $recoveries->recover(
            $writeOff,$this->account($scenario['recovery_account']),$scenario['recovery_amount'],$asOf,'cash',
            'SIM-ADJ-WRITEOFF-RECOVERY','Simulation partial recovery against written-off loan.',$this->user('sim.dar.cashier')
        );
    }

    private function seedRejectedWriteOff(
        array $scenario,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        LoanWriteOffService $writeOffs,
        CarbonImmutable $asOf,
    ): void {
        if (Loan::query()->where('purpose',$scenario['purpose'])->exists()) return;

        $loan = $this->createActiveLoan(
            $scenario['purpose'],$scenario['customer'],$scenario['principal'],$scenario['account'],
            'sim.arusha.loan','sim.inspector','sim.arusha.manager',$applications,$approvals,$disbursements,$asOf,
            'SIM-ADJ-DSB-WRITEOFF-REJECTED'
        );
        $requester = $this->user('sim.arusha.manager');
        $writeOff = $writeOffs->request(
            $loan,$scenario['write_off_amount'],'Simulation rejected write-off control case.',$requester
        );
        $this->attachWriteOffEvidence($writeOff->id,$requester,'rejected');
        $writeOffs->reject(
            $writeOff,$this->superAdministrator(),'Simulation write-off rejected for decision-path coverage.'
        );
    }

    private function createActiveLoan(
        string $purpose,
        string $customerNumber,
        string $principal,
        string $accountName,
        string $creatorUsername,
        string $approverUsername,
        string $disburserUsername,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        CarbonImmutable $asOf,
        string $reference,
    ): Loan {
        $creator = $this->user($creatorUsername);
        $customer = $this->customer($customerNumber);
        $loan = $applications->create([
            'branch_id'=>$customer->branch_id,
            'customer_id'=>$customer->id,
            'principal'=>$principal,
            'purpose'=>$purpose,
            'schedule_start_date'=>'2026-09-15',
            'guarantor_id'=>null,
            'collateral_ids'=>[],
        ],$this->product(),$creator);
        $loan = $applications->transition($loan,'application_submitted',$creator,'Simulation adjustment base loan submitted.');
        $loan = $approvals->submit($loan,$creator,'Simulation adjustment base loan approval review.');
        $loan = $approvals->decide(
            $loan,$this->user($approverUsername),'approve','Simulation adjustment base loan approved.'
        );
        $disbursement = $disbursements->disburse(
            $loan,$this->account($accountName),$loan->netDisbursementAmount(),$asOf,$reference,
            'Simulation adjustment base-loan disbursement.',$this->user($disburserUsername)
        );

        return $disbursement->loan->fresh();
    }

    private function attachWriteOffEvidence(int $writeOffId, User $uploader, string $tag): void
    {
        $path = 'private/simulation/writeoffs/sim-'.$tag.'-'.$writeOffId.'.txt';
        Storage::disk('local')->put($path,'Deterministic simulation write-off evidence.');
        LoanWriteOffDocument::query()->create([
            'loan_write_off_id'=>$writeOffId,
            'document_type'=>'simulation_evidence',
            'original_name'=>'simulation-writeoff-evidence.txt',
            'file_path'=>$path,
            'mime_type'=>'text/plain',
            'file_size'=>strlen('Deterministic simulation write-off evidence.'),
            'notes'=>'Simulation-only supporting evidence for write-off approval validation.',
            'uploaded_by'=>$uploader->id,
        ]);
    }

    private function runBranchScenario(BranchContext $context, string $branchCode, callable $callback): void
    {
        $branch = Branch::query()->where('code',$branchCode)->firstOrFail();
        $context->forBranch((int) $branch->id,$callback);
    }

    private function product(): LoanProduct
    {
        return LoanProduct::query()->where('name','SIM Flat Monthly')->firstOrFail();
    }

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

    private function superAdministrator(): User
    {
        return User::query()->where('email','admin@microfinance.local')->firstOrFail();
    }

    private function customer(string $number): Customer
    {
        return Customer::query()->where('customer_number',$number)->firstOrFail();
    }

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