<?php

namespace App\Http\Controllers\Finance;

use App\Enums\DocumentStatus;
use App\Http\Controllers\Controller;
use App\Models\Account;
use App\Models\Branch;
use App\Models\Driver;
use App\Models\DriverAdvance;
use App\Models\DriverSettlement;
use App\Models\Trip;
use App\Services\Accounting\DriverAdvancePostingService;
use App\Services\Accounting\DriverSettlementPostingService;
use App\Services\Documents\DocumentNumberService;
use App\Services\Workflow\ApprovalWorkflowService;
use App\Support\Decimal;
use DomainException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
use Throwable;

final class DriverAdvanceController extends Controller
{
    public function __construct(private readonly DocumentNumberService $numbers,private readonly ApprovalWorkflowService $workflow,private readonly DriverAdvancePostingService $advancePosting,private readonly DriverSettlementPostingService $settlementPosting){}
    public function index(Request $r):View{$this->permission($r,'view');$q=DriverAdvance::with(['driver','branch','settlements'])->where('company_id',$r->user()->company_id);$this->scope($r,$q);if($s=trim((string)$r->query('search')))$q->where(fn($q)=>$q->where('advance_number','like',"%$s%")->orWhereHas('driver',fn($d)=>$d->where('name','like',"%$s%")));$advances=$q->latest('advance_date')->paginate(25)->withQueryString();return view('driver-advances.index',compact('advances'));}
    public function create(Request $r):View{$this->permission($r,'create');$advance=new DriverAdvance(['branch_id'=>$r->user()->branch_id,'advance_date'=>today(),'advance_type'=>'trip','amount'=>'0','settled_amount'=>'0','status'=>DocumentStatus::DRAFT]);return$this->advanceForm($r,$advance);}
    public function store(Request $r):RedirectResponse{$this->permission($r,'create');$d=$this->advanceData($r);$d+=['company_id'=>$r->user()->company_id,'created_by'=>$r->user()->id,'status'=>DocumentStatus::DRAFT,'settled_amount'=>'0'];$d['advance_number']=$this->numbers->next((int)$d['company_id'],(int)$d['branch_id'],'DADV',$d['advance_date']);$a=DriverAdvance::create($d);return redirect()->route('driver-advances.show',$a)->with('success','Driver advance draft created.');}
    public function show(Request $r,DriverAdvance $advance):View{$this->access($r,$advance,'view');$advance->load(['company','branch','driver','trip','advanceAccount','cashBankAccount','settlements.journal.lines.account','journal.lines.account']);return view('driver-advances.show',compact('advance'));}
    public function edit(Request $r,DriverAdvance $advance):View{$this->access($r,$advance,'edit_draft');$this->editable($advance);return$this->advanceForm($r,$advance);}
    public function update(Request $r,DriverAdvance $advance):RedirectResponse{$this->access($r,$advance,'edit_draft');$this->editable($advance);$advance->update($this->advanceData($r)+['status'=>DocumentStatus::DRAFT,'approved_by'=>null,'approved_at'=>null]);return redirect()->route('driver-advances.show',$advance)->with('success','Driver advance updated.');}
    public function destroy(Request $r,DriverAdvance $advance):RedirectResponse{$this->access($r,$advance,'delete_draft');$this->editable($advance);$advance->delete();return redirect()->route('driver-advances.index')->with('success','Driver advance deleted.');}
    public function submit(Request $r,DriverAdvance $advance):RedirectResponse{return$this->transition($r,$advance,'submit',fn()=>$this->workflow->submit($advance),'Advance submitted.');}public function approve(Request $r,DriverAdvance $advance):RedirectResponse{return$this->transition($r,$advance,'approve',fn()=>$this->workflow->approve($advance),'Advance approved.');}public function reject(Request $r,DriverAdvance $advance):RedirectResponse{$r->validate(['reason'=>'required|string|max:1000']);return$this->transition($r,$advance,'reject',fn()=>$this->workflow->reject($advance,$r->string('reason')->toString()),'Advance rejected.');}public function post(Request $r,DriverAdvance $advance):RedirectResponse{return$this->transition($r,$advance,'post',fn()=>$this->advancePosting->post($advance),'Advance posted.');}
    public function settlementCreate(Request $r,DriverAdvance $advance):View{$this->access($r,$advance,'create');if($advance->status!==DocumentStatus::POSTED)abort(422,'Advance must be posted first.');$settlement=new DriverSettlement(['company_id'=>$advance->company_id,'branch_id'=>$advance->branch_id,'driver_advance_id'=>$advance->id,'settlement_date'=>today(),'expense_amount'=>'0','cash_refund_amount'=>'0','total_settled'=>'0','status'=>DocumentStatus::DRAFT]);return$this->settlementForm($r,$advance,$settlement);}
    public function settlementStore(Request $r,DriverAdvance $advance):RedirectResponse{$this->access($r,$advance,'create');$d=$this->settlementData($r,$advance);$d+=['company_id'=>$advance->company_id,'branch_id'=>$advance->branch_id,'driver_advance_id'=>$advance->id,'created_by'=>$r->user()->id,'status'=>DocumentStatus::DRAFT];$d['settlement_number']=$this->numbers->next((int)$advance->company_id,(int)$advance->branch_id,'DSET',$d['settlement_date']);$s=DriverSettlement::create($d);return redirect()->route('driver-advances.settlements.show',[$advance,$s])->with('success','Driver settlement draft created.');}
    public function settlementShow(Request $r,DriverAdvance $advance,DriverSettlement $settlement):View{$this->accessSettlement($r,$advance,$settlement,'view');$settlement->load(['advance.driver','expenseAccount','cashBankAccount','journal.lines.account']);return view('driver-advances.settlement-show',compact('advance','settlement'));}
    public function settlementEdit(Request $r,DriverAdvance $advance,DriverSettlement $settlement):View{$this->accessSettlement($r,$advance,$settlement,'edit_draft');$this->editable($settlement);return$this->settlementForm($r,$advance,$settlement);}
    public function settlementUpdate(Request $r,DriverAdvance $advance,DriverSettlement $settlement):RedirectResponse{$this->accessSettlement($r,$advance,$settlement,'edit_draft');$this->editable($settlement);$settlement->update($this->settlementData($r,$advance)+['status'=>DocumentStatus::DRAFT,'approved_by'=>null,'approved_at'=>null]);return redirect()->route('driver-advances.settlements.show',[$advance,$settlement])->with('success','Settlement updated.');}
    public function settlementDestroy(Request $r,DriverAdvance $advance,DriverSettlement $settlement):RedirectResponse{$this->accessSettlement($r,$advance,$settlement,'delete_draft');$this->editable($settlement);$settlement->delete();return redirect()->route('driver-advances.show',$advance)->with('success','Settlement deleted.');}
    public function settlementSubmit(Request $r,DriverAdvance $advance,DriverSettlement $settlement):RedirectResponse{$this->accessSettlement($r,$advance,$settlement,'submit');try{$this->workflow->submit($settlement);}catch(Throwable $e){return back()->with('error',$e->getMessage());}return back()->with('success','Settlement submitted.');}public function settlementApprove(Request $r,DriverAdvance $advance,DriverSettlement $settlement):RedirectResponse{$this->accessSettlement($r,$advance,$settlement,'approve');try{$this->workflow->approve($settlement);}catch(Throwable $e){return back()->with('error',$e->getMessage());}return back()->with('success','Settlement approved.');}public function settlementReject(Request $r,DriverAdvance $advance,DriverSettlement $settlement):RedirectResponse{$this->accessSettlement($r,$advance,$settlement,'reject');$r->validate(['reason'=>'required|string|max:1000']);try{$this->workflow->reject($settlement,$r->string('reason')->toString());}catch(Throwable $e){return back()->with('error',$e->getMessage());}return back()->with('success','Settlement rejected.');}public function settlementPost(Request $r,DriverAdvance $advance,DriverSettlement $settlement):RedirectResponse{$this->accessSettlement($r,$advance,$settlement,'post');try{$this->settlementPosting->post($settlement);}catch(Throwable $e){return back()->with('error',$e->getMessage());}return back()->with('success','Settlement posted.');}
    private function transition(Request $r,DriverAdvance $a,string $action,callable $fn,string $message):RedirectResponse{$this->access($r,$a,$action);try{$fn();}catch(Throwable $e){return back()->with('error',$e->getMessage());}return back()->with('success',$message);}
    private function advanceForm(Request $request, DriverAdvance $advance): View
    {
        $companyId = (int) $request->user()->company_id;
        $branches = Branch::query()->where('company_id', $companyId)->where('is_active', true);
        $drivers = Driver::query()->where('company_id', $companyId)->where('is_active', true);
        $trips = Trip::query()->where('company_id', $companyId);
        if ($request->user()->branch_id && ! $request->user()->hasPermission('branches.view_all')) {
            $branchId = (int) $request->user()->branch_id;
            $branches->whereKey($branchId);
            $drivers->where(fn ($query) => $query->whereNull('branch_id')->orWhere('branch_id', $branchId));
            $trips->where('branch_id', $branchId);
        }
        $branches = $branches->orderBy('code')->get();
        $drivers = $drivers->orderBy('name')->get();
        $trips = $trips->latest('id')->limit(1000)->get();
        $accounts = Account::query()->where('company_id', $companyId)->where('is_active', true)->orderBy('code')->get();

        return view('driver-advances.form', compact('advance', 'branches', 'drivers', 'trips', 'accounts'));
    }
    private function settlementForm(Request $r,DriverAdvance $advance,DriverSettlement $settlement):View{$accounts=Account::where('company_id',$r->user()->company_id)->where('is_active',true)->orderBy('code')->get();$open=Decimal::subtract($advance->amount,$advance->settled_amount);return view('driver-advances.settlement-form',compact('advance','settlement','accounts','open'));}
    private function advanceData(Request $request): array
    {
        $data = $request->validate([
            'branch_id' => 'required|integer',
            'driver_id' => 'required|integer',
            'trip_id' => 'nullable|integer',
            'advance_date' => 'required|date',
            'advance_type' => 'required|in:cash,bank,trip,salary',
            'amount' => 'required|numeric|gt:0',
            'advance_account_id' => 'required|integer',
            'cash_bank_account_id' => 'required|integer',
            'description' => 'nullable|string|max:2000',
        ]);

        $companyId = (int) $request->user()->company_id;
        $branchId = (int) $data['branch_id'];
        abort_unless(Branch::query()->where('company_id', $companyId)->whereKey($branchId)->where('is_active', true)->exists(), 422, 'Invalid branch.');
        if ($request->user()->branch_id && ! $request->user()->hasPermission('branches.view_all')) {
            abort_unless($branchId === (int) $request->user()->branch_id, 403, 'You cannot create a driver advance for another branch.');
        }

        abort_unless(Driver::query()
            ->where('company_id', $companyId)
            ->whereKey($data['driver_id'])
            ->where('is_active', true)
            ->where(fn ($query) => $query->whereNull('branch_id')->orWhere('branch_id', $branchId))
            ->exists(), 422, 'Invalid driver.');

        if (! empty($data['trip_id'])) {
            $trip = Trip::query()->where('company_id', $companyId)->where('branch_id', $branchId)->find($data['trip_id']);
            abort_unless($trip, 422, 'Invalid trip.');
            if ($trip->driver_id) {
                abort_unless((int) $trip->driver_id === (int) $data['driver_id'], 422, 'The selected trip is assigned to another driver.');
            }
        }

        $accountIds = collect([$data['advance_account_id'], $data['cash_bank_account_id']])->unique();
        abort_unless(Account::query()->where('company_id', $companyId)->where('is_active', true)->whereIn('id', $accountIds)->count() === $accountIds->count(), 422, 'Invalid account.');
        abort_unless((int) $data['advance_account_id'] !== (int) $data['cash_bank_account_id'], 422, 'Advance and cash/bank accounts must be different.');
        $data['amount'] = Decimal::scale((string) $data['amount']);

        return $data;
    }
    private function settlementData(Request $request, DriverAdvance $advance): array
    {
        $data = $request->validate([
            'settlement_date' => 'required|date',
            'expense_amount' => 'required|numeric|min:0',
            'cash_refund_amount' => 'required|numeric|min:0',
            'expense_account_id' => 'nullable|integer',
            'cash_bank_account_id' => 'nullable|integer',
            'description' => 'nullable|string|max:2000',
        ]);
        $data['expense_amount'] = Decimal::scale((string) $data['expense_amount']);
        $data['cash_refund_amount'] = Decimal::scale((string) $data['cash_refund_amount']);
        $data['total_settled'] = Decimal::add($data['expense_amount'], $data['cash_refund_amount']);
        if (Decimal::compare($data['total_settled'], '0') <= 0) {
            throw new DomainException('Enter an expense or cash refund amount.');
        }
        if ($data['settlement_date'] < $advance->advance_date->format('Y-m-d')) {
            throw new DomainException('Settlement date cannot be before the advance date.');
        }
        if (Decimal::compare($data['expense_amount'], '0') > 0 && empty($data['expense_account_id'])) {
            throw new DomainException('Expense account is required for driver expenses.');
        }
        if (Decimal::compare($data['cash_refund_amount'], '0') > 0 && empty($data['cash_bank_account_id'])) {
            throw new DomainException('Cash/bank account is required for a driver refund.');
        }

        $accountIds = collect([$data['expense_account_id'] ?? null, $data['cash_bank_account_id'] ?? null])->filter()->unique();
        abort_unless(Account::query()->where('company_id', $advance->company_id)->where('is_active', true)->whereIn('id', $accountIds)->count() === $accountIds->count(), 422, 'Invalid settlement account.');
        abort_unless(! $accountIds->contains((int) $advance->advance_account_id), 422, 'Settlement debit accounts cannot be the driver advance account.');

        $open = Decimal::subtract((string) $advance->amount, (string) $advance->settled_amount);
        if (Decimal::compare($data['total_settled'], $open) > 0) {
            throw new DomainException('Settlement exceeds the open advance.');
        }

        return $data;
    }
    private function editable($model):void{$s=$model->status instanceof DocumentStatus?$model->status:DocumentStatus::from((string)$model->status);if(!in_array($s,[DocumentStatus::DRAFT,DocumentStatus::REJECTED],true))throw new DomainException('Only draft or rejected records can be edited.');}
    private function permission(Request $r,string $action):void{abort_unless($r->user()->hasPermission('driver_advances.'.$action),403);}private function access(Request $r,DriverAdvance $a,string $action):void{$this->permission($r,$action);abort_unless((int)$a->company_id===(int)$r->user()->company_id,404);if($r->user()->branch_id&&!$r->user()->hasPermission('branches.view_all'))abort_unless((int)$a->branch_id===(int)$r->user()->branch_id,403);}private function accessSettlement(Request $r,DriverAdvance $a,DriverSettlement $s,string $action):void{$this->access($r,$a,$action);abort_unless((int)$s->driver_advance_id===(int)$a->id,404);}private function scope(Request $r,$q):void{if($r->user()->branch_id&&!$r->user()->hasPermission('branches.view_all'))$q->where('branch_id',$r->user()->branch_id);}
}
