"""
Customer coordinate store.

Purpose:
  For each incoming order, look up the customer's phone number.
  If a previous verified delivery exists (rider coords on file), use those
  coords instead of the (possibly noisy) input delivery coords.
  If no record exists, create one so future calls benefit from it.

Storage:
  Pure dict + JSON — no FAISS dependency.
  phone_meta.json maps phone -> {name, rider_lat, rider_lon}.
  Coord lookup key is phone number, not coordinates, so vector search
  was never needed; FAISS has been removed.
"""

import os
import json
import logging
from math import radians, cos, sin, asin, sqrt
from threading import Lock

logger = logging.getLogger(__name__)

STORE_DIR = os.getenv("CUSTOMER_STORE_DIR", "ml_data/faiss_customer")
META_PATH  = os.path.join(STORE_DIR, "phone_meta.json")

# Within this distance the stored rider coords are considered the real delivery point
SIMILARITY_THRESHOLD_KM = float(os.getenv("FAISS_SIMILARITY_KM", "0.5"))


def _haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
    """Great-circle distance in km."""
    try:
        lon1, lat1, lon2, lat2 = map(radians, [float(lon1), float(lat1), float(lon2), float(lat2)])
        dlon = lon2 - lon1
        dlat = lat2 - lat1
        a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
        return 2 * asin(min(1.0, sqrt(a))) * 6371
    except Exception:
        return float("inf")


class CustomerCoordStore:
    """
    Thread-safe customer delivery coordinate store (dict + JSON backend).

    Primary flow (called per order before assignment):
        verified_lat, verified_lon, corrected = store.get_verified_coords(
            phone, customer_name, input_lat, input_lon
        )
    """

    def __init__(self):
        self._lock = Lock()
        os.makedirs(STORE_DIR, exist_ok=True)
        self._meta: dict = {}   # phone -> {name, rider_lat, rider_lon}
        self._load()

    # ------------------------------------------------------------------
    # Persistence
    # ------------------------------------------------------------------

    def _load(self):
        if os.path.exists(META_PATH):
            try:
                with open(META_PATH, "r", encoding="utf-8") as f:
                    self._meta = json.load(f)
                logger.info(f"[CustomerStore] Loaded {len(self._meta)} customer records.")
            except Exception as e:
                logger.warning(f"[CustomerStore] Could not load {META_PATH}: {e} — starting fresh.")
                self._meta = {}
        else:
            self._meta = {}
            logger.info("[CustomerStore] No existing store found — starting fresh.")

    def _save(self):
        try:
            with open(META_PATH, "w", encoding="utf-8") as f:
                json.dump(self._meta, f, indent=2)
        except Exception as e:
            logger.warning(f"[CustomerStore] Save failed: {e}")

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def get_verified_coords(
        self,
        phone: str,
        customer_name: str,
        input_lat: float,
        input_lon: float,
    ) -> tuple:
        """
        Verify / correct delivery coordinates using stored history.

        Returns:
            (lat, lon, was_corrected)
            was_corrected=True  → coords replaced with verified stored coords
            was_corrected=False → input coords used (new customer or new location)
        """
        if not phone or not input_lat or not input_lon:
            return input_lat, input_lon, False

        phone = str(phone).strip()

        with self._lock:
            record = self._meta.get(phone)

            if record:
                stored_lat = record["rider_lat"]
                stored_lon = record["rider_lon"]
                dist_km = _haversine(stored_lat, stored_lon, input_lat, input_lon)

                if dist_km <= SIMILARITY_THRESHOLD_KM:
                    # Stored rider coords match input → they represent the real door
                    logger.info(
                        f"[CustomerStore] {phone}: verified coords used "
                        f"(stored↔input dist={dist_km:.3f}km, within {SIMILARITY_THRESHOLD_KM}km)"
                    )
                    return stored_lat, stored_lon, True
                else:
                    # Customer location changed significantly, trust new input
                    logger.info(
                        f"[CustomerStore] {phone}: location changed "
                        f"(dist={dist_km:.3f}km > {SIMILARITY_THRESHOLD_KM}km), using input coords"
                    )
                    return input_lat, input_lon, False
            else:
                # New customer — create record with input coords
                self._create_record(phone, customer_name, input_lat, input_lon)
                logger.info(f"[CustomerStore] {phone}: new record created ({input_lat}, {input_lon})")
                return input_lat, input_lon, False

    def update_rider_coords(self, phone: str, rider_lat: float, rider_lon: float):
        """
        Called when a delivery is confirmed to update with actual rider GPS.
        This is what makes stored coords progressively more accurate.
        """
        phone = str(phone).strip()
        with self._lock:
            if phone in self._meta:
                record = self._meta[phone]
                record["rider_lat"] = rider_lat
                record["rider_lon"] = rider_lon
                self._save()
                logger.info(f"[CustomerStore] {phone}: rider coords updated ({rider_lat}, {rider_lon})")

    def record_count(self) -> int:
        return len(self._meta)

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    def _create_record(self, phone: str, name: str, lat: float, lon: float):
        self._meta[phone] = {
            "name": name,
            "rider_lat": lat,
            "rider_lon": lon,
        }
        self._save()


# Singleton — keep old name for backward compatibility with any imports
_store_instance: "CustomerCoordStore | None" = None
_store_lock = Lock()


def get_faiss_store() -> CustomerCoordStore:
    """Returns the singleton CustomerCoordStore (backward-compat name)."""
    global _store_instance
    if _store_instance is None:
        with _store_lock:
            if _store_instance is None:
                _store_instance = CustomerCoordStore()
    return _store_instance


# Also expose under new name
get_customer_store = get_faiss_store
