<?php

namespace Database\Seeders\Simulation;

use App\Models\Branch;
use App\Models\Business;
use App\Models\Collateral;
use App\Models\CollateralCategory;
use App\Models\Customer;
use App\Models\FinancialAccount;
use App\Models\Guarantor;
use App\Models\Loan;
use App\Models\LoanProduct;
use App\Models\RemotePaymentSubmission;
use App\Models\User;
use App\Services\BranchContext;
use App\Services\LoanApplicationService;
use App\Services\LoanApprovalService;
use App\Services\LoanDisbursementService;
use App\Services\LoanPaymentService;
use App\Services\OverdueLoanService;
use App\Services\RemotePaymentService;
use App\Support\Simulation\SimulationLoanLifecycleExpectations;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Database\Seeder;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;

class SimulationLoanLifecycleSeeder extends Seeder
{
    public function run(
        BranchContext $context,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        LoanPaymentService $payments,
        RemotePaymentService $remotePayments,
        OverdueLoanService $overdue,
    ): void {
        $asOf = CarbonImmutable::parse((string) config('simulation.as_of_date', SimulationLoanLifecycleExpectations::AS_OF_DATE))->setTime(12, 0);
        Carbon::setTestNow($asOf);

        try {
            DB::transaction(function () use ($context,$applications,$approvals,$disbursements,$payments,$remotePayments,$overdue,$asOf): void {
                $this->configureBusiness();

                foreach (SimulationLoanLifecycleExpectations::scenarios() as $key => $scenario) {
                    $branch = Branch::query()->where('code', $scenario['branch'])->firstOrFail();

                    $context->forBranch((int) $branch->id, function () use ($key,$scenario,$applications,$approvals,$disbursements,$payments,$remotePayments): void {
                        if (Loan::query()->where('purpose', $scenario['purpose'])->exists()) {
                            return;
                        }

                        match ($key) {
                            'draft' => $this->seedDraft($scenario, $applications),
                            'under_review' => $this->seedUnderReview($scenario, $applications, $approvals),
                            'rejected' => $this->seedRejected($scenario, $applications, $approvals),
                            'secured_ready' => $this->seedSecuredReady($scenario, $applications, $approvals),
                            'active_partial' => $this->seedActivePartial($scenario, $applications, $approvals, $disbursements, $payments),
                            'completed' => $this->seedCompleted($scenario, $applications, $approvals, $disbursements, $payments),
                            'overdue' => $this->seedOverdue($scenario, $applications, $approvals, $disbursements),
                            'remote' => $this->seedRemote($scenario, $applications, $approvals, $disbursements, $remotePayments),
                            default => null,
                        };
                    });
                }

                // Run delinquency after every active scenario exists so penalties and arrears
                // are generated by the same scheduled service used by the application.
                $context->systemWide(fn () => $overdue->process($asOf));
            });
        } finally {
            Carbon::setTestNow();
        }
    }

    private function configureBusiness(): void
    {
        Business::query()->firstOrFail()->update([
            'loan_approval_mode' => 'approval_required',
            'maker_checker_enabled' => true,
            'manager_approval_threshold' => '1500000.00',
            'require_signed_contract_before_disbursement' => false,
            'payment_deadline_penalties_enabled' => false,
            'deadline_late_penalty_type' => 'fixed',
            'deadline_late_penalty_value' => '0.000000',
            'deadline_late_penalty_settlement' => 'add_to_loan',
            'missed_schedule_penalty_type' => 'fixed',
            'missed_schedule_penalty_value' => '0.000000',
            'missed_schedule_penalty_settlement' => 'add_to_loan',
        ]);
    }

    private function seedDraft(array $scenario, LoanApplicationService $applications): void
    {
        $this->createLoan($scenario, $applications);
    }

    private function seedUnderReview(array $scenario, LoanApplicationService $applications, LoanApprovalService $approvals): void
    {
        $loan = $this->createLoan($scenario, $applications);
        $creator = $this->user($scenario['creator']);
        $applications->transition($loan, 'application_submitted', $creator, 'Simulation application submitted.');
        $approvals->submit($loan->fresh(), $creator, 'Simulation awaiting approval decision.');
    }

    private function seedRejected(array $scenario, LoanApplicationService $applications, LoanApprovalService $approvals): void
    {
        $loan = $this->createLoan($scenario, $applications);
        $creator = $this->user($scenario['creator']);
        $applications->transition($loan, 'application_submitted', $creator, 'Simulation application submitted.');
        $loan = $approvals->submit($loan->fresh(), $creator, 'Simulation approval review.');
        $approvals->decide($loan, $this->user($scenario['approver']), 'reject', 'Simulation rejection control case.');
    }

