<?php

namespace App\Http\Controllers\Operations;

use App\Http\Controllers\Controller;
use App\Models\Branch;
use App\Models\Contract;
use App\Models\JobCharge;
use App\Models\JobCost;
use App\Models\Party;
use App\Models\ProofOfDelivery;
use App\Models\RateCard;
use App\Models\ServiceType;
use App\Models\TaxCode;
use App\Models\TransportJob;
use App\Models\TransportRoute;
use App\Models\User;
use App\Services\Documents\DocumentNumberService;
use App\Services\Operations\JobProfitabilityService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

final class TransportJobController extends Controller
{
    public function __construct(
        private readonly DocumentNumberService $numbers,
        private readonly JobProfitabilityService $profitability,
    ) {}

    public function index(Request $request): View
    {
        $this->permission($request, 'view');
        $query = TransportJob::query()->with(['customer','branch','route','serviceType'])->where('company_id', $request->user()->company_id);
        $this->scopeBranch($request, $query);
        if ($search = trim((string) $request->query('search'))) {
            $query->where(function ($query) use ($search): void {
                $query->where('job_number','like',"%{$search}%")
                    ->orWhere('customer_po','like',"%{$search}%")
                    ->orWhere('pickup_location','like',"%{$search}%")
                    ->orWhere('delivery_location','like',"%{$search}%")
                    ->orWhereHas('customer', fn ($q) => $q->where('legal_name','like',"%{$search}%"));
            });
        }
        foreach (['status','delivery_status','billing_status'] as $field) {
            if ($value = $request->query($field)) { $query->where($field, $value); }
        }
        $jobs = $query->latest('job_date')->latest('id')->paginate(25)->withQueryString();
        return view('jobs.index', compact('jobs'));
    }

    public function create(Request $request): View
    {
        $this->permission($request, 'create');
        $job = new TransportJob([
            'branch_id'=>$request->user()->branch_id,
            'job_date'=>today(), 'requested_date'=>today(), 'quantity'=>'1.0000', 'number_of_trips'=>1,
            'status'=>'enquiry', 'delivery_status'=>'pending', 'billing_status'=>'not_invoiced', 'vendor_billing_status'=>'not_billed',
        ]);
        return $this->form($request, $job);
    }

    public function store(Request $request): RedirectResponse
    {
        $this->permission($request, 'create');
        $data = $this->validated($request);
        $data += ['company_id'=>$request->user()->company_id, 'created_by'=>$request->user()->id];
        $data['job_number'] = $this->numbers->next((int)$data['company_id'], (int)$data['branch_id'], 'JOB', $data['job_date']);
        $job = TransportJob::query()->create($data);
        return redirect()->route('jobs.show',$job)->with('success','Transport job created.');
    }

    public function show(Request $request, TransportJob $job): View
    {
        $this->access($request,$job,'view');
        $this->profitability->recalculate($job);
        $job->load(['customer','branch','contract','serviceType','route','taxCode','assignedUser','trips.vehicle','trips.driver','trips.vendor','charges.taxCode','costs.vendor','costs.trip','pods','invoices','vendorBills']);
        return view('jobs.show', compact('job'));
    }

    public function edit(Request $request, TransportJob $job): View
    {
        $this->access($request,$job,'edit_draft');
        abort_if(in_array($job->status,['closed','cancelled'],true),422,'Closed or cancelled jobs cannot be edited.');
        return $this->form($request,$job);
    }

    public function update(Request $request, TransportJob $job): RedirectResponse
    {
        $this->access($request,$job,'edit_draft');
        abort_if(in_array($job->status,['closed','cancelled'],true),422,'Closed or cancelled jobs cannot be edited.');
        $job->update($this->validated($request));
        $this->profitability->recalculate($job);
        return redirect()->route('jobs.show',$job)->with('success','Transport job updated.');
    }

    public function destroy(Request $request, TransportJob $job): RedirectResponse
    {
        $this->access($request,$job,'delete_draft');
        abort_if($job->trips()->exists() || $job->invoices()->exists() || $job->vendorBills()->exists(),422,'A job with trips or financial documents cannot be deleted.');
        $job->delete();
        return redirect()->route('jobs.index')->with('success','Transport job deleted.');
    }

    public function status(Request $request, TransportJob $job): RedirectResponse
    {
        $this->access($request,$job,'edit_draft');
        $data=$request->validate([
            'status'=>['required',Rule::in(['enquiry','quotation','confirmed','scheduled','vehicle_assigned','driver_assigned','in_transit','reached_destination','delivered','pod_received','ready_to_invoice','invoiced','closed','cancelled'])],
            'delivery_status'=>['nullable',Rule::in(['pending','in_transit','delivered','failed','cancelled'])],
        ]);
        $job->update($data);
        return back()->with('success','Job status updated.');
    }

