<?php

namespace App\Services;

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

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

    /** @param array{principal:string,interest:string,fees:string,penalties:string} $components */
    public function post(LoanEarlySettlement $settlement, FinancialAccount $account, array $components, CarbonInterface $date, string $reference, ?string $notes, User $user): FinancialAccountTransaction
    {
        $minor = [];
        foreach (['principal','interest','fees','penalties'] as $category) {
            $minor[$category] = $this->money->toMinor((string) ($components[$category] ?? '0'));
        }
        $subtotal = array_sum($minor);
        $adjustment = $this->money->toSignedMinor((string) $settlement->settlement_adjustment);
        $final = $this->money->toMinor((string) $settlement->final_amount);
        if ($subtotal <= 0 || $final <= 0 || $subtotal + $adjustment !== $final) {
            throw ValidationException::withMessages(['settlement'=>'Early-settlement accounting does not reconcile.']);
        }

        $locked = FinancialAccount::query()->lockForUpdate()->findOrFail($account->id);
        if ($locked->branch_id !== $settlement->branch_id || $locked->status !== 'active') {
            throw ValidationException::withMessages(['financial_account_id'=>'Settlement must be received into an active account in the same branch.']);
        }

        $current = $this->money->toMinor((string) $locked->current_balance);
        $newBalance = $this->money->fromMinor($current + $final);
        $finalValue = $this->money->fromMinor($final);
        $description = $notes ?: 'Early settlement '.$settlement->settlement_number;
        $transactionNumber = $this->nextTransactionNumber($locked->branch_id);
        $locked->update(['current_balance'=>$newBalance,'updated_by'=>$user->id]);

        $transaction = FinancialAccountTransaction::create([
            'transaction_number'=>$transactionNumber,'account_id'=>$locked->id,'branch_id'=>$locked->branch_id,'transaction_date'=>$date,
            'reference'=>$reference,'type'=>'loan_early_settlement','description'=>$description,'money_in'=>$finalValue,'money_out'=>'0.00',
            'running_balance'=>$newBalance,'user_id'=>$user->id,'related_type'=>LoanEarlySettlement::class,'related_id'=>$settlement->id,
        ]);

        $settlement->loadMissing('loan');
        $journal = 'JRN-'.$settlement->settlement_number;
        AccountingEntry::create([
            'journal_number'=>$journal,'branch_id'=>$settlement->branch_id,'financial_account_id'=>$locked->id,'entry_date'=>$date,
            'reference'=>$reference,'category'=>'assets','event_type'=>'loan_early_settlement','description'=>$description,
            'debit'=>$finalValue,'credit'=>'0.00','user_id'=>$user->id,'related_type'=>LoanEarlySettlement::class,'related_id'=>$settlement->id,
        ]);
        foreach ($minor as $category => $value) {
            if ($value <= 0) continue;
            AccountingEntry::create([
                'journal_number'=>$journal,'branch_id'=>$settlement->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
                'reference'=>$reference,'category'=>$category,'event_type'=>'loan_early_settlement',
                'description'=>ucfirst($category).' settled on '.$settlement->loan->loan_number,
                'debit'=>'0.00','credit'=>$this->money->fromMinor($value),'user_id'=>$user->id,
                'related_type'=>LoanEarlySettlement::class,'related_id'=>$settlement->id,
            ]);
        }
        if ($adjustment < 0) {
            AccountingEntry::create([
                'journal_number'=>$journal,'branch_id'=>$settlement->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
                'reference'=>$reference,'category'=>'settlement_adjustment','event_type'=>'loan_early_settlement',
                'description'=>'Authorized early-settlement reduction on '.$settlement->loan->loan_number,
                'debit'=>$this->money->fromMinor(abs($adjustment)),'credit'=>'0.00','user_id'=>$user->id,
                'related_type'=>LoanEarlySettlement::class,'related_id'=>$settlement->id,
            ]);
        } elseif ($adjustment > 0) {
            AccountingEntry::create([
                'journal_number'=>$journal,'branch_id'=>$settlement->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
                'reference'=>$reference,'category'=>'settlement_adjustment','event_type'=>'loan_early_settlement',
                'description'=>'Authorized early-settlement addition on '.$settlement->loan->loan_number,
                'debit'=>'0.00','credit'=>$this->money->fromMinor($adjustment),'user_id'=>$user->id,
                'related_type'=>LoanEarlySettlement::class,'related_id'=>$settlement->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);
    }
}
