<?php

namespace App\Http\Controllers\Api\V1;

use App\Enums\TripStatus;
use App\Events\DriverLocationChanged;
use App\Http\Controllers\Controller;
use App\Http\Requests\CreateTripRequest;
use App\Http\Requests\DriverLocationRequest;
use App\Http\Requests\DriverStatusRequest;
use App\Http\Requests\FareQuoteRequest;
use App\Http\Requests\TripCommandRequest;
use App\Models\Driver;
use App\Models\DriverGroup;
use App\Models\DriverLocation;
use App\Models\Trip;
use App\Services\TripService;
use App\Support\ApiResponder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Throwable;

class TripController extends Controller
{
    use ApiResponder;

    public function driverProfile(Request $request): JsonResponse
    {
        return $this->ok($request->user()->driver);
    }

    public function driverStatus(DriverStatusRequest $request): JsonResponse
    {
        $driver = $request->user()->driver;
        abort_unless($driver->status?->value === 'active', 403);
        abort_if(! $request->boolean('online') && \App\Models\DriverGroupRouteRun::where('driver_id', $driver->id)->where('status', 'in_progress')->exists(), 409, 'Complete your group route before going offline.');
        $driver->update(['is_online' => $request->boolean('online')]);

        return $this->ok($driver->fresh(), 'Driver status updated.');
    }

    public function driverLocation(DriverLocationRequest $request): JsonResponse
    {
        $driver = $request->user()->driver;
        abort_unless($driver->status?->value === 'active' && $driver->is_online, 403);
        $data = $request->validated();
        $driver->update(['current_latitude' => $data['latitude'], 'current_longitude' => $data['longitude'], 'location_updated_at' => now()]);
        $location = DriverLocation::create(['driver_id' => $driver->id, 'latitude' => $data['latitude'], 'longitude' => $data['longitude'], 'accuracy' => $data['accuracy'] ?? null, 'heading' => $data['heading'] ?? null, 'speed_kph' => $data['speed_kph'] ?? null, 'captured_at' => $data['captured_at'], 'received_at' => now()]);
        $active = $driver->trips()->whereIn('status', ['assigned', 'driver_en_route', 'driver_arrived', 'trip_started'])->latest()->first();
        if ($active) {
            DriverLocationChanged::dispatch($active, $location);
        }
        $routeRun = \App\Models\DriverGroupRouteRun::where('driver_id', $driver->id)->where('status', 'in_progress')->latest()->first();
        if ($routeRun) {
            $last = $routeRun->locations()->latest('captured_at')->first();
            if (! $last || $last->captured_at->diffInSeconds(now()) >= 15) {
                \App\Models\DriverGroupRouteLocation::create(['route_run_id' => $routeRun->id, 'latitude' => $data['latitude'], 'longitude' => $data['longitude'], 'captured_at' => $data['captured_at']]);
            }
        }

        return $this->ok(null, 'Location received.');
    }

    public function quote(FareQuoteRequest $request, TripService $service): JsonResponse
    {
        $quote = $service->quote($request->user()->customer, $request->validated());

        return $this->ok($quote, 'Fare quote created.', 201);
    }

    public function active(Request $request): JsonResponse
    {
        $user = $request->user();
        $trips = match ($user->role) {
            'customer' => $user->customer?->trips(),
            'driver' => $user->driver?->trips(),
            default => null,
        };
        $trip = $user->role === 'driver'
            ? $trips?->whereIn('status', [TripStatus::Assigned, TripStatus::DriverEnRoute, TripStatus::DriverArrived, TripStatus::TripStarted])->latest()->first()
            : $trips?->whereNotIn('status', [TripStatus::TripCompleted, TripStatus::Cancelled, TripStatus::Expired])->latest()->first();

        return $this->ok($trip?->load(['fareQuote', 'driver.user', 'vehicle', 'statusLogs']));
    }

    public function store(CreateTripRequest $request, TripService $service): JsonResponse
    {
        try {
            $data = $request->validated();
            foreach (['driver_group_id', 'requested_driver_id'] as $key) {
                if (isset($data[$key])) {
                    $data[$key] = $key === 'driver_group_id' ? DriverGroup::where('public_id', $data[$key])->value('id') : Driver::where('public_id', $data[$key])->value('id');
                }
            } $trip = $service->create($request->user()->customer, $data, $request->user());

            return $this->ok($trip, 'Trip created.', 201);
        } catch (Throwable $e) {
            return $this->error($e->getMessage(), 'TRIP_CREATE_FAILED', 422);
        }
    }

    public function show(Request $request, Trip $trip): JsonResponse
    {
        abort_unless($trip->customer_id === $request->user()->customer?->id || $trip->driver_id === $request->user()->driver?->id || $request->user()->role === 'admin', 403);

        return $this->ok($trip->load(['fareQuote', 'statusLogs', 'customer.user', 'driver.user', 'vehicle']));
    }

    public function accept(Request $request, Trip $trip, TripService $service): JsonResponse
    {
        try {
            return $this->ok($service->accept($trip, $request->user()->driver, $request->user()), 'Trip accepted.');
        } catch (Throwable $e) {
            return $this->error($e->getMessage(), 'TRIP_ACCEPT_FAILED', 409);
        }
    }

    public function available(Request $request, TripService $service): JsonResponse
    {
        abort_unless($request->user()->driver?->status?->value === 'active' && $request->user()->driver->is_online, 403);

        return $this->ok($service->available($request->user()->driver));
    }

    public function reject(Request $request, Trip $trip, TripService $service): JsonResponse
    {
        try {
            $service->reject($trip, $request->user()->driver, $request->user());

            return $this->ok(null, 'Trip rejected.');
        } catch (Throwable $e) {
            return $this->error($e->getMessage(), 'TRIP_REJECT_FAILED', 409);
        }
    }

    public function command(TripCommandRequest $request, Trip $trip, string $command, TripService $service): JsonResponse
    {
        $map = ['en-route' => [TripStatus::Assigned, TripStatus::DriverEnRoute], 'arrive' => [TripStatus::DriverEnRoute, TripStatus::DriverArrived], 'start' => [TripStatus::DriverArrived, TripStatus::TripStarted], 'complete' => [TripStatus::TripStarted, TripStatus::TripCompleted]];
        abort_unless(isset($map[$command]), 404);
        abort_unless($trip->driver_id === $request->user()->driver?->id, 403);
        [$expected, $target] = $map[$command];
        try {
            return $this->ok($service->transition($trip, $expected, $target, $request->user()), 'Trip updated.');
        } catch (Throwable $e) {
            return $this->error($e->getMessage(), 'TRIP_TRANSITION_FAILED', 409);
        }
    }

    public function cancel(TripCommandRequest $request, Trip $trip, TripService $service): JsonResponse
    {
        abort_unless($trip->customer_id === $request->user()->customer?->id || $trip->driver_id === $request->user()->driver?->id || $request->user()->role === 'admin', 403);
        try {
            return $this->ok($service->cancel($trip, $request->user(), $request->reason), 'Trip cancelled.');
        } catch (Throwable $e) {
            return $this->error($e->getMessage(), 'TRIP_CANCEL_FAILED', 409);
        }
    }
}
