<?php

namespace App\Services;

use App\Models\AccountingEntry;
use App\Models\Branch;
use App\Models\FinancialAccount;
use App\Models\FinancialAccountTransaction;
use App\Models\Loan;
use App\Models\LoanDisbursement;
use App\Models\User;
use Carbon\CarbonInterface;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class ProcessingFeeSettlementService
{
    public function __construct(private MoneyMath $money) {}

    public function settleDeducted(LoanDisbursement $disbursement, string $fee, CarbonInterface $date, string $reference, User $user): void
    {
        $feeMinor = $this->money->toMinor($fee);
        if ($feeMinor <= 0) return;

        $value = $this->money->fromMinor($feeMinor);
        $disbursement->loadMissing('loan');
        $journal = 'JRN-'.$disbursement->disbursement_number.'-FEE';
        $description = 'Processing fee withheld from disbursement '.$disbursement->loan->loan_number;

        AccountingEntry::create([
            'journal_number'=>$journal,'branch_id'=>$disbursement->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
            'reference'=>$reference,'category'=>'principal','event_type'=>'loan_processing_fee_deducted','description'=>'Additional principal receivable for '.$disbursement->loan->loan_number,
            'debit'=>$value,'credit'=>'0.00','user_id'=>$user->id,'related_type'=>LoanDisbursement::class,'related_id'=>$disbursement->id,
        ]);
        AccountingEntry::create([
            'journal_number'=>$journal,'branch_id'=>$disbursement->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
            'reference'=>$reference,'category'=>'fees','event_type'=>'loan_processing_fee_deducted','description'=>$description,
            'debit'=>'0.00','credit'=>$value,'user_id'=>$user->id,'related_type'=>LoanDisbursement::class,'related_id'=>$disbursement->id,
        ]);
    }

    public function collectCash(Loan $loan, FinancialAccount $account, string $fee, CarbonInterface $date, string $reference, User $user): ?FinancialAccountTransaction
    {
        $feeMinor = $this->money->toMinor($fee);
        if ($feeMinor <= 0) return null;

        return DB::transaction(function () use ($loan,$account,$feeMinor,$date,$reference,$user): FinancialAccountTransaction {
            $locked = FinancialAccount::query()->lockForUpdate()->findOrFail($account->id);
            if ($locked->branch_id !== $loan->branch_id) throw ValidationException::withMessages(['processing_fee_account_id'=>'The processing fee account must belong to the loan branch.']);
            if ($locked->status !== 'active' || $locked->account_type !== 'cash') throw ValidationException::withMessages(['processing_fee_account_id'=>'Cash processing fees must be received into an active cash account.']);

            $current = $this->money->toMinor((string) $locked->current_balance);
            $newBalance = $this->money->fromMinor($current + $feeMinor);
            $value = $this->money->fromMinor($feeMinor);
            $number = $this->nextTransactionNumber($locked->branch_id);
            $description = 'Cash processing fee for '.$loan->loan_number;
            $locked->update(['current_balance'=>$newBalance,'updated_by'=>$user->id]);

            $transaction = FinancialAccountTransaction::create([
                'transaction_number'=>$number,'account_id'=>$locked->id,'branch_id'=>$locked->branch_id,'transaction_date'=>$date,
                'reference'=>$reference,'type'=>'loan_processing_fee','description'=>$description,'money_in'=>$value,'money_out'=>'0.00',
                'running_balance'=>$newBalance,'user_id'=>$user->id,'related_type'=>Loan::class,'related_id'=>$loan->id,
            ]);

            $journal = 'JRN-'.$number;
            AccountingEntry::create([
                'journal_number'=>$journal,'branch_id'=>$locked->branch_id,'financial_account_id'=>$locked->id,'entry_date'=>$date,
                'reference'=>$reference,'category'=>'assets','event_type'=>'loan_processing_fee_cash','description'=>$description,
                'debit'=>$value,'credit'=>'0.00','user_id'=>$user->id,'related_type'=>Loan::class,'related_id'=>$loan->id,
            ]);
            AccountingEntry::create([
                'journal_number'=>$journal,'branch_id'=>$locked->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
                'reference'=>$reference,'category'=>'fees','event_type'=>'loan_processing_fee_cash','description'=>$description,
                'debit'=>'0.00','credit'=>$value,'user_id'=>$user->id,'related_type'=>Loan::class,'related_id'=>$loan->id,
            ]);

            return $transaction;
        });
    }

    private function nextTransactionNumber(int $branchId): string
    {
        $year = (int) now()->format('Y');
        DB::table('number_sequences')->insertOrIgnore(['type'=>'financial_transaction','branch_id'=>$branchId,'year'=>$year,'current_value'=>0,'created_at'=>now(),'updated_at'=>now()]);
        $row = DB::table('number_sequences')->where('type','financial_transaction')->where('branch_id',$branchId)->where('year',$year)->lockForUpdate()->first();
        $next = ((int) $row->current_value) + 1;
        DB::table('number_sequences')->where('id',$row->id)->update(['current_value'=>$next,'updated_at'=>now()]);
        $branch = Branch::findOrFail($branchId);
        $tag = strtoupper(preg_replace('/[^A-Z0-9]/','',str_replace('BR-','',$branch->code)) ?: (string) $branchId);
        return 'FTX-'.$tag.'-'.$year.'-'.str_pad((string) $next,6,'0',STR_PAD_LEFT);
    }
}
