"""
FRED API fetcher — récupère les dernières releases macro US.

Séries suivies :
  CPIAUCSL   → CPI mensuel (YoY calculé)
  PAYEMS     → Non-Farm Payrolls (variation mensuelle k emplois)
  UNRATE     → Taux de chômage
  FEDFUNDS   → Fed Funds Rate
  GDP        → PIB US trimestriel (croissance %)
  ICSA       → Initial Jobless Claims (hebdo)
  T10Y2Y     → Spread 10Y-2Y (courbe des taux)
  DEXUSEU    → EUR/USD (proxy macro)
"""
from __future__ import annotations

import logging
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional

logger = logging.getLogger("fred_fetcher")

# Series config : (id, label, unit, asset_impact, direction_interpretation)
FRED_SERIES = [
    ("CPIAUCSL", "US CPI (inflation)",      "index",  ["metals", "rates", "forex"],  "higher_bearish"),
    ("PAYEMS",   "US Non-Farm Payrolls",     "k jobs", ["indices", "forex", "rates"], "higher_bullish"),
    ("UNRATE",   "US Unemployment Rate",     "%",      ["indices", "forex"],          "higher_bearish"),
    ("FEDFUNDS", "Fed Funds Rate",           "%",      ["bonds", "forex", "indices"], "higher_bearish_growth"),
    ("GDP",      "US GDP Growth",            "bn$",    ["indices", "forex"],          "higher_bullish"),
    ("ICSA",     "US Initial Jobless Claims","k claims",["indices", "forex"],         "higher_bearish"),
    ("T10Y2Y",   "10Y-2Y Spread (courbe)",  "%",      ["bonds", "indices"],          "positive_bullish"),
]


def _get_fred_key() -> Optional[str]:
    try:
        from services.database import get_config
        return get_config("fred_api_key") or None
    except Exception:
        return None


def _fetch_series_observations(series_id: str, api_key: str, count: int = 3) -> List[Dict]:
    """Fetch last N observations from FRED for a given series."""
    import urllib.request
    import urllib.parse
    import json

    params = urllib.parse.urlencode({
        "series_id": series_id,
        "api_key": api_key,
        "file_type": "json",
        "sort_order": "desc",
        "limit": count,
        "observation_start": (datetime.utcnow() - timedelta(days=730)).strftime("%Y-%m-%d"),
    })
    url = f"https://api.stlouisfed.org/fred/series/observations?{params}"
    try:
        with urllib.request.urlopen(url, timeout=8) as resp:
            data = json.loads(resp.read())
        obs = data.get("observations", [])
        return [{"date": o["date"], "value": o["value"]} for o in obs if o.get("value") not in (".", None)]
    except Exception as e:
        logger.warning(f"[FRED] {series_id} fetch failed: {e}")
        return []


def _parse_value(v: str) -> Optional[float]:
    try:
        return float(v)
    except (ValueError, TypeError):
        return None


def _surprise_direction(series_id: str, current: float, previous: float, direction_hint: str) -> str:
    """Returns 'bullish', 'bearish', or 'neutral' based on change direction."""
    delta = current - previous
    if abs(delta) < 0.01:
        return "neutral"
    if direction_hint == "higher_bullish":
        return "bullish" if delta > 0 else "bearish"
    elif direction_hint in ("higher_bearish", "higher_bearish_growth"):
        return "bearish" if delta > 0 else "bullish"
    elif direction_hint == "positive_bullish":
        return "bullish" if current > 0 else "bearish"
    return "neutral"


def _yoy_change(obs: List[Dict]) -> Optional[float]:
    """For monthly series, compute rough YoY % if we have ≥12 month history."""
    if len(obs) < 2:
        return None
    v_latest = _parse_value(obs[0]["value"])
    v_prev = _parse_value(obs[-1]["value"])
    if v_latest is None or v_prev is None or v_prev == 0:
        return None
    return round((v_latest - v_prev) / abs(v_prev) * 100, 2)


def get_fred_recent_releases() -> List[Dict[str, Any]]:
    """
    Fetch latest FRED data for key macro series.

    Returns a list of dicts, one per series, with:
      series_id, label, unit, latest_date, latest_value,
      previous_value, change, change_pct, direction, asset_impact
    """
    api_key = _get_fred_key()
    if not api_key:
        return []

    results = []
    for series_id, label, unit, asset_impact, direction_hint in FRED_SERIES:
        obs = _fetch_series_observations(series_id, api_key, count=13)  # 13 for YoY
        if not obs:
            continue
        latest = obs[0]
        previous = obs[1] if len(obs) > 1 else None

        v_latest = _parse_value(latest["value"])
        v_prev = _parse_value(previous["value"]) if previous else None

        if v_latest is None:
            continue

        change = round(v_latest - v_prev, 4) if v_prev is not None else None
        change_pct = round((v_latest - v_prev) / abs(v_prev) * 100, 2) if v_prev and v_prev != 0 else None
        direction = _surprise_direction(series_id, v_latest, v_prev, direction_hint) if v_prev is not None else "neutral"

        # For CPI, compute YoY
        display_value = v_latest
        display_unit = unit
        if series_id == "CPIAUCSL" and len(obs) >= 13:
            yoy = _yoy_change(obs[:13])
            if yoy is not None:
                display_value = yoy
                display_unit = "% YoY"
                # Re-compute change vs previous month's YoY
                if len(obs) >= 14:
                    yoy_prev = _yoy_change(obs[1:14])
                    if yoy_prev is not None:
                        change = round(yoy - yoy_prev, 3)
                        direction = "bearish" if change > 0 else "bullish"  # higher CPI = bearish markets

        entry: Dict[str, Any] = {
            "series_id": series_id,
            "label": label,
            "unit": display_unit,
            "latest_date": latest["date"],
            "latest_value": display_value,
            "previous_value": v_prev,
            "change": change,
            "direction": direction,
            "asset_impact": asset_impact,
        }
        results.append(entry)

    return results


