"""
Weather Skill — 5-day forecast via Open-Meteo (free, no API key).

Open-Meteo: https://open-meteo.com
Geocoding:  https://geocoding-api.open-meteo.com

Saved locations stored at ~/.familiar/data/weather_locations.json:
{
  "locations": [
    {"name": "San Francisco, CA", "lat": 37.77, "lon": -122.42, "unit": "fahrenheit"}
  ],
  "default_unit": "fahrenheit"
}
"""

from __future__ import annotations

import json
import logging
from pathlib import Path

import httpx

logger = logging.getLogger(__name__)

GEOCODE_URL = "https://geocoding-api.open-meteo.com/v1/search"
FORECAST_URL = "https://api.open-meteo.com/v1/forecast"

_WMO_CODES = {
    0: "Clear sky",
    1: "Mainly clear",
    2: "Partly cloudy",
    3: "Overcast",
    45: "Foggy",
    48: "Icy fog",
    51: "Light drizzle",
    53: "Drizzle",
    55: "Heavy drizzle",
    61: "Light rain",
    63: "Rain",
    65: "Heavy rain",
    71: "Light snow",
    73: "Snow",
    75: "Heavy snow",
    77: "Snow grains",
    80: "Rain showers",
    81: "Heavy showers",
    82: "Violent showers",
    85: "Snow showers",
    86: "Heavy snow showers",
    95: "Thunderstorm",
    96: "Thunderstorm w/ hail",
    99: "Thunderstorm w/ heavy hail",
}

_CONDITION_EMOJI = {
    0: "☀️",
    1: "🌤️",
    2: "⛅",
    3: "☁️",
    45: "🌫️",
    48: "🌫️",
    51: "🌦️",
    53: "🌦️",
    55: "🌧️",
    61: "🌧️",
    63: "🌧️",
    65: "🌧️",
    71: "🌨️",
    73: "❄️",
    75: "❄️",
    77: "❄️",
    80: "🌦️",
    81: "🌧️",
    82: "⛈️",
    85: "🌨️",
    86: "❄️",
    95: "⛈️",
    96: "⛈️",
    99: "⛈️",
}


def _locations_path() -> Path:
    try:
        from familiar.core.paths import DATA_DIR

        return DATA_DIR / "weather_locations.json"
    except ImportError:
        return Path.home() / ".familiar" / "data" / "weather_locations.json"


def _load_config() -> dict:
    path = _locations_path()
    if not path.exists():
        return {"locations": [], "default_unit": "fahrenheit"}
    try:
        return json.loads(path.read_text())
    except Exception:
        return {"locations": [], "default_unit": "fahrenheit"}


def _save_config(cfg: dict) -> None:
    path = _locations_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(cfg, indent=2))


def geocode(city: str) -> dict | None:
    """Resolve a city name to {name, lat, lon, country}. Returns None if not found."""
    try:
        resp = httpx.get(
            GEOCODE_URL,
            params={"name": city, "count": 1, "language": "en", "format": "json"},
            timeout=8,
        )
        results = resp.json().get("results", [])
        if not results:
            return None
        r = results[0]
        parts = [r.get("name", city)]
        if r.get("admin1"):
            parts.append(r["admin1"])
        if r.get("country"):
            parts.append(r["country"])
        return {
            "name": ", ".join(parts),
            "lat": r["latitude"],
            "lon": r["longitude"],
            "country": r.get("country", ""),
        }
    except Exception as e:
        logger.debug("Geocoding failed for %s: %s", city, e)
        return None


