<?php

namespace App\Services;

use App\Models\CustomerStatementEntry;
use App\Models\DailyCollection;
use App\Models\FinancialAccount;
use App\Models\Loan;
use App\Models\LoanWriteOff;
use App\Models\LoanWriteOffRecovery;
use App\Models\User;
use App\Services\CreditScore\CreditScoreRefreshCoordinator;
use Carbon\CarbonInterface;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class LoanWriteOffRecoveryService
{
    public function __construct(
        private MoneyMath $money,
        private LoanWriteOffAccountingService $accounting,
        private CreditScoreRefreshCoordinator $creditScores,
    ) {}

    public function recover(
        LoanWriteOff $writeOff,
        FinancialAccount $account,
        string $amount,
        CarbonInterface $date,
        string $method,
        ?string $reference,
        ?string $notes,
        User $user
    ): LoanWriteOffRecovery {
        if (! $user->can('loans.writeoff.recover')) {
            throw ValidationException::withMessages(['recovery'=>'You are not authorized to post written-off loan recoveries.']);
        }
        $amountMinor = $this->money->toMinor($amount);
        if ($amountMinor <= 0) {
            throw ValidationException::withMessages(['amount'=>'Recovery amount must be greater than zero.']);
        }

        return DB::transaction(function () use ($writeOff,$account,$amountMinor,$date,$method,$reference,$notes,$user): LoanWriteOffRecovery {
            $record = LoanWriteOff::query()->lockForUpdate()->findOrFail($writeOff->id);
            if (! $user->canAccessBranch($record->branch_id)) {
                throw ValidationException::withMessages(['recovery'=>'You do not have access to this branch.']);
            }
            if ($record->status !== 'approved') {
                throw ValidationException::withMessages(['recovery'=>'Recoveries can only be posted against an approved write-off.']);
            }

            $loan = Loan::query()->lockForUpdate()->findOrFail($record->loan_id);
            if ($loan->status !== 'written_off') {
                throw ValidationException::withMessages(['loan'=>'The related loan is not currently written off.']);
            }
            if ($account->branch_id !== $record->branch_id) {
                throw ValidationException::withMessages(['financial_account_id'=>'The receiving account must belong to the written-off loan branch.']);
            }

            $recoverableMinor = $this->money->toMinor((string) $record->recoverable_balance);
            if ($recoverableMinor <= 0) {
                throw ValidationException::withMessages(['amount'=>'This written-off loan has already been fully recovered.']);
            }
            if ($amountMinor > $recoverableMinor) {
                throw ValidationException::withMessages([
                    'amount'=>'Recovery cannot exceed the remaining written-off balance of '.$this->money->fromMinor($recoverableMinor).'.',
                ]);
            }

            $number = $this->nextNumber($record);
            $effectiveReference = $reference ?: $number;
            $amountValue = $this->money->fromMinor($amountMinor);
            $recovery = LoanWriteOffRecovery::create([
                'recovery_number'=>$number,'loan_write_off_id'=>$record->id,'loan_id'=>$record->loan_id,'customer_id'=>$record->customer_id,
                'branch_id'=>$record->branch_id,'financial_account_id'=>$account->id,'amount'=>$amountValue,'recovery_date'=>$date,
                'method'=>$method,'reference'=>$effectiveReference,'notes'=>$notes,'received_by'=>$user->id,
            ]);
            $transaction = $this->accounting->postRecovery($recovery,$account,$date,$effectiveReference,$notes,$user);
            $recovery->update(['financial_account_transaction_id'=>$transaction->id]);

            $recoveredMinor = $this->money->toMinor((string) $record->recovered_amount) + $amountMinor;
            $remainingMinor = $recoverableMinor - $amountMinor;
            $record->update([
                'recovered_amount'=>$this->money->fromMinor($recoveredMinor),
                'recoverable_balance'=>$this->money->fromMinor($remainingMinor),
            ]);

            CustomerStatementEntry::create([
                'customer_id'=>$record->customer_id,'loan_id'=>$record->loan_id,'branch_id'=>$record->branch_id,'entry_date'=>$date,
                'reference'=>$number,'type'=>'loan_write_off_recovery','description'=>'Written-off loan recovery '.$number,
                'debit'=>'0.00','credit'=>$amountValue,'running_balance'=>$this->money->fromMinor($remainingMinor),
                'related_type'=>LoanWriteOffRecovery::class,'related_id'=>$recovery->id,
            ]);
            $this->updateDailyCollection($record->branch_id,$account->id,$date,$amountMinor);
            $this->creditScores->afterCommitForLoan($loan, $remainingMinor === 0 ? 'write_off_fully_recovered' : 'write_off_recovery');

            return $recovery->fresh(['writeOff','loan.customer','branch','financialAccount','receiver','accountTransaction']);
        });
    }

    private function updateDailyCollection(int $branchId, int $accountId, CarbonInterface $date, int $amountMinor): void
    {
        $day = $date->format('Y-m-d');
        DB::table('daily_collections')->insertOrIgnore([
            'branch_id'=>$branchId,'financial_account_id'=>$accountId,'collection_date'=>$day,'amount'=>0,
            'principal'=>0,'interest'=>0,'fees'=>0,'penalties'=>0,'write_off_recoveries'=>0,
            'write_off_recoveries_count'=>0,'payments_count'=>0,'created_at'=>now(),'updated_at'=>now(),
        ]);
        $row = DailyCollection::query()
            ->where('branch_id',$branchId)->where('financial_account_id',$accountId)->whereDate('collection_date',$day)
            ->lockForUpdate()->firstOrFail();
        $row->update([
            'amount'=>$this->money->fromMinor($this->money->toMinor((string) $row->amount)+$amountMinor),
            'write_off_recoveries'=>$this->money->fromMinor($this->money->toMinor((string) $row->write_off_recoveries)+$amountMinor),
            'write_off_recoveries_count'=>$row->write_off_recoveries_count+1,
        ]);
    }

    private function nextNumber(LoanWriteOff $writeOff): string
    {
        $year = (int) now()->format('Y');
        DB::table('number_sequences')->insertOrIgnore([
            'type'=>'loan_write_off_recovery','branch_id'=>$writeOff->branch_id,'year'=>$year,'current_value'=>0,'created_at'=>now(),'updated_at'=>now(),
        ]);
        $row = DB::table('number_sequences')
            ->where('type','loan_write_off_recovery')->where('branch_id',$writeOff->branch_id)->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()]);
        $writeOff->loadMissing('branch');
        $tag = strtoupper(preg_replace('/[^A-Z0-9]/','',str_replace('BR-','',$writeOff->branch->code)) ?: (string) $writeOff->branch_id);

        return 'WRC-'.$tag.'-'.$year.'-'.str_pad((string) $next,6,'0',STR_PAD_LEFT);
    }
}