    public function storeCharge(Request $request, TransportJob $job): RedirectResponse
    {
        $this->access($request,$job,'edit_draft');
        $data=$request->validate(['charge_type'=>'required|string|max:50','description'=>'nullable|string|max:500','amount'=>'required|numeric|min:0','tax_code_id'=>'nullable|integer','is_billable'=>'nullable|boolean']);
        if (! empty($data['tax_code_id'])) {
            abort_unless(TaxCode::query()->where('company_id', $request->user()->company_id)->whereKey($data['tax_code_id'])->exists(), 422, 'Invalid tax code.');
        }
        $data['is_billable']=$request->boolean('is_billable');
        $job->charges()->create($data); $this->profitability->recalculate($job);
        return back()->with('success','Job charge added.');
    }

    public function destroyCharge(Request $request, TransportJob $job, JobCharge $charge): RedirectResponse
    {
        $this->access($request,$job,'edit_draft'); abort_unless((int)$charge->transport_job_id===(int)$job->id,404);
        $charge->delete(); $this->profitability->recalculate($job);
        return back()->with('success','Job charge removed.');
    }

    public function storeCost(Request $request, TransportJob $job): RedirectResponse
    {
        $this->access($request,$job,'edit_draft');
        $data=$request->validate(['trip_id'=>'nullable|integer','vendor_id'=>'nullable|integer','cost_type'=>'required|string|max:50','description'=>'nullable|string|max:500','amount'=>'required|numeric|min:0','tax_code_id'=>'nullable|integer','is_accrued'=>'nullable|boolean']);
        if (! empty($data['trip_id'])) {
            abort_unless($job->trips()->whereKey($data['trip_id'])->exists(), 422, 'The selected trip does not belong to this job.');
        }
        if (! empty($data['vendor_id'])) {
            abort_unless(Party::query()->where('company_id', $request->user()->company_id)->whereKey($data['vendor_id'])->whereHas('roles', fn ($query) => $query->whereIn('role', ['vendor', 'subcontractor', 'vehicle_owner']))->exists(), 422, 'Invalid vendor.');
        }
        if (! empty($data['tax_code_id'])) {
            abort_unless(TaxCode::query()->where('company_id', $request->user()->company_id)->whereKey($data['tax_code_id'])->exists(), 422, 'Invalid tax code.');
        }
        $data['is_accrued']=$request->boolean('is_accrued');
        $job->costs()->create($data); $this->profitability->recalculate($job);
        return back()->with('success','Job cost added.');
    }

    public function destroyCost(Request $request, TransportJob $job, JobCost $cost): RedirectResponse
    {
        $this->access($request,$job,'edit_draft'); abort_unless((int)$cost->transport_job_id===(int)$job->id,404);
        $cost->delete(); $this->profitability->recalculate($job);
        return back()->with('success','Job cost removed.');
    }

    public function storePod(Request $request, TransportJob $job): RedirectResponse
    {
        $this->access($request,$job,'edit_draft');
        $data=$request->validate(['trip_id'=>'nullable|integer','received_date'=>'nullable|date','received_by'=>'nullable|string|max:150','signature_name'=>'nullable|string|max:150','remarks'=>'nullable|string|max:1000','file'=>'required|file|mimes:pdf,jpg,jpeg,png,webp|max:10240']);
        if (! empty($data['trip_id'])) {
            abort_unless($job->trips()->whereKey($data['trip_id'])->exists(), 422, 'The selected trip does not belong to this job.');
        }
        $path=$request->file('file')->store('private/pod/'.$request->user()->company_id.'/'.$job->public_id,'local');
        $pod=$job->pods()->create([
            'trip_id'=>$data['trip_id']??null,'received_date'=>$data['received_date']??today(),'received_by'=>$data['received_by']??null,'signature_name'=>$data['signature_name']??null,'remarks'=>$data['remarks']??null,'file_path'=>$path,'uploaded_by'=>$request->user()->id,
        ]);
        if ($job->status==='delivered') { $job->update(['status'=>'pod_received']); }
        return back()->with('success','Proof of delivery uploaded.');
    }

    public function downloadPod(Request $request, TransportJob $job, ProofOfDelivery $pod): BinaryFileResponse
    {
        $this->access($request,$job,'view'); abort_unless((int)$pod->transport_job_id===(int)$job->id,404); abort_unless($pod->file_path && Storage::disk('local')->exists($pod->file_path),404);
        return response()->download(Storage::disk('local')->path($pod->file_path));
    }

    public function destroyPod(Request $request, TransportJob $job, ProofOfDelivery $pod): RedirectResponse
    {
        $this->access($request,$job,'edit_draft'); abort_unless((int)$pod->transport_job_id===(int)$job->id,404);
        if($pod->file_path){Storage::disk('local')->delete($pod->file_path);} $pod->delete();
        return back()->with('success','POD removed.');
    }

