<?php

namespace App\Services;

use App\Models\CustomerStatementEntry;
use App\Models\Loan;
use App\Models\LoanStatusHistory;
use App\Models\LoanWriteOff;
use App\Models\User;
use App\Services\CreditScore\CreditScoreRefreshCoordinator;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

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

    public function request(Loan $loan, string $amount, string $reason, User $user): LoanWriteOff
    {
        if (! $user->can('loans.writeoff.request')) {
            throw ValidationException::withMessages(['write_off'=>'You are not authorized to request a loan write-off.']);
        }

        return DB::transaction(function () use ($loan,$amount,$reason,$user): LoanWriteOff {
            $locked = Loan::query()->with('branch.business')->lockForUpdate()->findOrFail($loan->id);
            $this->assertBranch($locked,$user);
            if ($locked->status !== 'active') {
                throw ValidationException::withMessages(['loan'=>'Only an active loan can be submitted for write-off.']);
            }
            if (LoanWriteOff::query()->where('loan_id',$locked->id)->whereIn('status',['pending','approved'])->exists()) {
                throw ValidationException::withMessages(['loan'=>'This loan already has an unresolved or approved write-off.']);
            }
            if ($locked->restructurings()->where('status','pending')->exists()) {
                throw ValidationException::withMessages(['loan'=>'Resolve the pending restructuring request before requesting a write-off.']);
            }
            if ($locked->topUpReplacementLoans()->where('status','!=','rejected')->exists()) {
                throw ValidationException::withMessages(['loan'=>'Resolve the existing top-up replacement application before requesting a write-off.']);
            }

            $components = $this->components($locked);
            $total = array_sum($components);
            $requested = $this->money->toMinor($amount);
            if ($requested !== $total) {
                throw ValidationException::withMessages([
                    'write_off_amount'=>'A loan write-off closes the active loan and must equal the full current outstanding balance of '.$this->money->fromMinor($total).'.',
                ]);
            }
            $reason = trim($reason);
            if ($reason === '') {
                throw ValidationException::withMessages(['reason'=>'A write-off reason is required.']);
            }

            return LoanWriteOff::create([
                'write_off_number'=>$this->nextNumber($locked),'loan_id'=>$locked->id,'customer_id'=>$locked->customer_id,'branch_id'=>$locked->branch_id,
                'original_principal'=>$locked->principal,'original_total_repayment'=>$locked->total_repayment,
                'principal_outstanding'=>$this->money->fromMinor($components['principal']),
                'interest_outstanding'=>$this->money->fromMinor($components['interest']),
                'fees_outstanding'=>$this->money->fromMinor($components['fees']),
                'penalties_outstanding'=>$this->money->fromMinor($components['penalties']),
                'write_off_amount'=>$this->money->fromMinor($total),'recovered_amount'=>'0.00','recoverable_balance'=>$this->money->fromMinor($total),
                'reason'=>$reason,'status'=>'pending','requested_by'=>$user->id,'requested_at'=>now(),
            ]);
        });
    }

    public function approve(LoanWriteOff $writeOff, User $user, ?string $comments = null): LoanWriteOff
    {
        if (! $user->can('loans.writeoff.approve')) {
            throw ValidationException::withMessages(['write_off'=>'You are not authorized to approve a loan write-off.']);
        }

        return DB::transaction(function () use ($writeOff,$user,$comments): LoanWriteOff {
            $record = LoanWriteOff::query()->lockForUpdate()->findOrFail($writeOff->id);
            if ($record->status !== 'pending') {
                throw ValidationException::withMessages(['write_off'=>'Only a pending write-off request can be approved.']);
            }
            if (! $user->canAccessBranch($record->branch_id)) {
                throw ValidationException::withMessages(['write_off'=>'You do not have access to this branch.']);
            }
            if (! $record->documents()->exists()) {
                throw ValidationException::withMessages(['write_off'=>'At least one supporting document is required before approval.']);
            }

            $loan = Loan::query()->with('branch.business')->lockForUpdate()->findOrFail($record->loan_id);
            if ($loan->status !== 'active') {
                throw ValidationException::withMessages(['loan'=>'The loan is no longer active.']);
            }
            if ($loan->branch->business->maker_checker_enabled && (int) $record->requested_by === (int) $user->id) {
                throw ValidationException::withMessages(['write_off'=>'Maker-checker is enabled. The employee who requested this write-off cannot approve it.']);
            }

            $current = $this->components($loan);
            $expected = [
                'principal'=>$this->money->toMinor((string) $record->principal_outstanding),
                'interest'=>$this->money->toMinor((string) $record->interest_outstanding),
                'fees'=>$this->money->toMinor((string) $record->fees_outstanding),
                'penalties'=>$this->money->toMinor((string) $record->penalties_outstanding),
            ];
            if ($current !== $expected || array_sum($current) !== $this->money->toMinor((string) $record->write_off_amount)) {
                throw ValidationException::withMessages([
                    'write_off'=>'The loan balance changed after this write-off request. Create a new request using the current balance.',
                ]);
            }

            $approvedAt = now();
            $journal = $this->accounting->postWriteOff($record,$approvedAt,$user);
            $record->update([
                'status'=>'approved','approved_by'=>$user->id,'approved_at'=>$approvedAt,'decision_comments'=>$comments,
                'accounting_journal_number'=>$journal,
            ]);
            $loan->update(['status'=>'written_off','written_off_at'=>$approvedAt,'written_off_by'=>$user->id,'updated_by'=>$user->id]);

            LoanStatusHistory::create([
                'loan_id'=>$loan->id,'from_status'=>'active','to_status'=>'written_off',
                'notes'=>'Loan written off under '.$record->write_off_number.'. Outstanding history remains preserved for recovery.',
                'changed_by'=>$user->id,'changed_at'=>$approvedAt,
            ]);
            CustomerStatementEntry::create([
                'customer_id'=>$loan->customer_id,'loan_id'=>$loan->id,'branch_id'=>$loan->branch_id,'entry_date'=>$approvedAt,
                'reference'=>$record->write_off_number,'type'=>'loan_write_off',
                'description'=>'Loan moved to written-off recovery tracking: '.$record->reason,
                'debit'=>'0.00','credit'=>'0.00','running_balance'=>$record->recoverable_balance,
                'related_type'=>LoanWriteOff::class,'related_id'=>$record->id,
            ]);
            $this->creditScores->afterCommitForLoan($loan, 'loan_written_off');

            return $record->fresh(['loan.customer','branch','requester','approver','documents.uploader','recoveries']);
        });
    }

    public function reject(LoanWriteOff $writeOff, User $user, string $comments): LoanWriteOff
    {
        if (! $user->can('loans.writeoff.approve')) {
            throw ValidationException::withMessages(['write_off'=>'You are not authorized to decide a loan write-off.']);
        }

        return DB::transaction(function () use ($writeOff,$user,$comments): LoanWriteOff {
            $record = LoanWriteOff::query()->lockForUpdate()->findOrFail($writeOff->id);
            if ($record->status !== 'pending') {
                throw ValidationException::withMessages(['write_off'=>'Only a pending write-off request can be rejected.']);
            }
            if (! $user->canAccessBranch($record->branch_id)) {
                throw ValidationException::withMessages(['write_off'=>'You do not have access to this branch.']);
            }
            $comments = trim($comments);
            if ($comments === '') {
                throw ValidationException::withMessages(['comments'=>'A rejection reason is required.']);
            }
            $record->update([
                'status'=>'rejected','rejected_by'=>$user->id,'rejected_at'=>now(),'decision_comments'=>$comments,
            ]);

            return $record->fresh(['loan.customer','branch','requester','rejecter','documents.uploader']);
        });
    }

    /** @return array{principal:int,interest:int,fees:int,penalties:int} */
    private function components(Loan $loan): array
    {
        $components = [
            'principal'=>$this->money->toMinor((string) ($loan->principal_outstanding ?? '0')),
            'interest'=>$this->money->toMinor((string) ($loan->interest_outstanding ?? '0')),
            'fees'=>$this->money->toMinor((string) ($loan->fees_outstanding ?? '0')),
            'penalties'=>$this->money->toMinor((string) ($loan->penalties_outstanding ?? '0')),
        ];
        foreach ($components as $value) {
            if ($value < 0) {
                throw ValidationException::withMessages(['loan'=>'Loan outstanding components cannot be negative.']);
            }
        }
        $total = array_sum($components);
        if ($total <= 0 || $total !== $this->money->toMinor((string) ($loan->total_outstanding ?? '0'))) {
            throw ValidationException::withMessages(['loan'=>'The current loan outstanding components do not reconcile.']);
        }

        return $components;
    }

    private function assertBranch(Loan $loan, User $user): void
    {
        if (! $user->canAccessBranch($loan->branch_id)) {
            throw ValidationException::withMessages(['loan'=>'You do not have access to this loan branch.']);
        }
    }

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

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