def _compute_zscore_surprise(series_id: str, api_key: str, latest_value: float) -> tuple:
    """Compute z-score of latest value vs 12-period MA. Returns (forecast, surprise_pct, zscore)."""
    import math
    obs = _fetch_series_observations(series_id, api_key, count=14)
    values = [_parse_value(o["value"]) for o in obs if _parse_value(o["value"]) is not None]
    if len(values) < 3:
        return None, None, None
    # Use obs[1:] as history (exclude latest), up to 12 periods
    history = values[1:13]
    if not history:
        return None, None, None
    mean = sum(history) / len(history)
    variance = sum((x - mean) ** 2 for x in history) / len(history)
    std = math.sqrt(variance) if variance > 0 else 0.0
    surprise_pct = round((latest_value - mean) / abs(mean) * 100, 2) if mean != 0 else 0.0
    zscore = round((latest_value - mean) / std, 2) if std > 0 else 0.0
    return round(mean, 4), surprise_pct, zscore


def save_fred_releases_to_db(releases: List[Dict], api_key: str = "") -> int:
    """Persist FRED releases to economic_events table with surprise scores. Returns count saved."""
    from services.database import save_economic_event
    import json as _json

    # Map series_id → assets_impacted from FRED_SERIES config
    _assets_map = {sid: assets for sid, _, _, assets, _ in FRED_SERIES}

    saved = 0
    for r in releases:
        sid = r.get("series_id", "")
        if not sid or r.get("latest_value") is None:
            continue
        # Compute z-score surprise if api_key available
        forecast_val, surprise_pct, zscore = (None, None, None)
        if api_key:
            try:
                forecast_val, surprise_pct, zscore = _compute_zscore_surprise(sid, api_key, r["latest_value"])
            except Exception:
                pass

        try:
            save_economic_event(
                event_name=r.get("label", sid),
                series_id=sid,
                event_date=r.get("latest_date", ""),
                actual_value=r.get("latest_value"),
                actual_unit=r.get("unit", ""),
                forecast_value=forecast_val,
                previous_value=r.get("previous_value"),
                surprise_pct=surprise_pct,
                surprise_zscore=zscore,
                surprise_direction=r.get("direction", "neutral"),
                assets_impacted=_assets_map.get(sid, []),
                source="FRED",
            )
            saved += 1
        except Exception as e:
            logger.warning(f"[FRED] Failed to save {sid} to DB: {e}")

    return saved


def build_fred_context_block(releases: List[Dict]) -> str:
    """Format FRED releases as a prompt-ready string with surprise scoring."""
    if not releases:
        return ""
    lines = ["## RECENT MACRO DATA (FRED — latest releases)"]
    for r in releases:
        val_str = f"{r['latest_value']:.2f} {r['unit']}" if isinstance(r.get("latest_value"), float) else str(r.get("latest_value", "N/A"))
        chg_str = ""
        if r.get("change") is not None:
            arrow = "+" if r["change"] > 0 else ""
            chg_str = f" ({arrow}{r['change']:.3f} vs prior)"
        surprise_str = ""
        if r.get("surprise_zscore") is not None:
            z = r["surprise_zscore"]
            if abs(z) >= 1.5:
                surprise_str = f" ⚡ SURPRISE z={z:+.1f}"
            elif abs(z) >= 0.8:
                surprise_str = f" (notable z={z:+.1f})"
        dir_tag = {"bullish": "[BULLISH]", "bearish": "[BEARISH]", "neutral": "[NEUTRAL]"}.get(r.get("direction", "neutral"), "")
        lines.append(
            f"  {r['label']} [{r['latest_date']}]: {val_str}{chg_str}{surprise_str} → {dir_tag}"
        )
    lines.append("Use these figures to assess whether current market pricing already reflects macro reality.")
    return "\n".join(lines)


def build_economic_surprise_block(days: int = 14) -> str:
    """Build a prompt block from stored economic_events table — recent surprises highlighted."""
    try:
        from services.database import get_recent_economic_surprises
        events = get_recent_economic_surprises(days=days, min_zscore=0.0)
        if not events:
            return ""
        lines = [f"## ECONOMIC SURPRISE TRACKER (last {days} days — FRED actuals vs trend baseline)"]
        for ev in events[:8]:
            z = ev.get("surprise_zscore") or 0.0
            sp = ev.get("surprise_pct") or 0.0
            direction = ev.get("surprise_direction", "neutral")
            actual = ev.get("actual_value")
            unit = ev.get("actual_unit", "")
            forecast = ev.get("forecast_value")
            flag = ""
            if abs(z) >= 1.5:
                flag = " ⚡ SIGNIFICANT SURPRISE"
            elif abs(z) >= 0.8:
                flag = " (notable)"
            actual_str = f"{actual:.2f}{unit}" if actual is not None else "N/A"
            forecast_str = f"{forecast:.2f}{unit}" if forecast is not None else "N/A"
            dir_tag = {"bullish": "BULLISH", "bearish": "BEARISH", "neutral": "NEUTRAL"}.get(direction, "")
            lines.append(
                f"  [{ev['event_date']}] {ev['event_name']}: actual={actual_str} vs baseline={forecast_str}"
                f" (z={z:+.2f}, {sp:+.1f}%) → {dir_tag}{flag}"
            )
        lines.append("→ High z-scores = unexpected move vs recent trend → potential mispricing in related assets")
        return "\n".join(lines)
    except Exception as e:
        logger.warning(f"[FRED] build_economic_surprise_block failed: {e}")
        return ""
