<?php

namespace App\Http\Controllers\Front;

use App\Http\Controllers\Controller;
use App\Http\Requests\Front\EstacionesIndexRequest;
use App\Models\Admin\Estacion;
use App\Models\Ciudad;
use App\Models\Departamento;
use App\Models\Servicio;
use Illuminate\Support\Collection;
use Illuminate\Pagination\LengthAwarePaginator;

class EstacionesController extends Controller
{
    private const PER_PAGE = 12;

    /**
     * Expone el catálogo público con filtros validados y soporte opcional de
     * orden por cercanía usando coordenadas del navegador.
     */
    public function index(EstacionesIndexRequest $request)
    {
        return [
            'estaciones' => $this->getEstaciones($request),
            'servicios' => Servicio::query()
                ->select(['id', 'titulo'])
                ->where('estado', true)
                ->whereHas('estaciones', function ($query) {
                    $query->where('estaciones.estado', true);
                })
                ->get(),
            'departamentos' => $this->getDepartamentos()
        ];
    }

    /**
     * Obtiene estaciones con el mínimo de columnas necesarias para la vista y
     * evita cargar relaciones que la página no usa.
     */
    private function getEstaciones(EstacionesIndexRequest $request)
    {
        $servicio_id = $request->get('servicio_id', null);
        $departamento_id = $request->get('departamento_id', null);
        $ciudad_id = $request->get('ciudad_id', null);
        $latitude = $this->normalizeCoordinate($request->get('lat'));
        $longitude = $this->normalizeCoordinate($request->get('lng'));
        $orderByDistance = $request->boolean('nearest') && $latitude !== null && $longitude !== null;
        $ciudades_ids = [];

        if ($departamento_id && !$ciudad_id) {
            $ciudades_ids = Ciudad::where('departamento_id', $departamento_id)
                ->pluck('id');
        } else if ($ciudad_id) {
            $ciudades_ids = [$ciudad_id];
        }

        $query = Estacion::query()
            ->select([
                'id',
                'titulo',
                'direccion',
                'ciudad_id',
                'ubicacion',
                'estado',
            ])
            ->where('estado', true)
            ->with([
                'ciudad:id,nombre,departamento_id',
                'ciudad.departamento:id,nombre',
            ])
            ->where(function ($query) use ($ciudades_ids) {
                if (count($ciudades_ids) > 0) {
                    $query->whereIn('ciudad_id', $ciudades_ids);
                }
            });

        if ($servicio_id) {
            $query->whereHas('servicios', function ($serviceQuery) use ($servicio_id) {
                $serviceQuery->where('servicios.id', $servicio_id);
            });
        }

        if ($orderByDistance) {
            $estaciones = $this->paginateNearestStations(
                $query->get(),
                $latitude,
                $longitude,
                (int) $request->get('page', 1)
            );
        } else {
            $estaciones = $query->paginate(self::PER_PAGE, [
                'id',
                'titulo',
                'direccion',
                'ciudad_id',
                'ubicacion',
                'estado',
            ]);
        }

        $estaciones->getCollection()->transform(function (Estacion $estacion) use ($orderByDistance, $latitude, $longitude) {
            $estacion->departamento = $estacion->ciudad?->departamento;

            if ($orderByDistance) {
                $estacion->distance_km = $this->calculateDistanceInKm(
                    $latitude,
                    $longitude,
                    (float) $estacion->ubicacion->lat,
                    (float) $estacion->ubicacion->lng
                );
            }

            return $estacion;
        });

        return [
            'data' => $estaciones->items(),
            'current_page' => $estaciones->currentPage(),
            'last_page' => $estaciones->lastPage(),
            'total' => $estaciones->total(),
        ];
    }

    /**
     * Ordena en memoria por cercanía para evitar SQL específico de motor y
     * mantener el cálculo consistente con la geolocalización del navegador.
     */
    private function paginateNearestStations(
        Collection $stations,
        float $latitude,
        float $longitude,
        int $page
    ): LengthAwarePaginator {
        $sortedStations = $stations
            ->filter(function (Estacion $station) {
                return $station->ubicacion->lat !== null && $station->ubicacion->lng !== null;
            })
            ->sortBy(function (Estacion $station) use ($latitude, $longitude) {
                return $this->calculateDistanceInKm(
                    $latitude,
                    $longitude,
                    (float) $station->ubicacion->lat,
                    (float) $station->ubicacion->lng
                );
            })
            ->values();

        $items = $sortedStations
            ->forPage($page, self::PER_PAGE)
            ->values();

        return new LengthAwarePaginator(
            $items,
            $sortedStations->count(),
            self::PER_PAGE,
            $page
        );
    }

    /**
     * Normaliza coordenadas de entrada para evitar errores por strings vacíos o
     * formatos no numéricos enviados desde cliente.
     */
    private function normalizeCoordinate(mixed $value): ?float
    {
        if (!is_numeric($value)) {
            return null;
        }

        return (float) $value;
    }

    /**
     * Calcula distancia aproximada en kilómetros usando Haversine.
     */
    private function calculateDistanceInKm(
        float $originLatitude,
        float $originLongitude,
        float $targetLatitude,
        float $targetLongitude
    ): float {
        $earthRadiusInKm = 6371;
        $latitudeDelta = deg2rad($targetLatitude - $originLatitude);
        $longitudeDelta = deg2rad($targetLongitude - $originLongitude);
        $originLatitude = deg2rad($originLatitude);
        $targetLatitude = deg2rad($targetLatitude);

        $a = sin($latitudeDelta / 2) ** 2
            + cos($originLatitude) * cos($targetLatitude) * sin($longitudeDelta / 2) ** 2;

        return round($earthRadiusInKm * (2 * asin(min(1, sqrt($a)))), 1);
    }

    private function getCiudadesEstacionesIds()
    {
        return Estacion::where('estado', true)
            ->select(['ciudad_id'])
            ->pluck('ciudad_id');
    }

    private function getDepartamentos()
    {
        $ciudades_ids = $this->getCiudadesEstacionesIds();

        return Departamento::query()->whereHas('ciudades', function ($query) use ($ciudades_ids) {
            $query->whereIn('ciudades.id', $ciudades_ids);
        })
            ->with([
                'ciudades' => function ($query) use ($ciudades_ids) {
                    $query
                        ->select(['id', 'nombre', 'departamento_id'])
                        ->whereIn('id', $ciudades_ids);
                }
            ])
            ->select(['id', 'nombre'])
            ->get();
    }
}