    public function suggestRate(Request $request)
    {
        $this->permission($request,'view');
        $data=$request->validate(['party_id'=>'required|integer','side'=>['required',Rule::in(['customer','vendor'])],'service_type_id'=>'nullable|integer','route_id'=>'nullable|integer','vehicle_type'=>'nullable|string','date'=>'nullable|date']);
        abort_unless(Party::query()->where('company_id', $request->user()->company_id)->whereKey($data['party_id'])->exists(), 422, 'Invalid party.');
        $date=$data['date']??today()->toDateString();
        $rate=RateCard::query()->where('company_id',$request->user()->company_id)->where('party_id',$data['party_id'])->where('rate_side',$data['side'])->where('is_active',true)
            ->when($data['service_type_id']??null,fn($q,$v)=>$q->where(fn($q)=>$q->whereNull('service_type_id')->orWhere('service_type_id',$v)))
            ->when($data['route_id']??null,fn($q,$v)=>$q->where(fn($q)=>$q->whereNull('route_id')->orWhere('route_id',$v)))
            ->when($data['vehicle_type']??null,fn($q,$v)=>$q->where(fn($q)=>$q->whereNull('vehicle_type')->orWhere('vehicle_type',$v)))
            ->whereDate('effective_from','<=',$date)->where(fn($q)=>$q->whereNull('effective_to')->orWhereDate('effective_to','>=',$date))->latest('effective_from')->first();
        return response()->json(['rate'=>$rate?->rate,'minimum_charge'=>$rate?->minimum_charge,'rate_card_id'=>$rate?->id]);
    }

    private function form(Request $request, TransportJob $job): 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();
        $users=User::query()->where('company_id',$company)->where('is_active',true)->orderBy('name')->get();
        return view('jobs.form',compact('job','branches','customers','contracts','services','routes','taxCodes','users'));
    }

    private function validated(Request $request): array
    {
        $data=$request->validate([
            'branch_id'=>'required|integer','customer_id'=>'required|integer','contract_id'=>'nullable|integer','service_type_id'=>'nullable|integer','route_id'=>'nullable|integer','job_date'=>'required|date','requested_date'=>'nullable|date','customer_po'=>'nullable|string|max:150','pickup_location'=>'nullable|string|max:255','delivery_location'=>'nullable|string|max:255','cargo_description'=>'nullable|string|max:2000','quantity'=>'required|numeric|gt:0','weight'=>'nullable|numeric|min:0','volume'=>'nullable|numeric|min:0','number_of_trips'=>'required|integer|min:1','vehicle_type'=>'nullable|string|max:100','estimated_revenue'=>'nullable|numeric|min:0','estimated_cost'=>'nullable|numeric|min:0','tax_code_id'=>'nullable|integer','status'=>['required',Rule::in(['enquiry','quotation','confirmed','scheduled','vehicle_assigned','driver_assigned','in_transit','reached_destination','delivered','pod_received','ready_to_invoice','invoiced','closed','cancelled'])],'delivery_status'=>'required|string|max:30','billing_status'=>'required|string|max:30','vendor_billing_status'=>'required|string|max:30','internal_notes'=>'nullable|string','customer_notes'=>'nullable|string','assigned_user_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['customer_id'])->whereHas('roles',fn($q)=>$q->where('role','customer'))->exists(),422,'Invalid customer.');
        if (! empty($data['contract_id'])) {
            abort_unless(Contract::query()->where('company_id', $company)->where('party_id', $data['customer_id'])->whereKey($data['contract_id'])->exists(), 422, 'Invalid customer contract.');
        }
        if (! empty($data['service_type_id'])) {
            abort_unless(ServiceType::query()->where('company_id', $company)->whereKey($data['service_type_id'])->exists(), 422, 'Invalid service type.');
        }
        if (! empty($data['route_id'])) {
            abort_unless(TransportRoute::query()->where('company_id', $company)->whereKey($data['route_id'])->exists(), 422, 'Invalid route.');
        }
        if (! empty($data['tax_code_id'])) {
            abort_unless(TaxCode::query()->where('company_id', $company)->whereKey($data['tax_code_id'])->exists(), 422, 'Invalid tax code.');
        }
        if (! empty($data['assigned_user_id'])) {
            abort_unless(User::query()->where('company_id', $company)->whereKey($data['assigned_user_id'])->exists(), 422, 'Invalid assigned user.');
        }
        return $data;
    }

    private function permission(Request $request,string $action): void { abort_unless($request->user()->hasPermission('transport_jobs.'.$action),403); }
    private function access(Request $request,TransportJob $job,string $action): void { $this->permission($request,$action); abort_unless((int)$job->company_id===(int)$request->user()->company_id,404); if($request->user()->branch_id && !$request->user()->hasPermission('branches.view_all')) abort_unless((int)$job->branch_id===(int)$request->user()->branch_id,403); }
    private function scopeBranch(Request $request,$query): void { if($request->user()->branch_id && !$request->user()->hasPermission('branches.view_all')) $query->where('branch_id',$request->user()->branch_id); }
}
