<?php

namespace Modules\Routing\Http\Controllers;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Routing\Controller;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Modules\Catalog\Models\EvCompany;
use Modules\Routing\Http\Requests\SearchRoutesRequest;
use Modules\Routing\Http\Resources\EvRouteResource;
use Modules\Routing\Http\Resources\RoutePricingResource;
use Modules\Routing\Http\Resources\RouteTimeSlotResource;
use Modules\Routing\Models\EvRoute;

class EvRouteController extends Controller
{
    /**
     * @var list<string>
     */
    private const EAGER_LOADS = ['company', 'fromDestination', 'toDestination', 'timeSlots', 'pricing'];

    private const CACHE_TAG = 'routes';

    private const CACHE_TTL_MINUTES = 5;

    /**
     * POST, not GET: round_trip=true returns two independent result sets
     * (routes + return_routes) in one response, which doesn't fit a plain
     * GET-with-query-params search shape as cleanly (domain.md §2b).
     */
    public function search(SearchRoutesRequest $request): JsonResponse
    {
        $filters = $request->only(['company', 'from', 'to', 'date', 'time_slot']);
        $isRoundTrip = $request->boolean('round_trip');

        // Separate page params: routes and return_routes almost always have
        // different totals, so paging one must never slice the other at the
        // same offset (e.g. return_routes with only 3 rows would come back
        // empty on page=2 while routes still has real data there).
        $routes = $this->searchRoutes($filters, fromKey: 'from', toKey: 'to', pageName: 'page', page: $request->integer('page', 1));

        $returnRoutes = $isRoundTrip
            ? $this->searchRoutes($filters, fromKey: 'to', toKey: 'from', pageName: 'return_page', page: $request->integer('return_page', 1))
            : new LengthAwarePaginator([], 0, 15);

        return response()->json([
            'routes' => EvRouteResource::collection($routes)->response()->getData(true),
            'return_routes' => EvRouteResource::collection($returnRoutes)->response()->getData(true),
            // The company/time_slot options actually available for this
            // from->to pair — computed from from/to alone, ignoring any
            // company/time_slot already applied, so the client can offer
            // switching between them rather than guessing a static list.
            'filters' => $this->filterOptions($filters['from'] ?? null, $filters['to'] ?? null),
            'return_filters' => $isRoundTrip
                ? $this->filterOptions($filters['to'] ?? null, $filters['from'] ?? null)
                : ['companies' => [], 'time_slots' => []],
        ]);
    }

    /**
     * @return array{companies: array<int, array<string, mixed>>, time_slots: array<int, array<string, mixed>>}
     */
    private function filterOptions(mixed $from, mixed $to): array
    {
        if (blank($from) || blank($to)) {
            return ['companies' => [], 'time_slots' => []];
        }

        $cacheKey = "routes:filter-options:{$from}:{$to}";

        return Cache::tags(self::CACHE_TAG)->remember(
            $cacheKey,
            now()->addMinutes(self::CACHE_TTL_MINUTES),
            function () use ($from, $to) {
                $routes = EvRoute::query()
                    ->where('is_active', true)
                    ->where('from_destination_id', $from)
                    ->where('to_destination_id', $to)
                    ->with(['company', 'timeSlots' => fn (BelongsToMany $query) => $query->wherePivot('is_active', true)])
                    ->get();

                $companies = $routes->pluck('company')->filter()->unique('id')->sortBy('name')->values();
                $timeSlots = $routes->flatMap(fn (EvRoute $route) => $route->timeSlots)->unique('id')->sortBy('time')->values();

                return [
                    // Facet purposes only — not the full EvCompanyResource
                    // (no slug/description/contact/logo needed just to
                    // populate a filter option).
                    'companies' => $companies->map(fn (EvCompany $company) => [
                        'id' => $company->id,
                        'name' => $company->name,
                        'mm_name' => $company->mm_name,
                    ])->all(),
                    'time_slots' => $timeSlots->map(fn ($slot) => [
                        'id' => $slot->id,
                        'label' => $slot->label,
                        'time' => $slot->time?->format('H:i'),
                    ])->all(),
                ];
            },
        );
    }

    /**
     * @param  array<string, mixed>  $filters  Keyed by 'from'/'to' regardless of
     *                                         $fromKey/$toKey — swapped for the return leg of a round trip.
     */
    private function searchRoutes(array $filters, string $fromKey, string $toKey, string $pageName, int $page): LengthAwarePaginator
    {
        $cacheKey = 'routes:search:'.md5(json_encode($filters + ['fromKey' => $fromKey, 'page' => $page]));

        return Cache::tags(self::CACHE_TAG)->remember(
            $cacheKey,
            now()->addMinutes(self::CACHE_TTL_MINUTES),
            fn () => EvRoute::query()
                ->where('is_active', true)
                ->when(filled($filters['company'] ?? null), fn (Builder $query) => $query->where('ev_company_id', $filters['company']))
                ->when(filled($filters[$fromKey] ?? null), fn (Builder $query) => $query->where('from_destination_id', $filters[$fromKey]))
                ->when(filled($filters[$toKey] ?? null), fn (Builder $query) => $query->where('to_destination_id', $filters[$toKey]))
                ->when(filled($filters['time_slot'] ?? null), fn (Builder $query) => $query->whereHas(
                    'timeSlots',
                    fn (Builder $timeSlotQuery) => $timeSlotQuery
                        ->where('departure_time_slots.time', Carbon::createFromFormat('H:i', $filters['time_slot'])->format('H:i:s'))
                        ->where('ev_route_time_slots.is_active', true),
                ))
                // `date` is accepted for forward-compatibility with future per-date capacity
                // checks (domain.md §7), but v1 has no route-level calendar to filter against.
                ->with(self::EAGER_LOADS)
                ->paginate(perPage: 15, pageName: $pageName, page: $page),
        );
    }

    public function show(EvRoute $route): EvRouteResource
    {
        abort_unless($route->is_active, 404);

        $route = Cache::tags(self::CACHE_TAG)->remember(
            "routes:show:{$route->id}",
            now()->addMinutes(self::CACHE_TTL_MINUTES),
            fn () => $route->load(self::EAGER_LOADS),
        );

        return new EvRouteResource($route);
    }

    public function pricing(EvRoute $route): AnonymousResourceCollection
    {
        abort_unless($route->is_active, 404);

        $pricing = Cache::tags(self::CACHE_TAG)->remember(
            "routes:pricing:{$route->id}",
            now()->addMinutes(self::CACHE_TTL_MINUTES),
            fn () => $route->pricing()->get(),
        );

        return RoutePricingResource::collection($pricing);
    }

    public function timeSlots(EvRoute $route): AnonymousResourceCollection
    {
        abort_unless($route->is_active, 404);

        $timeSlots = Cache::tags(self::CACHE_TAG)->remember(
            "routes:time-slots:{$route->id}",
            now()->addMinutes(self::CACHE_TTL_MINUTES),
            fn () => $route->timeSlots()->get(),
        );

        return RouteTimeSlotResource::collection($timeSlots);
    }
}
