<?php

namespace App\Http\Controllers\Finance;

use App\Http\Controllers\Controller;
use App\Models\Branch;
use App\Models\Contract;
use App\Models\Party;
use App\Models\Quotation;
use App\Models\ServiceType;
use App\Models\TaxCode;
use App\Models\TransportJob;
use App\Models\TransportRoute;
use App\Services\Audit\AuditLogger;
use App\Services\Documents\DocumentNumberService;
use App\Services\Documents\PdfService;
use App\Support\Decimal;
use DomainException;
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\Response;
use Throwable;

final class QuotationController extends Controller
{
    public function __construct(
        private readonly DocumentNumberService $numbers,
        private readonly PdfService $pdf,
        private readonly AuditLogger $audit,
    ) {}

    public function index(Request $request): View
    {
        $this->permission($request, 'view');
        $query=Quotation::query()->with(['party','branch','convertedJob'])->where('company_id',$request->user()->company_id);
        $this->scope($request,$query);
        if($search=trim((string)$request->query('search')))$query->where(fn($q)=>$q->where('quotation_number','like',"%{$search}%")->orWhereHas('party',fn($q)=>$q->where('legal_name','like',"%{$search}%")));
        if($status=$request->query('status'))$query->where('status',$status);
        $quotations=$query->latest('quotation_date')->latest('id')->paginate(25)->withQueryString();
        return view('quotations.index',compact('quotations'));
    }

    public function create(Request $request): View
    {
        $this->permission($request,'create');
        $quotation=new Quotation(['branch_id'=>$request->user()->branch_id,'quotation_date'=>today(),'expiry_date'=>today()->addDays(30),'currency_code'=>'AED','exchange_rate'=>'1','pricing_mode'=>'exclusive','status'=>'draft','version'=>1]);
        $quotation->setRelation('items',collect());
        return $this->form($request,$quotation);
    }

    public function store(Request $request): RedirectResponse
    {
        $this->permission($request,'create');
        [$header,$items]=$this->validated($request);
        $header+=['company_id'=>$request->user()->company_id,'created_by'=>$request->user()->id,'status'=>'draft','version'=>1];
        $header['quotation_number']=$this->numbers->next((int)$header['company_id'],(int)$header['branch_id'],'QTN',$header['quotation_date']);
        $quotation=DB::transaction(function()use($header,$items){$quotation=Quotation::query()->create($header);foreach($items as $i=>$item)$quotation->items()->create(['line_number'=>$i+1,...$item]);return$quotation;});
        $this->audit->log('created','quotations',$quotation,[],['quotation_number'=>$quotation->quotation_number,'total'=>$quotation->grand_total]);
        return redirect()->route('quotations.show',$quotation)->with('success','Quotation draft created.');
    }

    public function show(Request $request,Quotation $quotation): View
    {
        $this->access($request,$quotation,'view');$quotation->load(['company','branch','party','contract','convertedJob','creator','approver','items.serviceType','items.route','items.taxCode']);
        return view('quotations.show',compact('quotation'));
    }

    public function edit(Request $request,Quotation $quotation): View
    {
        $this->access($request,$quotation,'edit_draft');$this->editable($quotation);return $this->form($request,$quotation->load('items'));
    }

    public function update(Request $request,Quotation $quotation): RedirectResponse
    {
        $this->access($request,$quotation,'edit_draft');$this->editable($quotation);[$header,$items]=$this->validated($request);$old=$quotation->toArray();
        DB::transaction(function()use($quotation,$header,$items){$quotation->update($header+['status'=>'draft','approved_by'=>null,'approved_at'=>null,'version'=>$quotation->version+1]);$quotation->items()->delete();foreach($items as $i=>$item)$quotation->items()->create(['line_number'=>$i+1,...$item]);});
        $this->audit->log('updated','quotations',$quotation,$old,$quotation->fresh()->toArray());return redirect()->route('quotations.show',$quotation)->with('success','Quotation updated as a new version.');
    }

    public function destroy(Request $request,Quotation $quotation): RedirectResponse
    {
        $this->access($request,$quotation,'delete_draft');$this->editable($quotation);$quotation->delete();return redirect()->route('quotations.index')->with('success','Quotation draft deleted.');
    }

    public function submit(Request $request,Quotation $quotation): RedirectResponse
    {
        $this->access($request,$quotation,'submit');if(!in_array($quotation->status,['draft','rejected'],true))return back()->with('error','Only a draft or rejected quotation can be submitted.');$quotation->update(['status'=>'pending_approval']);$this->audit->log('submitted','quotations',$quotation,[],['status'=>'pending_approval']);return back()->with('success','Quotation submitted for approval.');
    }

