"""Helpers for collection-level store selection settings and location resolution."""

from __future__ import annotations

import math
from typing import Any, Dict, List, Optional

from sqlalchemy import select
from sqlalchemy.orm import Session

from db.location_registry import active_location_directory
from db.models import CollectionCommerceProfile


DEFAULT_SELECTION_MODE = "manual_with_optional_geolocation"


def extract_location_settings(raw: Any) -> Dict[str, Any]:
    if isinstance(raw, dict):
        settings = raw.get("location_settings") if isinstance(raw.get("location_settings"), dict) else raw
    else:
        settings = {}
    if not isinstance(settings, dict):
        settings = {}
    return {
        "default_location_code": _clean(settings.get("default_location_code")),
        "allow_geolocation": _as_bool(settings.get("allow_geolocation"), default=True),
        "allow_manual_selection": _as_bool(settings.get("allow_manual_selection"), default=True),
        "selection_mode": _clean(settings.get("selection_mode")) or DEFAULT_SELECTION_MODE,
        "location_label": _clean(settings.get("location_label")) or "Store Location",
        "notes": _clean(settings.get("notes")),
    }


def merge_location_settings(
    landing_metadata: Optional[Dict[str, Any]],
    *,
    default_location_code: Optional[str] = None,
    allow_geolocation: Optional[Any] = None,
    allow_manual_selection: Optional[Any] = None,
    selection_mode: Optional[str] = None,
    location_label: Optional[str] = None,
    notes: Optional[str] = None,
) -> Dict[str, Any]:
    out = dict(landing_metadata) if isinstance(landing_metadata, dict) else {}
    current = extract_location_settings(out)
    if default_location_code is not None:
        current["default_location_code"] = _clean(default_location_code)
    if allow_geolocation is not None:
        current["allow_geolocation"] = _as_bool(allow_geolocation, default=True)
    if allow_manual_selection is not None:
        current["allow_manual_selection"] = _as_bool(allow_manual_selection, default=True)
    if selection_mode is not None:
        current["selection_mode"] = _clean(selection_mode) or DEFAULT_SELECTION_MODE
    if location_label is not None:
        current["location_label"] = _clean(location_label) or "Store Location"
    if notes is not None:
        current["notes"] = _clean(notes)
    out["location_settings"] = current
    return out


def collection_location_context(
    session: Session,
    *,
    profile: Optional[CollectionCommerceProfile] = None,
    category_l1: Optional[str] = None,
    collection_name: Optional[str] = None,
    preferred_location_code: Optional[str] = None,
    user_latitude: Optional[float] = None,
    user_longitude: Optional[float] = None,
) -> Dict[str, Any]:
    row = profile or _find_profile(session, category_l1=category_l1, collection_name=collection_name)
    directory = active_location_directory(session)
    by_code = {str(item.get("code")): item for item in directory if str(item.get("code") or "").strip()}
    settings = extract_location_settings(row.landing_metadata_json if row is not None else None)
    default_location = _preferred_default_location(by_code, settings.get("default_location_code"))
    resolved = resolve_location_selection(
        directory=directory,
        default_location_code=default_location.get("code") if default_location else None,
        allow_geolocation=bool(settings.get("allow_geolocation")),
        preferred_location_code=preferred_location_code,
        user_latitude=user_latitude,
        user_longitude=user_longitude,
    )
    return {
        "location_directory": directory,
        "location_settings": {
            **settings,
            "default_location_code": default_location.get("code") if default_location else settings.get("default_location_code"),
            "default_location_name": default_location.get("name") if default_location else None,
        },
        "resolved_location": resolved,
    }