def fetch_forecast(lat: float, lon: float, unit: str = "fahrenheit", days: int = 5) -> dict:
    """Fetch N-day forecast from Open-Meteo. Returns structured data."""
    temp_unit = "fahrenheit" if unit.lower() in ("f", "fahrenheit", "imperial") else "celsius"
    try:
        resp = httpx.get(
            FORECAST_URL,
            params={
                "latitude": lat,
                "longitude": lon,
                "daily": ",".join(
                    [
                        "weather_code",
                        "temperature_2m_max",
                        "temperature_2m_min",
                        "precipitation_sum",
                        "wind_speed_10m_max",
                        "precipitation_probability_max",
                    ]
                ),
                "temperature_unit": temp_unit,
                "wind_speed_unit": "mph" if temp_unit == "fahrenheit" else "kmh",
                "precipitation_unit": "inch" if temp_unit == "fahrenheit" else "mm",
                "forecast_days": days,
                "timezone": "auto",
            },
            timeout=10,
        )
        data = resp.json()
        daily = data.get("daily", {})
        dates = daily.get("time", [])
        codes = daily.get("weather_code", [])
        hi = daily.get("temperature_2m_max", [])
        lo = daily.get("temperature_2m_min", [])
        precip = daily.get("precipitation_sum", [])
        wind = daily.get("wind_speed_10m_max", [])
        rain_pct = daily.get("precipitation_probability_max", [])

        unit_symbol = "°F" if temp_unit == "fahrenheit" else "°C"
        speed_unit = "mph" if temp_unit == "fahrenheit" else "km/h"
        precip_unit = "in" if temp_unit == "fahrenheit" else "mm"

        days_out = []
        for i, date in enumerate(dates):
            code = codes[i] if i < len(codes) else 0
            days_out.append(
                {
                    "date": date,
                    "condition": _WMO_CODES.get(code, "Unknown"),
                    "emoji": _CONDITION_EMOJI.get(code, "🌡️"),
                    "high": round(hi[i], 1) if i < len(hi) and hi[i] is not None else None,
                    "low": round(lo[i], 1) if i < len(lo) and lo[i] is not None else None,
                    "precip": round(precip[i], 2)
                    if i < len(precip) and precip[i] is not None
                    else 0,
                    "wind": round(wind[i], 1) if i < len(wind) and wind[i] is not None else None,
                    "rain_pct": int(rain_pct[i])
                    if i < len(rain_pct) and rain_pct[i] is not None
                    else None,
                    "unit": unit_symbol,
                    "speed_unit": speed_unit,
                    "precip_unit": precip_unit,
                }
            )
        return {"days": days_out, "unit": unit_symbol}
    except Exception as e:
        logger.debug("Forecast fetch failed for %.4f,%.4f: %s", lat, lon, e)
        return {"days": [], "error": str(e)}


# ── Public tool handlers ─────────────────────────────────────────────────────


def add_location(data: dict) -> str:
    city = data.get("city", "").strip()
    if not city:
        return "Please provide a city name."
    unit = data.get("unit", "").strip().lower() or None
    geo = geocode(city)
    if not geo:
        return f"Couldn't find '{city}'. Try a more specific name (e.g. 'Portland, Oregon')."
    cfg = _load_config()
    if any(
        abs(loc["lat"] - geo["lat"]) < 0.1 and abs(loc["lon"] - geo["lon"]) < 0.1
        for loc in cfg["locations"]
    ):
        return f"Already tracking weather for {geo['name']}."
    cfg["locations"].append(
        {
            "name": geo["name"],
            "lat": geo["lat"],
            "lon": geo["lon"],
            "unit": unit or cfg.get("default_unit", "fahrenheit"),
        }
    )
    _save_config(cfg)
    return f"Added weather location: {geo['name']} ({geo['lat']:.2f}, {geo['lon']:.2f})"


def remove_location(data: dict) -> str:
    city = data.get("city", "").strip().lower()
    if not city:
        return "Please provide a city name."
    cfg = _load_config()
    before = len(cfg["locations"])
    cfg["locations"] = [loc for loc in cfg["locations"] if city not in loc["name"].lower()]
    if len(cfg["locations"]) == before:
        return f"No location matching '{city}' found."
    _save_config(cfg)
    return f"Removed weather location matching '{city}'."