    public function approve(Request $request,Quotation $quotation): RedirectResponse
    {
        $this->access($request,$quotation,'approve');if($quotation->status!=='pending_approval')return back()->with('error','Only a pending quotation can be approved.');if(config('accounting.maker_checker')&&(int)$quotation->created_by===(int)$request->user()->id)return back()->with('error','Maker-checker is enabled. The creator cannot approve this quotation.');$quotation->update(['status'=>'approved','approved_by'=>$request->user()->id,'approved_at'=>now()]);$this->audit->log('approved','quotations',$quotation,[],['status'=>'approved']);return back()->with('success','Quotation approved.');
    }

    public function reject(Request $request,Quotation $quotation): RedirectResponse
    {
        $this->access($request,$quotation,'reject');$data=$request->validate(['reason'=>'required|string|max:1000']);if($quotation->status!=='pending_approval')return back()->with('error','Only a pending quotation can be rejected.');$quotation->update(['status'=>'rejected']);$this->audit->log('rejected','quotations',$quotation,[],['status'=>'rejected'],$data['reason']);return back()->with('success','Quotation rejected.');
    }

    public function send(Request $request,Quotation $quotation): RedirectResponse
    {
        $this->access($request,$quotation,'manage');if(!in_array($quotation->status,['approved','sent'],true))return back()->with('error','Approve the quotation before marking it as sent.');$quotation->update(['status'=>'sent','sent_at'=>now()]);$this->audit->log('sent','quotations',$quotation,[],['status'=>'sent']);return back()->with('success','Quotation marked as sent.');
    }

    public function accept(Request $request,Quotation $quotation): RedirectResponse
    {
        $this->access($request,$quotation,'manage');if(!in_array($quotation->status,['approved','sent'],true))return back()->with('error','This quotation cannot be accepted from its current status.');$quotation->update(['status'=>'accepted','accepted_at'=>now()]);$this->audit->log('accepted','quotations',$quotation,[],['status'=>'accepted']);return back()->with('success','Quotation marked as accepted.');
    }

    public function convert(Request $request,Quotation $quotation): RedirectResponse
    {
        $this->access($request,$quotation,'manage');abort_unless($request->user()->hasPermission('transport_jobs.create'),403);
        if($quotation->status!=='accepted')return back()->with('error','Only an accepted quotation can be converted to a job.');
        if($quotation->converted_job_id)return redirect()->route('jobs.show',$quotation->converted_job_id)->with('warning','This quotation has already been converted.');
        $quotation->load('items.route');$first=$quotation->items->first();if(!$first)return back()->with('error','Quotation has no lines.');
        $job=DB::transaction(function()use($request,$quotation,$first){$job=TransportJob::query()->create(['company_id'=>$quotation->company_id,'branch_id'=>$quotation->branch_id,'customer_id'=>$quotation->party_id,'contract_id'=>$quotation->contract_id,'service_type_id'=>$first->service_type_id,'route_id'=>$first->route_id,'job_number'=>$this->numbers->next((int)$quotation->company_id,(int)$quotation->branch_id,'JOB',today()),'job_date'=>today(),'requested_date'=>today(),'pickup_location'=>$first->route?->origin,'delivery_location'=>$first->route?->destination,'cargo_description'=>$first->description,'quantity'=>$first->quantity,'number_of_trips'=>max(1,(int)ceil((float)$first->quantity)),'vehicle_type'=>$first->vehicle_type,'estimated_revenue'=>$quotation->taxable_total,'estimated_cost'=>'0','actual_revenue'=>'0','actual_cost'=>'0','tax_code_id'=>$first->tax_code_id,'status'=>'confirmed','delivery_status'=>'pending','billing_status'=>'not_invoiced','vendor_billing_status'=>'not_billed','customer_notes'=>$quotation->notes,'created_by'=>$request->user()->id]);$quotation->update(['converted_job_id'=>$job->id,'status'=>'converted']);return$job;});
        $this->audit->log('converted','quotations',$quotation,[],['job_id'=>$job->id,'job_number'=>$job->job_number]);return redirect()->route('jobs.show',$job)->with('success','Quotation converted to transport job.');
    }

    public function print(Request $request,Quotation $quotation): View {$this->access($request,$quotation,'print');$quotation->load(['company','branch','party','items.serviceType','items.route']);return view('documents.quotation',compact('quotation'));}
    public function pdf(Request $request,Quotation $quotation): Response {$this->access($request,$quotation,'print');$quotation->load(['company','branch','party','items.serviceType','items.route']);return $this->pdf->download(view('documents.quotation',compact('quotation'))->render(),$quotation->quotation_number.'.pdf');}

    private function form(Request $request,Quotation $quotation): View
    {
        $company=(int)$request->user()->company_id;$branches=Branch::query()->where('company_id',$company)->where('is_active',true)->orderBy('code')->get();$customers=Party::query()->where('company_id',$company)->where('is_active',true)->whereHas('roles',fn($q)=>$q->where('role','customer'))->orderBy('legal_name')->get();$contracts=Contract::query()->where('company_id',$company)->whereIn('status',['active','approved'])->orderBy('contract_number')->get();$services=ServiceType::query()->where('company_id',$company)->where('is_active',true)->orderBy('name')->get();$routes=TransportRoute::query()->where('company_id',$company)->where('is_active',true)->orderBy('name')->get();$taxCodes=TaxCode::query()->where('company_id',$company)->where('is_active',true)->orderBy('code')->get();return view('quotations.form',compact('quotation','branches','customers','contracts','services','routes','taxCodes'));
    }

