"""
Empirical ETA Calculator
========================

Drop-in replacement for `RealisticETACalculator` that prefers ETAs *learned
from actual delivery times* (see delivery_history_service.py) and falls back to
the original formula whenever history is too thin.

Design goals
------------
* **Interface-compatible**: `calculate_eta(...)` keeps the same positional args
  and `int`-minutes return as the formula calculator, so existing call sites
  work unchanged. Extra args (`kitchen`, `drop_coords`, `rider_id`) are optional
  and let callers that *have* that context (the optimizer does) get sharper,
  zone-aware estimates.
* **Safe by default**: if empirical data is missing for a context, or the
  feature is disabled via `eta_empirical_enabled`, it returns exactly what the
  formula would — zero behavior change until real data exists.
* **Non-blocking**: never calls Postgres on the hot path. If the stats cache is
  empty it kicks off a one-shot background refresh and serves the formula
  meanwhile.

Empirical values are real door-to-door leg times (gap between consecutive
deliveries), so they already include travel + drop service time. We only add the
kitchen pickup buffer for the first leg, mirroring the formula's semantics.
"""

import logging
from typing import Any, List, Optional, Tuple

from app.services.routing.realistic_eta_calculator import (
    RealisticETACalculator,
    get_time_of_day_category,
)

logger = logging.getLogger(__name__)


class EmpiricalETACalculator:
    """ETA calculator backed by empirical history, with formula fallback."""

    def __init__(self):
        # Composed formula calculator — the fallback and the source of buffers.
        self.formula = RealisticETACalculator()

    # ------------------------------------------------------------------
    # Main entry point (signature is a superset of RealisticETACalculator)
    # ------------------------------------------------------------------

    def calculate_eta(
        self,
        distance_km: float,
        is_first_order: bool = False,
        order_type: str = "Economy",
        time_of_day: str = "peak",
        kitchen: Optional[str] = None,
        drop_coords: Optional[Tuple[float, float]] = None,
        rider_id: Optional[Any] = None,
    ) -> int:
        """Return ETA in minutes — empirical if available, else the formula."""
        if distance_km is not None and distance_km <= 0 and not is_first_order:
            return 0

        from app.config.dynamic_config import get_config
        cfg = get_config()

        if not bool(cfg.get("eta_empirical_enabled", True)):
            return self._formula_eta(distance_km, is_first_order, order_type, time_of_day)

        try:
            from app.services.routing.delivery_history_service import (
                get_delivery_history_service,
            )
            svc = get_delivery_history_service()

            if not svc.has_data():
                # Populate in the background; serve the formula for now.
                svc.maybe_background_refresh(
                    days=int(cfg.get("eta_history_days", 14)),
                )
                return self._formula_eta(distance_km, is_first_order, order_type, time_of_day)

            hit = svc.lookup(
                distance_km=float(distance_km or 0.0),
                traffic_cat=time_of_day,
                kitchen=kitchen,
                drop_coords=drop_coords,
                min_samples=int(cfg.get("eta_min_samples", 20)),
                stat=str(cfg.get("eta_stat", "median")),
            )
        except Exception as e:
            logger.debug(f"[EmpiricalETA] lookup failed, using formula: {e}")
            hit = None

        if not hit:
            return self._formula_eta(distance_km, is_first_order, order_type, time_of_day)

        value = float(hit["value_min"])
        # First leg includes time spent picking up at the kitchen; the empirical
        # leg gap starts at pickup completion, so add the same buffer the formula uses.
        if is_first_order:
            value += float(cfg.get("eta_pickup_time_min", 3.0))
        return int(value) + 1  # round up for safety, matching the formula

    def _formula_eta(self, distance_km, is_first_order, order_type, time_of_day) -> int:
        return self.formula.calculate_eta(
            distance_km=distance_km,
            is_first_order=is_first_order,
            order_type=order_type,
            time_of_day=time_of_day,
        )

    # ------------------------------------------------------------------
    # Batch helper (kept compatible with RealisticETACalculator)
    # ------------------------------------------------------------------

    def calculate_batch_eta(self, orders: List[dict]) -> List[dict]:
        """Calculate ETAs for a batch in sequence (formula-parity batch path)."""
        traffic = get_time_of_day_category()
        for order in orders:
            distance_km = float(order.get("previouskms", 0) or 0)
            step = order.get("step", 1)
            order_type = order.get("ordertype", "Economy")
            drop = None
            try:
                dlat = float(order.get("deliverylat") or order.get("droplat") or 0)
                dlon = float(order.get("deliverylong") or order.get("droplon") or 0)
                if dlat and dlon:
                    drop = (dlat, dlon)
            except (TypeError, ValueError):
                drop = None
            eta = self.calculate_eta(
                distance_km=distance_km,
                is_first_order=(step == 1),
                order_type=order_type,
                time_of_day=traffic,
                kitchen=order.get("pickupcustomer") or order.get("locationname"),
                drop_coords=drop,
                rider_id=order.get("userid"),
            )
            order["eta"] = str(eta)
            order["eta_empirical"] = True
        return orders
