"""
GPS location smoothing — rider-api

Smooths noisy rider GPS pings (typical error +-5-15m, worse on poor signal,
occasional bad-fix "jumps") using a per-rider exponential moving average
(EMA): each new reading is blended with the running estimate so a single bad
ping can't yank the rider's position, while the estimate still tracks real
movement.

This used to be implemented as a full Kalman filter (per-coordinate process/
measurement covariance, gain computed every update). For a "constant
position" state model with no velocity term — which is what this is, since
we're smoothing noisy pings, not tracking motion — the Kalman update reduces
mathematically to an EMA once the gain reaches steady state, which happens
within the first couple of updates. The EMA below is the same behavior with
one constant instead of two, and no covariance bookkeeping to explain to the
next person reading this file.

Only two things are actually used elsewhere in the app:
  smooth_rider_locations(riders)  — per-rider EMA, stateful across calls
  smooth_order_coordinates(orders) — validates/normalises delivery coords
                                      (NOT smoothed — see its docstring)
"""

import logging
import time
from typing import Dict, Optional, Tuple

logger = logging.getLogger(__name__)

# Smoothing factor: how much weight a new GPS reading gets against the
# running estimate. Lower = smoother/slower to react, higher = trusts each
# new ping more. 0.1 matches the steady-state behavior of the Kalman filter
# this replaced (process_noise=1e-4, measurement_noise=0.01).
_ALPHA = 0.1
_STALE_SECONDS = 1800.0  # reset a rider's running estimate after 30 min silence


def _is_valid_coord(lat: float, lon: float) -> bool:
    try:
        lat, lon = float(lat), float(lon)
        return (
            -90.0 <= lat <= 90.0
            and -180.0 <= lon <= 180.0
            and not (lat == 0.0 and lon == 0.0)
        )
    except (TypeError, ValueError):
        return False


class _RiderEstimate:
    """Running EMA estimate of one rider's position."""

    def __init__(self):
        self.lat: Optional[float] = None
        self.lon: Optional[float] = None
        self.last_updated: float = time.time()

    def update(self, lat: float, lon: float) -> Tuple[float, float]:
        if not _is_valid_coord(lat, lon):
            return (self.lat, self.lon) if self.lat is not None else (lat, lon)

        if time.time() - self.last_updated > _STALE_SECONDS:
            self.lat = self.lon = None  # stale — start fresh

        if self.lat is None:
            self.lat, self.lon = lat, lon
        else:
            self.lat += _ALPHA * (lat - self.lat)
            self.lon += _ALPHA * (lon - self.lon)

        self.last_updated = time.time()
        return self.lat, self.lon


_rider_estimates: Dict[str, _RiderEstimate] = {}


def smooth_rider_locations(riders: list) -> list:
    """
    Apply EMA smoothing to a list of rider dicts in-place, keyed by rider id
    (history preserved across calls via a process-level registry).

    Reads/writes: latitude, longitude (and currentlat/currentlong if present).
    Adds: _location_smoothed = True on each processed rider.
    """
    for rider in riders:
        try:
            rider_id = str(
                rider.get("userid") or rider.get("riderid") or
                rider.get("id") or "unknown"
            )
            raw_lat = float(rider.get("latitude") or rider.get("currentlat") or 0)
            raw_lon = float(rider.get("longitude") or rider.get("currentlong") or 0)
            if raw_lat == 0.0 and raw_lon == 0.0:
                continue

            estimate = _rider_estimates.setdefault(rider_id, _RiderEstimate())
            smooth_lat, smooth_lon = estimate.update(raw_lat, raw_lon)

            # Cast back to string for Go compatibility
            s_lat, s_lon = str(round(smooth_lat, 8)), str(round(smooth_lon, 8))
            rider["latitude"] = s_lat
            rider["longitude"] = s_lon
            if "currentlat" in rider:
                rider["currentlat"] = s_lat
            if "currentlong" in rider:
                rider["currentlong"] = s_lon
            rider["_location_smoothed"] = True
        except Exception as e:
            logger.debug(f"Rider location smoothing skipped: {e}")
    return riders


def smooth_order_coordinates(orders: list) -> list:
    """
    Validate and lightly normalise delivery coordinates in a list of order dicts.

    DESIGN NOTE — why these are NOT smoothed:
    Smoothing blends successive measurements from the same source over time.
    Delivery coordinates are a single static point (one measurement) — there
    is nothing to blend. Per-customer GPS accuracy is handled upstream by the
    FAISS coordinate store (verified historical rider-confirmed delivery
    points). This function only normalises the coordinate fields to floats
    so downstream code never sees raw strings or None values.

    Modifies orders in-place. Returns the same list.
    """
    for order in orders:
        try:
            dlat_raw = order.get("deliverylat") or order.get("droplat")
            dlon_raw = order.get("deliverylong") or order.get("droplon")
            if dlat_raw is None or dlon_raw is None:
                continue
            dlat = float(dlat_raw)
            dlon = float(dlon_raw)
            if not _is_valid_coord(dlat, dlon):
                continue
            # Normalise to string (Go service expects string coordinates)
            s_lat = str(round(dlat, 8))
            s_lon = str(round(dlon, 8))
            order["deliverylat"] = s_lat
            order["deliverylong"] = s_lon
            if "droplat" in order:
                order["droplat"] = s_lat
            if "droplon" in order:
                order["droplon"] = s_lon
        except Exception as e:
            logger.debug(f"Coordinate normalisation skipped: {e}")
    return orders