    private function validated(Request $request): array
    {
        $data=$request->validate(['branch_id'=>'required|integer','party_id'=>'required|integer','contract_id'=>'nullable|integer','quotation_date'=>'required|date','expiry_date'=>'nullable|date|after_or_equal:quotation_date','currency_code'=>'required|string|size:3','exchange_rate'=>'required|numeric|gt:0','pricing_mode'=>['required',Rule::in(['exclusive','inclusive'])],'payment_terms'=>'nullable|string|max:500','terms'=>'nullable|string|max:10000','notes'=>'nullable|string|max:5000','items'=>'required|array|min:1','items.*.service_type_id'=>'nullable|integer','items.*.route_id'=>'nullable|integer','items.*.vehicle_type'=>'nullable|string|max:100','items.*.description'=>'required|string|max:1000','items.*.quantity'=>'required|numeric|gt:0','items.*.unit'=>'nullable|string|max:30','items.*.unit_rate'=>'required|numeric|min:0','items.*.discount_amount'=>'nullable|numeric|min:0','items.*.tax_code_id'=>'nullable|integer']);
        $company=(int)$request->user()->company_id;abort_unless(Branch::query()->where('company_id',$company)->whereKey($data['branch_id'])->exists(),422,'Invalid branch.');abort_unless(Party::query()->where('company_id',$company)->whereKey($data['party_id'])->whereHas('roles',fn($q)=>$q->where('role','customer'))->exists(),422,'Invalid customer.');
        $items=[];$subtotal=Decimal::scale('0');$discountTotal=Decimal::scale('0');$taxableTotal=Decimal::scale('0');$vatTotal=Decimal::scale('0');$grandTotal=Decimal::scale('0');
        foreach($data['items'] as $input){$tax=!empty($input['tax_code_id'])?TaxCode::query()->where('company_id',$company)->findOrFail($input['tax_code_id']):null;$rate=(string)($tax?->rate??'0');$qty=Decimal::scale((string)$input['quantity']);$unitRate=Decimal::scale((string)$input['unit_rate']);$discount=Decimal::scale((string)($input['discount_amount']??0));$before=Decimal::multiply($qty,$unitRate);if(Decimal::compare($discount,$before)>0)throw new DomainException('Line discount cannot exceed the line amount.');$gross=Decimal::subtract($before,$discount);if($data['pricing_mode']==='inclusive'&&Decimal::compare($rate,'0')>0){$factor=Decimal::add('1',Decimal::divide($rate,'100'));$taxable=Decimal::divide($gross,$factor);$vat=Decimal::subtract($gross,$taxable);$lineTotal=$gross;}else{$taxable=$gross;$vat=Decimal::divide(Decimal::multiply($taxable,$rate),'100');$lineTotal=Decimal::add($taxable,$vat);}$items[]=['service_type_id'=>$input['service_type_id']??null,'route_id'=>$input['route_id']??null,'vehicle_type'=>$input['vehicle_type']??null,'description'=>$input['description'],'quantity'=>$qty,'unit'=>$input['unit']??null,'unit_rate'=>$unitRate,'discount_amount'=>$discount,'taxable_amount'=>$taxable,'vat_rate'=>$rate,'vat_amount'=>$vat,'line_total'=>$lineTotal,'tax_code_id'=>$tax?->id];$subtotal=Decimal::add($subtotal,$before);$discountTotal=Decimal::add($discountTotal,$discount);$taxableTotal=Decimal::add($taxableTotal,$taxable);$vatTotal=Decimal::add($vatTotal,$vat);$grandTotal=Decimal::add($grandTotal,$lineTotal);}
        unset($data['items']);$data+=['subtotal'=>$subtotal,'discount_total'=>$discountTotal,'taxable_total'=>$taxableTotal,'vat_total'=>$vatTotal,'grand_total'=>$grandTotal];return[$data,$items];
    }

    private function editable(Quotation $quotation):void{if(!in_array($quotation->status,['draft','rejected'],true))throw new DomainException('Only draft or rejected quotations can be edited.');}
    private function permission(Request $request,string $action):void{abort_unless($request->user()->hasPermission('quotations.'.$action),403);}
    private function access(Request $request,Quotation $quotation,string $action):void{$this->permission($request,$action);abort_unless((int)$quotation->company_id===(int)$request->user()->company_id,404);if($request->user()->branch_id&&!$request->user()->hasPermission('branches.view_all'))abort_unless((int)$quotation->branch_id===(int)$request->user()->branch_id,403);}
    private function scope(Request $request,$query):void{if($request->user()->branch_id&&!$request->user()->hasPermission('branches.view_all'))$query->where('branch_id',$request->user()->branch_id);}
}
