<?php

namespace App\Http\Controllers\Api;

use App\Enums\PriceReliability;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\NearbyStationsRequest;
use App\Http\Resources\Api\StationResource;
use App\Models\Search;
use App\Models\Station;
use App\Services\PostcodeService;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Carbon;
use Illuminate\Validation\ValidationException;

class StationController extends Controller
{
    public function __construct(private readonly PostcodeService $postcodeService) {}

    public function index(NearbyStationsRequest $request): JsonResponse
    {
        if ($request->filled('postcode')) {
            $location = $this->postcodeService->resolve($request->string('postcode')->toString());

            if ($location === null) {
                throw ValidationException::withMessages(['postcode' => 'Postcode not found.']);
            }

            $lat = $location->lat;
            $lng = $location->lng;
        } else {
            $lat = (float) $request->input('lat');
            $lng = (float) $request->input('lng');
        }
        $fuelType = $request->fuelType();
        $radius = $request->radius();
        $sort = $request->sort();

        $all = Station::query()
            ->selectRaw(
                'stations.*, spc.price_pence, spc.fuel_type, spc.price_effective_at,
                (6371 * acos(GREATEST(-1.0, LEAST(1.0,
                    cos(radians(?)) * cos(radians(lat)) * cos(radians(lng) - radians(?))
                    + sin(radians(?)) * sin(radians(lat))
                )))) AS distance_km',
                [$lat, $lng, $lat],
            )
            ->join('station_prices_current as spc', function (JoinClause $join) use ($fuelType): void {
                $join->on('stations.node_id', '=', 'spc.station_id')
                    ->where('spc.fuel_type', '=', $fuelType->value);
            })
            ->where('stations.temporary_closure', false)
            ->where('stations.permanent_closure', false)
            ->get();

        $filtered = $all->filter(fn ($s) => (float) $s->distance_km <= $radius);

        $stations = $sort === 'reliable'
            ? $filtered
                ->sort(function ($a, $b) {
                    $weightA = PriceReliability::fromUpdatedAt(
                        $a->price_effective_at ? Carbon::parse($a->price_effective_at) : null
                    )->weight();
                    $weightB = PriceReliability::fromUpdatedAt(
                        $b->price_effective_at ? Carbon::parse($b->price_effective_at) : null
                    )->weight();

                    return $weightA <=> $weightB
                        ?: ((int) $a->price_pence <=> (int) $b->price_pence)
                        ?: ((float) $a->distance_km <=> (float) $b->distance_km);
                })
                ->values()
            : $filtered->sortBy(match ($sort) {
                'price' => fn ($s) => (int) $s->price_pence,
                'updated' => fn ($s) => $s->price_effective_at ? -strtotime($s->price_effective_at) : PHP_INT_MAX,
                default => fn ($s) => (float) $s->distance_km,
            })->values();

        $prices = $stations->pluck('price_pence');

        $reliabilityCounts = $stations
            ->groupBy(fn ($s) => PriceReliability::fromUpdatedAt(
                $s->price_effective_at ? Carbon::parse($s->price_effective_at) : null
            )->value)
            ->map->count();

        Search::create([
            'lat_bucket' => round($lat, 2),
            'lng_bucket' => round($lng, 2),
            'fuel_type' => $fuelType->value,
            'results_count' => $stations->count(),
            'lowest_pence' => $prices->min(),
            'highest_pence' => $prices->max(),
            'avg_pence' => $prices->isNotEmpty() ? round($prices->avg(), 2) : null,
            'searched_at' => now(),
            'ip_hash' => hash('sha256', $request->ip() ?? ''),
        ]);

        return response()->json([
            'data' => StationResource::collection($stations),
            'meta' => [
                'count' => $stations->count(),
                'fuel_type' => $fuelType->value,
                'radius_km' => $radius,
                'lat' => $lat,
                'lng' => $lng,
                'lowest_pence' => $prices->min(),
                'highest_pence' => $prices->max(),
                'cheapest_price_pence' => $prices->min(),
                'avg_pence' => $prices->isNotEmpty() ? round($prices->avg(), 2) : null,
                'reliability_counts' => [
                    'reliable' => (int) $reliabilityCounts->get(PriceReliability::Reliable->value, 0),
                    'stale' => (int) $reliabilityCounts->get(PriceReliability::Stale->value, 0),
                    'outdated' => (int) $reliabilityCounts->get(PriceReliability::Outdated->value, 0),
                ],
            ],
        ]);
    }
}
