<?php

namespace App\Services;

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

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

    public function postWriteOff(LoanWriteOff $writeOff, CarbonInterface $date, User $user): string
    {
        $principalMinor = $this->money->toMinor((string) $writeOff->principal_outstanding);
        if ($principalMinor < 0) {
            throw ValidationException::withMessages(['write_off'=>'Outstanding principal cannot be negative.']);
        }

        $journal = 'JRN-'.$writeOff->write_off_number;
        if ($principalMinor === 0) {
            AccountingEntry::create([
                'journal_number'=>$journal,'branch_id'=>$writeOff->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
                'reference'=>$writeOff->write_off_number,'category'=>'write_offs','event_type'=>'loan_write_off',
                'description'=>'Loan write-off '.$writeOff->write_off_number.' has no principal receivable remaining to remove.',
                'debit'=>'0.00','credit'=>'0.00','user_id'=>$user->id,'related_type'=>LoanWriteOff::class,'related_id'=>$writeOff->id,
            ]);
            return $journal;
        }

        $principal = $this->money->fromMinor($principalMinor);
        $writeOff->loadMissing('loan');
        AccountingEntry::create([
            'journal_number'=>$journal,'branch_id'=>$writeOff->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
            'reference'=>$writeOff->write_off_number,'category'=>'write_offs','event_type'=>'loan_write_off',
            'description'=>'Bad-debt expense for written-off principal on '.$writeOff->loan->loan_number,
            'debit'=>$principal,'credit'=>'0.00','user_id'=>$user->id,'related_type'=>LoanWriteOff::class,'related_id'=>$writeOff->id,
        ]);
        AccountingEntry::create([
            'journal_number'=>$journal,'branch_id'=>$writeOff->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
            'reference'=>$writeOff->write_off_number,'category'=>'principal','event_type'=>'loan_write_off',
            'description'=>'Remove written-off principal receivable on '.$writeOff->loan->loan_number,
            'debit'=>'0.00','credit'=>$principal,'user_id'=>$user->id,'related_type'=>LoanWriteOff::class,'related_id'=>$writeOff->id,
        ]);

        return $journal;
    }

    public function postRecovery(
        LoanWriteOffRecovery $recovery,
        FinancialAccount $account,
        CarbonInterface $date,
        string $reference,
        ?string $notes,
        User $user
    ): FinancialAccountTransaction {
        $amountMinor = $this->money->toMinor((string) $recovery->amount);
        if ($amountMinor <= 0) {
            throw ValidationException::withMessages(['amount'=>'Recovery amount must be greater than zero.']);
        }

        $locked = FinancialAccount::query()->lockForUpdate()->findOrFail($account->id);
        if ($locked->branch_id !== $recovery->branch_id) {
            throw ValidationException::withMessages(['financial_account_id'=>'The recovery account must belong to the written-off loan branch.']);
        }
        if ($locked->status !== 'active') {
            throw ValidationException::withMessages(['financial_account_id'=>'Written-off loan recovery requires an active receiving account.']);
        }

        $currentMinor = $this->money->toMinor((string) $locked->current_balance);
        $newBalance = $this->money->fromMinor($currentMinor + $amountMinor);
        $amount = $this->money->fromMinor($amountMinor);
        $transactionNumber = $this->nextTransactionNumber($locked->branch_id);
        $description = $notes ?: 'Written-off loan recovery '.$recovery->recovery_number;

        $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_write_off_recovery','description'=>$description,'money_in'=>$amount,'money_out'=>'0.00',
            'running_balance'=>$newBalance,'user_id'=>$user->id,'related_type'=>LoanWriteOffRecovery::class,'related_id'=>$recovery->id,
        ]);

        $recovery->loadMissing('loan');
        $journal = 'JRN-'.$recovery->recovery_number;
        AccountingEntry::create([
            'journal_number'=>$journal,'branch_id'=>$recovery->branch_id,'financial_account_id'=>$locked->id,'entry_date'=>$date,
            'reference'=>$reference,'category'=>'assets','event_type'=>'loan_write_off_recovery','description'=>$description,
            'debit'=>$amount,'credit'=>'0.00','user_id'=>$user->id,'related_type'=>LoanWriteOffRecovery::class,'related_id'=>$recovery->id,
        ]);
        AccountingEntry::create([
            'journal_number'=>$journal,'branch_id'=>$recovery->branch_id,'financial_account_id'=>null,'entry_date'=>$date,
            'reference'=>$reference,'category'=>'write_off_recoveries','event_type'=>'loan_write_off_recovery',
            'description'=>'Recovery income from written-off loan '.$recovery->loan->loan_number,
            'debit'=>'0.00','credit'=>$amount,'user_id'=>$user->id,'related_type'=>LoanWriteOffRecovery::class,'related_id'=>$recovery->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);
    }
}
