<?php

namespace App\Http\Controllers;

use App\Models\Branch;
use App\Models\FinancialAccount;
use App\Models\Loan;
use App\Models\LoanWriteOff;
use App\Models\LoanWriteOffDocument;
use App\Models\LoanWriteOffRecovery;
use App\Rules\SameBranchAs;
use App\Services\AuditService;
use App\Services\LoanWriteOffRecoveryService;
use App\Services\LoanWriteOffService;
use App\Services\SecureFileService;
use Carbon\CarbonImmutable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Throwable;

class LoanWriteOffController extends Controller
{
    public function index(Request $request): View
    {
        $branchIds = $this->allowedBranchIds($request);
        $search = trim((string) $request->string('search'));

        $eligible = Loan::query()->with(['customer','branch','product'])
            ->whereIn('branch_id',$branchIds)->where('status','active')->where('total_outstanding','>',0)
            ->whereDoesntHave('writeOffs', fn ($query) => $query->whereIn('status',['pending','approved']))
            ->when($search !== '', fn ($query) => $query->where(function ($sub) use ($search): void {
                $sub->where('loan_number','like',"%{$search}%")
                    ->orWhereHas('customer', fn ($customer) => $customer->where('customer_number','like',"%{$search}%")
                        ->orWhere('first_name','like',"%{$search}%")->orWhere('last_name','like',"%{$search}%")->orWhere('phone','like',"%{$search}%"));
            }))->latest('id')->paginate(15,['*'],'eligible_page')->withQueryString();

        $writeOffs = LoanWriteOff::query()->with(['loan.customer','branch','requester','approver','rejecter'])
            ->whereIn('branch_id',$branchIds)
            ->when($search !== '', fn ($query) => $query->where(function ($sub) use ($search): void {
                $sub->where('write_off_number','like',"%{$search}%")
                    ->orWhereHas('loan', fn ($loan) => $loan->where('loan_number','like',"%{$search}%")
                        ->orWhereHas('customer', fn ($customer) => $customer->where('customer_number','like',"%{$search}%")
                            ->orWhere('first_name','like',"%{$search}%")->orWhere('last_name','like',"%{$search}%")));
            }))->latest('id')->paginate(15,['*'],'write_off_page')->withQueryString();

        $recoveries = LoanWriteOffRecovery::query()->with(['writeOff','loan.customer','branch','financialAccount','receiver'])
            ->whereIn('branch_id',$branchIds)
            ->when($search !== '', fn ($query) => $query->where(function ($sub) use ($search): void {
                $sub->where('recovery_number','like',"%{$search}%")
                    ->orWhere('reference','like',"%{$search}%")
                    ->orWhereHas('loan', fn ($loan) => $loan->where('loan_number','like',"%{$search}%")
                        ->orWhereHas('customer', fn ($customer) => $customer->where('customer_number','like',"%{$search}%")
                            ->orWhere('first_name','like',"%{$search}%")->orWhere('last_name','like',"%{$search}%")));
            }))->latest('recovery_date')->latest('id')->paginate(15,['*'],'recovery_page')->withQueryString();

        return view('loan-write-offs.index',compact('eligible','writeOffs','recoveries'));
    }

    public function create(Request $request, Loan $loan): View
    {
        $this->assertBranchAccess($request,$loan->branch_id);
        abort_unless($loan->status === 'active',409,'Only active loans can be submitted for write-off.');
        abort_if($loan->writeOffs()->whereIn('status',['pending','approved'])->exists(),409,'This loan already has an unresolved or approved write-off.');
        $loan->load(['customer','branch.business','product','payments','collaterals.category','collectionFollowUps']);

        return view('loan-write-offs.create',compact('loan'));
    }

    public function store(
        Request $request,
        Loan $loan,
        LoanWriteOffService $service,
        SecureFileService $files
    ): RedirectResponse {
        $this->assertBranchAccess($request,$loan->branch_id);
        $data = $request->validate([
            'write_off_amount'=>['required','regex:/^\d+(?:\.\d{1,2})?$/'],
            'reason'=>['required','string','max:4000'],
            'documents'=>['required','array','min:1','max:10'],
            'documents.*'=>['required','file','mimes:jpg,jpeg,png,webp,pdf,doc,docx','max:8192'],
        ]);

        $storedPaths = [];
        try {
            $record = DB::transaction(function () use ($request,$loan,$service,$files,$data,&$storedPaths): LoanWriteOff {
                $record = $service->request($loan,$data['write_off_amount'],$data['reason'],$request->user());
                foreach ($request->file('documents',[]) as $file) {
                    $path = $files->store($file,'loan-write-offs/'.$record->id);
                    $storedPaths[] = $path;
                    LoanWriteOffDocument::create([
                        'loan_write_off_id'=>$record->id,'document_type'=>'Supporting document',
                        'original_name'=>$file->getClientOriginalName(),'file_path'=>$path,'mime_type'=>$file->getMimeType(),
                        'file_size'=>$file->getSize(),'uploaded_by'=>$request->user()->id,
                    ]);
                }

                return $record;
            });
        } catch (Throwable $exception) {
            foreach ($storedPaths as $path) {
                $files->delete($path);
            }
            throw $exception;
        }

        AuditService::record('loan_write_off.requested',$record,[],[
            'loan_id'=>$record->loan_id,'write_off_amount'=>(string) $record->write_off_amount,'reason'=>$record->reason,
            'documents_count'=>$record->documents()->count(),'principal_outstanding'=>(string) $record->principal_outstanding,
            'interest_outstanding'=>(string) $record->interest_outstanding,'fees_outstanding'=>(string) $record->fees_outstanding,
            'penalties_outstanding'=>(string) $record->penalties_outstanding,
        ],$record->branch_id);

        return redirect()->route('loan-write-offs.show',$record)
            ->with('success','Write-off request created with supporting evidence. The loan remains active until management approval.');
    }