def list_locations(data: dict) -> str:
    cfg = _load_config()
    locs = cfg.get("locations", [])
    if not locs:
        return "No weather locations saved. Say 'add weather location <city>'."
    lines = ["📍 Saved weather locations:"]
    for loc in locs:
        lines.append(f"  • {loc['name']} ({loc['unit']})")
    return "\n".join(lines)


def get_forecast(data: dict) -> str:
    """Get forecast for a specific city (or all saved locations if no city given)."""
    city = data.get("city", "").strip()
    days = min(int(data.get("days", 5)), 7)

    if city:
        geo = geocode(city)
        if not geo:
            return f"Couldn't find '{city}'."
        unit = data.get("unit", "fahrenheit")
        fc = fetch_forecast(geo["lat"], geo["lon"], unit=unit, days=days)
        return _format_forecast_text(geo["name"], fc)

    cfg = _load_config()
    locs = cfg.get("locations", [])
    if not locs:
        return "No locations saved. Say 'add weather location <city>' first."

    parts = []
    for loc in locs:
        fc = fetch_forecast(loc["lat"], loc["lon"], unit=loc.get("unit", "fahrenheit"), days=days)
        parts.append(_format_forecast_text(loc["name"], fc))
    return "\n\n".join(parts)


def _format_forecast_text(name: str, fc: dict) -> str:
    if fc.get("error") or not fc.get("days"):
        return f"⚠️ Could not fetch forecast for {name}."
    lines = [f"🌍 {name} — {len(fc['days'])}-Day Forecast"]
    for d in fc["days"]:
        rain = f" 💧{d['rain_pct']}%" if d["rain_pct"] is not None else ""
        wind = f" 💨{d['wind']}{d['speed_unit']}" if d["wind"] is not None else ""
        hi = f"{d['high']}{d['unit']}" if d["high"] is not None else "?"
        lo = f"{d['low']}{d['unit']}" if d["low"] is not None else "?"
        lines.append(f"  {d['emoji']} {d['date']}  {d['condition']}  ↑{hi} ↓{lo}{rain}{wind}")
    return "\n".join(lines)


def get_briefing_forecast() -> list[dict]:
    """Return structured forecast for all saved locations — used by briefing."""
    cfg = _load_config()
    locs = cfg.get("locations", [])
    result = []
    for loc in locs:
        fc = fetch_forecast(loc["lat"], loc["lon"], unit=loc.get("unit", "fahrenheit"), days=5)
        if fc.get("days"):
            result.append({"name": loc["name"], "unit": fc["unit"], "days": fc["days"]})
    return result


TOOLS = [
    {
        "name": "get_forecast",
        "description": (
            "Get a 5-day weather forecast for any city, or for all your saved locations. "
            "Uses Open-Meteo (free, no API key needed). "
            "Example: get_forecast city='Portland, Oregon' days=5"
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "City name (optional — uses saved locations if omitted)",
                },
                "days": {"type": "integer", "description": "Number of days 1-7 (default 5)"},
                "unit": {
                    "type": "string",
                    "description": "fahrenheit or celsius (default fahrenheit)",
                },
            },
        },
        "handler": get_forecast,
        "category": "weather",
    },
    {
        "name": "add_weather_location",
        "description": (
            "Save a location so its forecast appears in your daily briefing every morning. "
            "Example: add_weather_location city='Chicago, IL' unit='fahrenheit'"
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name or 'City, State/Country'"},
                "unit": {"type": "string", "description": "fahrenheit or celsius"},
            },
            "required": ["city"],
        },
        "handler": add_location,
        "category": "weather",
    },
    {
        "name": "remove_weather_location",
        "description": "Remove a saved weather location from your daily briefing.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name to remove"},
            },
            "required": ["city"],
        },
        "handler": remove_location,
        "category": "weather",
    },
    {
        "name": "list_weather_locations",
        "description": "List all saved weather locations in your daily briefing.",
        "input_schema": {"type": "object", "properties": {}},
        "handler": list_locations,
        "category": "weather",
    },
]
