<?php

namespace App\Services;

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

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

    /**
     * @param array{principal:string,interest:string,fees:string,penalties:string} $settled
     */
    public function post(LoanTopUp $topUp, FinancialAccount $account, array $settled, CarbonInterface $date, string $reference, ?string $notes, User $user): FinancialAccountTransaction
    {
        $newPrincipal = $this->money->toMinor((string) $topUp->new_principal);
        $cash = $this->money->toMinor((string) $topUp->cash_given_to_customer);
        $components = collect(['principal','interest','fees','penalties'])->mapWithKeys(
            fn (string $key): array => [$key => $this->money->toMinor((string) ($settled[$key] ?? '0'))]
        )->all();
        $settledTotal = array_sum($components);

        if ($newPrincipal <= 0 || $cash < 0 || $settledTotal <= 0 || $newPrincipal !== $cash + $settledTotal) {
            throw ValidationException::withMessages(['top_up'=>'Top-up accounting does not reconcile: new principal must equal internal settlement plus net customer cash.']);
        }

        $locked = FinancialAccount::query()->lockForUpdate()->findOrFail($account->id);
        if ($locked->branch_id !== $topUp->branch_id || $locked->status !== 'active') {
            throw ValidationException::withMessages(['financial_account_id'=>'Top-up funding requires an active account in the same branch.']);
        }

        $current = $this->money->toMinor((string) $locked->current_balance);
        if ($current < $cash) {
            throw ValidationException::withMessages(['financial_account_id'=>'The selected account has insufficient funds for the net top-up cash.']);
        }

        $newBalance = $this->money->fromMinor($current - $cash);
        $cashValue = $this->money->fromMinor($cash);
        $description = $notes ?: 'Loan top-up '.$topUp->top_up_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_top_up',
            'description'=>$description,
            'money_in'=>'0.00',
            'money_out'=>$cashValue,
            'running_balance'=>$newBalance,
            'user_id'=>$user->id,
            'related_type'=>LoanTopUp::class,
            'related_id'=>$topUp->id,
        ]);

        $journal = 'JRN-'.$topUp->top_up_number;
        AccountingEntry::create([
            'journal_number'=>$journal,'branch_id'=>$topUp->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
            'reference'=>$reference,'category'=>'principal','event_type'=>'loan_top_up','description'=>'New principal receivable for '.$topUp->newLoan->loan_number,
            'debit'=>$topUp->new_principal,'credit'=>'0.00','user_id'=>$user->id,'related_type'=>LoanTopUp::class,'related_id'=>$topUp->id,
        ]);

        if ($cash > 0) {
            AccountingEntry::create([
                'journal_number'=>$journal,'branch_id'=>$topUp->branch_id,'financial_account_id'=>$locked->id,'entry_date'=>$date,
                'reference'=>$reference,'category'=>'assets','event_type'=>'loan_top_up','description'=>'Net top-up cash disbursed to customer',
                'debit'=>'0.00','credit'=>$cashValue,'user_id'=>$user->id,'related_type'=>LoanTopUp::class,'related_id'=>$topUp->id,
            ]);
        }

        foreach ($components as $category => $minor) {
            if ($minor <= 0) continue;
            AccountingEntry::create([
                'journal_number'=>$journal,'branch_id'=>$topUp->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
                'reference'=>$reference,'category'=>$category,'event_type'=>'loan_top_up','description'=>ucfirst($category).' settled internally on '.$topUp->previousLoan->loan_number,
                'debit'=>'0.00','credit'=>$this->money->fromMinor($minor),'user_id'=>$user->id,'related_type'=>LoanTopUp::class,'related_id'=>$topUp->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);
    }
}