    private function seedSecuredReady(array $scenario, LoanApplicationService $applications, LoanApprovalService $approvals): void
    {
        $customer = $this->customer($scenario['customer']);
        $creator = $this->user($scenario['creator']);
        $category = CollateralCategory::query()->firstOrFail();

        $guarantor = Guarantor::query()->updateOrCreate(
            ['customer_id' => $customer->id, 'full_name' => 'Simulation Mwanza Secured Guarantor'],
            [
                'nin' => 'SIM-GUA-MZA-001',
                'phone' => '0742000001',
                'address' => 'Nyamagana, Mwanza',
                'occupation' => 'Business Owner',
                'relationship' => 'Business Partner',
                'status' => 'active',
                'created_by' => $creator->id,
            ],
        );

        $collateral = Collateral::query()->updateOrCreate(
            ['code' => 'SIM-COL-MZA-001'],
            [
                'customer_id' => $customer->id,
                'branch_id' => $customer->branch_id,
                'category_id' => $category->id,
                'sequence_number' => 900001,
                'item' => 'Simulation Commercial Freezer',
                'brand' => 'SIM Brand',
                'model' => 'SIM-1200',
                'serial_number' => 'SIMSER-MZA-001',
                'colour' => 'White',
                'description' => 'Deterministic secured-loan collateral.',
                'estimated_value' => '2200000.00',
                'accepted_value' => '1600000.00',
                'condition' => 'Good',
                'received_at' => '2026-08-14',
                'store_room' => 'Simulation Store',
                'shelf' => 'MZA-A1',
                'received_by' => $creator->id,
                'status' => 'in_custody',
                'created_by' => $creator->id,
                'updated_by' => $creator->id,
            ],
        );

        $loan = $this->createLoan($scenario, $applications, $guarantor->id, [$collateral->id]);
        $applications->transition($loan, 'application_submitted', $creator, 'Simulation secured application submitted.');
        $loan = $applications->markContractGenerated($loan->fresh(), $creator);
        $loan = $applications->markContractSigned($loan->fresh(), $creator);
        $loan = $approvals->submit($loan->fresh(), $creator, 'Simulation secured approval review.');
        $approvals->decide($loan, $this->user($scenario['approver']), 'approve', 'Simulation secured loan approved.');
    }

    private function seedActivePartial(
        array $scenario,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        LoanPaymentService $payments,
    ): void {
        $loan = $this->approveLoan($scenario, $applications, $approvals);
        $disbursement = $disbursements->disburse(
            $loan,
            $this->account($scenario['disbursement_account']),
            $loan->netDisbursementAmount(),
            CarbonImmutable::parse($scenario['disbursement_date']),
            'SIM-DSB-ACTIVE-PARTIAL',
            'Simulation active partial-payment disbursement.',
            $this->user($scenario['disburser']),
        );

        $payments->post(
            $disbursement->loan,
            $this->account($scenario['payment_account']),
            $scenario['payment_amount'],
            CarbonImmutable::parse($scenario['payment_date']),
            'cash',
            'SIM-PAY-ACTIVE-PARTIAL',
            'Simulation partial payment before first due date.',
            $this->user($scenario['disburser']),
        );
    }

    private function seedCompleted(
        array $scenario,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        LoanPaymentService $payments,
    ): void {
        $loan = $this->approveLoan($scenario, $applications, $approvals);
        $disbursement = $disbursements->disburse(
            $loan,
            $this->account($scenario['disbursement_account']),
            $loan->netDisbursementAmount(),
            CarbonImmutable::parse($scenario['disbursement_date']),
            'SIM-DSB-COMPLETED',
            'Simulation completed-loan disbursement.',
            $this->user($scenario['disburser']),
        );

        $payments->post(
            $disbursement->loan,
            $this->account($scenario['payment_account']),
            $scenario['payment_amount'],
            CarbonImmutable::parse($scenario['payment_date']),
            'cash',
            'SIM-PAY-COMPLETED',
            'Simulation full early repayment.',
            $this->user($scenario['disburser']),
        );
    }