def resolve_location_selection(
    *,
    directory: List[Dict[str, Any]],
    default_location_code: Optional[str] = None,
    allow_geolocation: bool = True,
    preferred_location_code: Optional[str] = None,
    user_latitude: Optional[float] = None,
    user_longitude: Optional[float] = None,
) -> Dict[str, Any]:
    by_code = {str(item.get("code")): item for item in directory if str(item.get("code") or "").strip()}
    preferred = by_code.get(str(preferred_location_code).strip()) if _clean(preferred_location_code) else None
    if preferred is not None:
        return _resolved_payload(preferred, strategy="preferred_location")

    geo_match = None
    if allow_geolocation and user_latitude is not None and user_longitude is not None:
        geo_match = _nearest_location(directory, user_latitude, user_longitude)
        if geo_match is not None:
            return _resolved_payload(geo_match, strategy="nearest_geolocation")

    default_location = _preferred_default_location(by_code, default_location_code)
    if default_location is not None:
        return _resolved_payload(default_location, strategy="default_location")

    first = directory[0] if directory else None
    if first is not None:
        return _resolved_payload(first, strategy="first_active_location")
    return {"selected_location_code": None, "selected_location_name": None, "selection_strategy": "none"}


def _preferred_default_location(by_code: Dict[str, Dict[str, Any]], explicit_code: Optional[str]) -> Optional[Dict[str, Any]]:
    code = _clean(explicit_code)
    if code and code in by_code:
        return by_code[code]
    for item in by_code.values():
        if bool(item.get("is_default")):
            return item
    return next(iter(by_code.values()), None)


def _nearest_location(directory: List[Dict[str, Any]], latitude: float, longitude: float) -> Optional[Dict[str, Any]]:
    best: Optional[tuple[float, Dict[str, Any]]] = None
    for item in directory:
        geo = item.get("geo") if isinstance(item.get("geo"), dict) else None
        if not geo:
            continue
        lat = geo.get("latitude")
        lon = geo.get("longitude")
        if lat is None or lon is None:
            continue
        distance = _haversine_miles(float(latitude), float(longitude), float(lat), float(lon))
        if best is None or distance < best[0]:
            best = (distance, item)
    if best is None:
        return None
    matched = dict(best[1])
    matched["distance_miles"] = round(best[0], 2)
    return matched


def _resolved_payload(location: Dict[str, Any], *, strategy: str) -> Dict[str, Any]:
    return {
        "selected_location_code": location.get("code"),
        "selected_location_name": location.get("name"),
        "selection_strategy": strategy,
        "distance_miles": location.get("distance_miles"),
    }


def _find_profile(
    session: Session,
    *,
    category_l1: Optional[str],
    collection_name: Optional[str],
) -> Optional[CollectionCommerceProfile]:
    collection = _clean(collection_name)
    if not collection:
        return None
    stmt = (
        select(CollectionCommerceProfile)
        .where(CollectionCommerceProfile.is_active.is_(True))
        .where(CollectionCommerceProfile.collection_name.ilike(collection))
        .order_by(CollectionCommerceProfile.id.desc())
    )
    category = _clean(category_l1)
    if category:
        stmt = stmt.where(
            (CollectionCommerceProfile.category_l1.is_(None))
            | (CollectionCommerceProfile.category_l1.ilike(category))
        )
    return session.scalar(stmt.limit(1))


def _haversine_miles(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
    radius_miles = 3958.7613
    phi1 = math.radians(lat1)
    phi2 = math.radians(lat2)
    delta_phi = math.radians(lat2 - lat1)
    delta_lambda = math.radians(lon2 - lon1)
    a = math.sin(delta_phi / 2) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2) ** 2
    return 2 * radius_miles * math.atan2(math.sqrt(a), math.sqrt(1 - a))


def _clean(value: Any) -> Optional[str]:
    text = str(value or "").strip()
    return text or None


def _as_bool(value: Any, *, default: bool) -> bool:
    if value is None:
        return default
    if isinstance(value, bool):
        return value
    text = str(value).strip().lower()
    if text in {"1", "true", "yes", "y", "on"}:
        return True
    if text in {"0", "false", "no", "n", "off"}:
        return False
    return default
