"""
Forward Curve Service — contango / backwardation per commodity.

Fetches front-month continuous futures prices from Yahoo Finance in a single
batch request to avoid rate limiting.  The "far" price uses a secondary batch
attempt for deferred contracts; if unavailable the structure is reported as
"unknown" rather than generating ERROR logs that pollute the system.
"""
import logging
import time
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional, Tuple

logger = logging.getLogger(__name__)

# (base_ticker, label, asset_class, months_spread)
_CURVE_SPECS: List[Tuple[str, str, str, int]] = [
    ("CL",  "WTI Crude",    "energy",  3),
    ("NG",  "Natural Gas",  "energy",  3),
    ("GC",  "Gold",         "metals",  3),
    ("SI",  "Silver",       "metals",  3),
    ("HG",  "Copper",       "metals",  3),
    ("ZC",  "Corn",         "agri",    3),
    ("ZW",  "Wheat",        "agri",    3),
    ("ZS",  "Soybeans",     "agri",    3),
]

# Month codes for futures tickers (standard CME/NYMEX/CBOT convention)
_MONTH_CODES = {
    1: "F", 2: "G", 3: "H", 4: "J", 5: "K", 6: "M",
    7: "N", 8: "Q", 9: "U", 10: "V", 11: "X", 12: "Z",
}

# Exchange suffixes required by Yahoo Finance for specific monthly contracts
_EXCHANGE_SUFFIX = {
    "CL": ".NYM", "NG": ".NYM",
    "GC": ".CMX", "SI": ".CMX", "HG": ".CMX",
    "ZC": ".CBT", "ZW": ".CBT", "ZS": ".CBT",
}


def _deferred_tickers(base: str, months_ahead: int) -> List[str]:
    """Return candidate deferred-contract tickers to try (with exchange suffix)."""
    candidates = []
    suffix = _EXCHANGE_SUFFIX.get(base, "")
    for offset in [0, 1, -1, 2, -2]:
        target = datetime.now() + timedelta(days=(months_ahead + offset) * 30)
        code = _MONTH_CODES[target.month]
        year = str(target.year)[-2:]
        candidates.append(f"{base}{code}{year}{suffix}")
    return candidates


def _batch_prices(tickers: List[str]) -> Dict[str, Optional[float]]:
    """Download a list of tickers in a single yfinance call. Returns {ticker: price}."""
    import yfinance as yf

    if not tickers:
        return {}
    try:
        data = yf.download(
            tickers,
            period="5d",
            progress=False,
            auto_adjust=True,
            group_by="ticker",
            threads=False,
        )
        result: Dict[str, Optional[float]] = {}
        for t in tickers:
            try:
                if len(tickers) == 1:
                    col = data["Close"]
                else:
                    col = data[t]["Close"] if t in data.columns.get_level_values(0) else None
                if col is None or col.dropna().empty:
                    result[t] = None
                else:
                    result[t] = round(float(col.dropna().iloc[-1]), 4)
            except Exception:
                result[t] = None
        return result
    except Exception as e:
        logger.debug(f"Forward curve batch download failed: {e}")
        return {t: None for t in tickers}


def fetch_forward_curves() -> List[Dict[str, Any]]:
    """Fetch front-month prices and attempt M+3 deferred prices for 8 commodities.

    Uses two batch downloads: one for all front-month contracts, one for candidate
    deferred tickers.  Yahoo Finance does not reliably expose specific monthly
    commodity contracts, so deferred prices are treated as optional; missing data
    is reported as structure='unknown' rather than raising ERROR logs.
    """
    front_tickers = [f"{base}=F" for base, *_ in _CURVE_SPECS]

    # ── Batch 1: front-month prices ──────────────────────────────────────────
    front_prices = _batch_prices(front_tickers)
    time.sleep(1)  # brief pause before second batch

    # ── Batch 2: deferred candidates (best-effort) ───────────────────────────
    deferred_candidates: List[str] = []
    for base, _, _, spread in _CURVE_SPECS:
        deferred_candidates.extend(_deferred_tickers(base, spread))
    deferred_prices = _batch_prices(deferred_candidates)

    results: List[Dict[str, Any]] = []
    for base, label, ac, spread in _CURVE_SPECS:
        front_ticker = f"{base}=F"
        front_price = front_prices.get(front_ticker)

        if not front_price:
            logger.debug(f"Forward curve: no front-month price for {label} ({front_ticker})")
            continue

        # Pick first available deferred price from candidates
        far_price: Optional[float] = None
        for t in _deferred_tickers(base, spread):
            p = deferred_prices.get(t)
            if p and p > 0:
                far_price = p
                break

        slope_pct: Optional[float] = None
        structure = "unknown"
        if far_price:
            slope_pct = round((far_price - front_price) / front_price * 100, 2)
            if slope_pct > 0.15:
                structure = "contango"
            elif slope_pct < -0.15:
                structure = "backwardation"
            else:
                structure = "flat"

        results.append({
            "asset": label,
            "asset_class": ac,
            "front_price": round(front_price, 2),
            "far_price": round(far_price, 2) if far_price else None,
            "slope_pct": slope_pct,
            "structure": structure,
            "months_spread": spread,
        })
        logger.info(
            f"Forward curve {label}: front={front_price:.2f} "
            f"far={far_price} slope={slope_pct}% -> {structure}"
        )

    return results