    private function seedOverdue(
        array $scenario,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
    ): void {
        $customer = $this->customer($scenario['customer']);
        $creator = $this->user($scenario['creator']);
        $guarantor = Guarantor::query()->updateOrCreate(
            ['customer_id' => $customer->id, 'full_name' => 'Simulation Dar Overdue Guarantor'],
            [
                'nin' => 'SIM-GUA-DAR-001',
                'phone' => '0741000001',
                'address' => 'Ilala, Dar es Salaam',
                'occupation' => 'Retailer',
                'relationship' => 'Relative',
                'status' => 'active',
                'created_by' => $creator->id,
            ],
        );

        $loan = $this->approveLoan($scenario, $applications, $approvals, $guarantor->id);
        $disbursements->disburse(
            $loan,
            $this->account($scenario['disbursement_account']),
            $loan->netDisbursementAmount(),
            CarbonImmutable::parse($scenario['disbursement_date']),
            'SIM-DSB-OVERDUE',
            'Simulation overdue-loan disbursement.',
            $this->user($scenario['disburser']),
            $this->account($scenario['processing_fee_account']),
        );
    }

    private function seedRemote(
        array $scenario,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        LoanDisbursementService $disbursements,
        RemotePaymentService $remotePayments,
    ): void {
        $loan = $this->approveLoan($scenario, $applications, $approvals);
        $disbursement = $disbursements->disburse(
            $loan,
            $this->account($scenario['disbursement_account']),
            $loan->netDisbursementAmount(),
            CarbonImmutable::parse($scenario['disbursement_date']),
            'SIM-DSB-REMOTE',
            'Simulation remote-payment loan disbursement.',
            $this->user($scenario['disburser']),
        );

        $submitter = $this->user($scenario['creator']);
        $verifier = $this->user($scenario['disburser']);

        $pending = $this->submitRemote($remotePayments, $disbursement->loan, '50000.00', 'SIM-RMP-PENDING-001', $submitter);
        $rejected = $this->submitRemote($remotePayments, $disbursement->loan, '60000.00', 'SIM-RMP-REJECTED-001', $submitter);
        $approved = $this->submitRemote($remotePayments, $disbursement->loan, $scenario['approved_remote_amount'], 'SIM-RMP-APPROVED-001', $submitter);

        $remotePayments->reject($rejected, 'Simulation rejected remote-payment evidence.', $verifier);
        $remotePayments->approve(
            $approved,
            $this->account($scenario['payment_account']),
            CarbonImmutable::parse($scenario['approved_remote_date']),
            'Simulation verified remote payment.',
            $verifier,
        );

        // Keep one record intentionally pending to validate operational queues.
        $pending->refresh();
    }

    private function submitRemote(RemotePaymentService $service, Loan $loan, string $amount, string $reference, User $submitter): RemotePaymentSubmission
    {
        return $service->submit(
            $loan->fresh(),
            $amount,
            'mpesa',
            $reference,
            UploadedFile::fake()->create('simulation-payment-proof.pdf', 12, 'application/pdf'),
            'Deterministic simulation remote-payment evidence.',
            'staff',
            $submitter,
        );
    }

    private function approveLoan(
        array $scenario,
        LoanApplicationService $applications,
        LoanApprovalService $approvals,
        ?int $guarantorId = null,
        array $collateralIds = [],
    ): Loan {
        $loan = $this->createLoan($scenario, $applications, $guarantorId, $collateralIds);
        $creator = $this->user($scenario['creator']);
        $applications->transition($loan, 'application_submitted', $creator, 'Simulation application submitted.');
        $loan = $approvals->submit($loan->fresh(), $creator, 'Simulation approval review.');

        return $approvals->decide(
            $loan,
            $this->user($scenario['approver']),
            'approve',
            'Simulation approval control case.',
        );
    }

    private function createLoan(
        array $scenario,
        LoanApplicationService $applications,
        ?int $guarantorId = null,
        array $collateralIds = [],
    ): Loan {
        $creator = $this->user($scenario['creator']);
        $customer = $this->customer($scenario['customer']);
        $product = LoanProduct::query()->where('name', $scenario['product'])->firstOrFail();

        return $applications->create([
            'branch_id' => $customer->branch_id,
            'customer_id' => $customer->id,
            'principal' => $scenario['principal'],
            'purpose' => $scenario['purpose'],
            'schedule_start_date' => $scenario['schedule_start_date'],
            'guarantor_id' => $guarantorId,
            'collateral_ids' => $collateralIds,
        ], $product, $creator);
    }

    private function user(string $username): User
    {
        return User::query()->where('username', $username)->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();
    }
}