    public function show(Request $request, LoanWriteOff $writeOff): View
    {
        $this->assertBranchAccess($request,$writeOff->branch_id);
        $writeOff->load([
            'loan.customer','loan.product','loan.payments.receiver','loan.collaterals.category','loan.collectionFollowUps.employee',
            'branch.business','requester','approver','rejecter','documents.uploader',
            'recoveries.financialAccount','recoveries.receiver','recoveries.accountTransaction',
        ]);

        return view('loan-write-offs.show',compact('writeOff'));
    }

    public function decision(
        Request $request,
        LoanWriteOff $writeOff,
        LoanWriteOffService $service
    ): RedirectResponse {
        $this->assertBranchAccess($request,$writeOff->branch_id);
        $data = $request->validate([
            'decision'=>['required',Rule::in(['approve','reject'])],
            'comments'=>['nullable','required_if:decision,reject','string','max:2000'],
        ]);
        $before = $writeOff->only(['status','approved_by','approved_at','rejected_by','rejected_at','decision_comments']);
        $updated = $data['decision'] === 'approve'
            ? $service->approve($writeOff,$request->user(),$data['comments'] ?? null)
            : $service->reject($writeOff,$request->user(),$data['comments']);

        AuditService::record('loan_write_off.'.$updated->status,$updated,$before,$updated->only([
            'status','approved_by','approved_at','rejected_by','rejected_at','decision_comments','accounting_journal_number',
        ]),$updated->branch_id);

        return back()->with('success',$updated->status === 'approved'
            ? 'Write-off approved. The loan is now Written Off and its outstanding history is preserved for recovery.'
            : 'Write-off request rejected. The active loan and repayment history were not changed.');
    }

    public function documentDownload(
        Request $request,
        LoanWriteOff $writeOff,
        LoanWriteOffDocument $document,
        SecureFileService $files
    ): StreamedResponse {
        $this->assertBranchAccess($request,$writeOff->branch_id);
        abort_unless($document->loan_write_off_id === $writeOff->id,404);

        return $files->download($document->file_path,$document->original_name);
    }

    public function recoveryForm(Request $request, LoanWriteOff $writeOff): View
    {
        $this->assertBranchAccess($request,$writeOff->branch_id);
        abort_unless($writeOff->status === 'approved',409,'Only an approved write-off can receive recoveries.');
        abort_unless((float) $writeOff->recoverable_balance > 0,409,'This written-off loan has already been fully recovered.');
        $writeOff->load(['loan.customer','branch.business','recoveries.financialAccount']);
        $accounts = FinancialAccount::query()->where('branch_id',$writeOff->branch_id)->where('status','active')->orderBy('name')->get();

        return view('loan-write-offs.recovery',compact('writeOff','accounts'));
    }

    public function recover(
        Request $request,
        LoanWriteOff $writeOff,
        LoanWriteOffRecoveryService $service
    ): RedirectResponse {
        $this->assertBranchAccess($request,$writeOff->branch_id);
        $data = $request->validate([
            'financial_account_id'=>['required','integer','exists:financial_accounts,id'],
            'amount'=>['required','regex:/^\d+(?:\.\d{1,2})?$/'],
            'date'=>['required','date'],
            'method'=>['required',Rule::in(['cash','bank','mobile_money','other'])],
            'reference'=>['nullable','string','max:150'],
            'notes'=>['nullable','string','max:2000'],
        ]);
        $account = FinancialAccount::findOrFail($data['financial_account_id']);
        $this->assertBranchAccess($request,$account->branch_id);
        $request->validate([
            'financial_account_id'=>[
                new SameBranchAs('financial_accounts',(int)$writeOff->branch_id,'The recovery account must belong to the written-off loan branch.'),
            ],
        ]);

        $recovery = $service->recover(
            $writeOff,$account,$data['amount'],CarbonImmutable::parse($data['date']),$data['method'],
            $data['reference'] ?? null,$data['notes'] ?? null,$request->user()
        );
        AuditService::record('loan_write_off.recovery_posted',$recovery,[],[
            'loan_write_off_id'=>$recovery->loan_write_off_id,'loan_id'=>$recovery->loan_id,'amount'=>(string) $recovery->amount,
            'financial_account_id'=>$recovery->financial_account_id,'recovery_date'=>$recovery->recovery_date?->toDateTimeString(),
            'remaining_recoverable'=>(string) $writeOff->fresh()->recoverable_balance,
        ],$recovery->branch_id);

        return redirect()->route('loan-write-offs.show',$writeOff)
            ->with('success','Written-off loan recovery posted successfully. Original write-off history remains unchanged.');
    }

    private function allowedBranchIds(Request $request)
    {
        return collect(app(\App\Services\BranchAccessService::class)->accessibleBranchIds($request->user()));
    }

    private function assertBranchAccess(Request $request, int $branchId): void
    {
        abort_unless($request->user()->canAccessBranch($branchId),403);
    }
}
