# -*- coding: utf-8 -*-
"""
Gold Market Data Module
- COMEX Futures (GC=F): price, volume, OI via yfinance
- CFTC COT Report: managed money positioning
- GLD ETF: fund flows as institutional demand proxy
- Macro drivers: TIPS yield, DXY, VIX, HYG
"""
import math, time, json, os
from collections import deque
from exchange.http import http_get


# ── COMEX Futures data (real-time + historical) ──
def comex_price():
    """Get latest COMEX gold futures price"""
    try:
        import yfinance as yf
        t = yf.Ticker("GC=F")
        info = t.info
        px = info.get("regularMarketPrice", 0) or info.get("previousClose", 0)
        bid = info.get("bid", 0) or 0
        ask = info.get("ask", 0) or 0
        return float(px), float(bid), float(ask)
    except Exception:
        return 0, 0, 0


def comex_candles(bar="1H", limit=100):
    """Get COMEX GC=F candles"""
    try:
        import yfinance as yf
        interval = {"1m": "1m", "5m": "5m", "15m": "15m", "1H": "1h", "4H": "1h", "1D": "1d"}.get(bar, "1h")
        days = min(limit * {"1m": 1/60, "5m": 5/60, "15m": 0.25, "1H": 1, "4H": 4, "1D": 24}.get(bar, 1) / 24, 60)
        period = f"{max(1, int(days))}d"

        t = yf.Ticker("GC=F")
        df = t.history(period=period, interval=interval)
        if df.empty:
            return []
        candles = []
        for idx, row in df.iterrows():
            candles.append([
                str(idx), float(row['Open']), float(row['High']),
                float(row['Low']), float(row['Close']), float(row['Volume'])
            ])
        return candles[-limit:]
    except Exception:
        return []


def comex_volume_oi():
    """Get latest COMEX volume + Open Interest (from daily bars)"""
    try:
        import yfinance as yf
        t = yf.Ticker("GC=F")
        df = t.history(period="30d", interval="1d")
        if df.empty or "Volume" not in df.columns:
            return 0, 0, 0
        vols = df["Volume"].dropna().tolist()
        if len(vols) < 5: return 0, 0, 0
        current = float(vols[-1])
        avg_vol = float(sum(vols[:-1]) / len(vols[:-1]))  # avg of all but today
        # OI from info (most recent available)
        oi = float(t.info.get("openInterest", 0) or 0)
        return current, avg_vol, oi
    except Exception:
        return 0, 0, 0


def comex_oi_history(days=20):
    """
    Get COMEX Open Interest history.
    yfinance daily bars don't include OI, so we estimate from:
    - 5-day OI change from consecutive info calls (cached)
    - Volume trend as OI proxy
    """
    try:
        import yfinance as yf
        t = yf.Ticker("GC=F")
        df = t.history(period=f"{days+5}d", interval="1d")
        if df.empty:
            return []
        # Use volume * price as OI proxy (rough but directionally correct)
        oi_est = []
        for idx, row in df.iterrows():
            vol = float(row.get('Volume', 0) or 0)
            close = float(row.get('Close', 0) or 0)
            oi_est.append(vol * close)
        return oi_est
    except Exception:
        return []


# ── CFTC COT Report (Commitments of Traders) ──
# Data source: cotbase or CFTC JSON API
# Managed Money = hedge funds / CTAs → most relevant for positioning extremes

def cot_positioning():
    """
    CFTC COT gold positioning. Try multiple API formats.
    管理基金(Managed Money)净持仓 → 极端值反转信号
    """
    try:
        # CFTC JSON endpoint (newer API)
        r = http_get(
            "https://www.cftc.gov/api/Reports/CotTxData/Data/GetCOTData",
            params={"market": "GOLD", "format": "json"}, timeout=15)
        data = r.json()
        reports = data.get("CotTxData", []) if isinstance(data, dict) else data
        if reports:
            latest = reports[0] if isinstance(reports, list) else reports
            mm_long = float(latest.get("M_MONEY_POSITIONS_LONG_ALL", 0) or 0)
            mm_short = float(latest.get("M_MONEY_POSITIONS_SHORT_ALL", 0) or 0)
            net = mm_long - mm_short
            total_oi = float(latest.get("OPEN_INTEREST_ALL", 0) or 0)
            net_pct = round(net / total_oi * 100, 1) if total_oi > 0 else 0
            return {
                "managed_money_long": int(mm_long),
                "managed_money_short": int(mm_short),
                "net_position": int(net),
                "net_pct_oi": net_pct,
                "total_oi": int(total_oi),
                "report_date": latest.get("REPORT_DATE_AS_MM_DD_YYYY", ""),
                "source": "CFTC COT",
            }
    except Exception:
        pass

    # Fallback: estimate positioning from price-OI divergence
    try:
        px, _, _ = comex_price()
        oi_list = comex_oi_history(20)
        if px > 0 and len(oi_list) >= 10:
            # Rising price + rising OI = speculators adding longs (bullish positioning)
            # Rising price + falling OI = shorts covering / longs exiting (neutral)
            px_5d_ago = comex_candles(bar="1D", limit=6)
            if px_5d_ago and len(px_5d_ago) >= 5:
                px_prev = px_5d_ago[-5][4]
                px_chg = (px - px_prev) / px_prev
                oi_recent = sum(oi_list[-5:]) / min(len(oi_list[-5:]), 1)
                oi_prev = sum(oi_list[:5]) / min(len(oi_list[:5]), 1)
                oi_chg = (oi_recent - oi_prev) / oi_prev if oi_prev > 0 else 0
                # OI+Price aligned = trend following positioning
                # OI up + Price up = net long speculators
                net_pct = max(-30, min(30, (oi_chg / 0.05) * (1 if px_chg > 0 else -1) * 15))
                return {
                    "net_pct_oi": round(net_pct, 1),
                    "estimated": True,
                    "source": "Price-OI estimate"
                }
    except Exception:
        pass
    return None


# ── GLD ETF Flow ──
def gld_flow():
    """Get GLD ETF daily volume + price (institutional gold demand proxy)"""
    try:
        import yfinance as yf
        t = yf.Ticker("GLD")
        info = t.info
        vol = info.get("volume", 0) or 0
        avg_vol = info.get("averageVolume", 0) or info.get("averageDailyVolume10Day", 0) or 0
        px = info.get("regularMarketPrice", 0) or info.get("previousClose", 0)
        # Net flow proxy: volume * price direction
        prev_close = info.get("previousClose", px)
        flow_direction = 1 if px > prev_close else (-1 if px < prev_close else 0)
        flow_value = vol * px * flow_direction
        return {
            "price": float(px),
            "volume": float(vol),
            "avg_volume": float(avg_vol),
            "flow_direction": flow_direction,
            "flow_value": round(float(flow_value), 0),
            "source": "GLD ETF",
        }
    except Exception:
        return None


# ── Macro Drivers ──
def macro_drivers():
    """Get key macro drivers for gold: real rates, DXY, VIX, credit spreads"""
    result = {}
    try:
        import yfinance as yf
        # Real rate proxy: TIP ETF price (inverse of real yield)
        t_tip = yf.Ticker("TIP")
        tip_info = t_tip.info
        result["tips_price"] = float(tip_info.get("regularMarketPrice", 0) or tip_info.get("previousClose", 0) or 0)
        result["tips_change"] = float(tip_info.get("regularMarketChangePercent", 0) or 0)

        # DXY proxy
        t_dxy = yf.Ticker("UUP")
        dxy_info = t_dxy.info
        result["dxy_price"] = float(dxy_info.get("regularMarketPrice", 0) or dxy_info.get("previousClose", 0) or 0)
        result["dxy_change"] = float(dxy_info.get("regularMarketChangePercent", 0) or 0)

        # VIX
        t_vix = yf.Ticker("^VIX")
        vix_info = t_vix.info
        result["vix"] = float(vix_info.get("regularMarketPrice", 0) or vix_info.get("previousClose", 0) or 0)

        # Credit: HYG
        t_hyg = yf.Ticker("HYG")
        hyg_info = t_hyg.info
        result["hyg_price"] = float(hyg_info.get("regularMarketPrice", 0) or hyg_info.get("previousClose", 0) or 0)

        result["source"] = "Yahoo Finance"
    except Exception:
        pass
    return result


# ── Gold-Specific News Sources ──
# 黄金资讯: Kitco / ForexLive / 经济数据日历
# 与BTC新闻体系完全独立，专注贵金属和宏观经济

GOLD_NEWS_SOURCES = [
    # Kitco gold news (RSS)
    {"url": "https://www.kitco.com/news/gold/feed", "label": "Kitco Gold", "type": "rss"},
    # ForexLive gold feed
    {"url": "https://www.forexlive.com/feed/gold", "label": "ForexLive Gold", "type": "rss"},
]

GOLD_KEYWORDS = [
    # 核心
    "gold", "xau", "precious metal", "bullion", "comex", "lbma",
    "黄金", "贵金属", "金价", "金市",
    # 宏观驱动
    "fed", "fomc", "interest rate", "inflation", "cpi", "ppi", "nfp",
    "nonfarm", "gdp", "recession", "美联储", "加息", "降息", "通胀", "非农",
    "ecb", "boj", "bank of england", "央行", "利率决议",
    # 美元/汇率
    "dollar", "dxy", "usd", "eurusd", "usdjpy", "treasury yield",
    "美元", "美债", "收益率", "汇率",
    # 地缘政治 (黄金避险)
    "geopolitical", "war", "sanctions", "tariff", "trade war",
    "iran", "russia", "ukraine", "middle east", "taiwan",
    "地缘", "战争", "制裁", "关税", "伊朗", "中东",
    # 实物需求
    "central bank gold", "gold reserve", "china gold", "india gold",
    "央行购金", "黄金储备", "etf flow", "gold etf",
    # 风险情绪
    "risk aversion", "safe haven", "vix", "stock market crash",
    "避险", "恐慌", "崩盘", "熔断",
]


def fetch_gold_news():
    """
    Fetch gold-related news from dedicated sources.
    Returns list of {title, source, age_m, url}
    """
    import re as _re
    news_items = []

    # Source 1: Kitco RSS
    try:
        r = http_get("https://www.kitco.com/news/gold/feed", timeout=8,
                     headers={"User-Agent": "Mozilla/5.0"})
        # Parse RSS XML
        items = _re.findall(r'<item>(.*?)</item>', r.text, _re.DOTALL)
        for item in items[:10]:
            title_m = _re.search(r'<title>(.*?)</title>', item)
            date_m = _re.search(r'<pubDate>(.*?)</pubDate>', item)
            if title_m:
                news_items.append({
                    "title": _re.sub(r'<[^>]+>', '', title_m.group(1)),
                    "source": "Kitco",
                    "age_m": 0,
                })
    except Exception:
        pass

    # Source 2: ForexLive gold-related headlines via web scraping
    try:
        r = http_get("https://www.forexlive.com/technical-analysis/gold/", timeout=8,
                     headers={"User-Agent": "Mozilla/5.0"})
        # Extract article titles
        titles = _re.findall(r'<a[^>]*class="article__title"[^>]*>(.*?)</a>', r.text)
        for title in titles[:8]:
            clean = _re.sub(r'<[^>]+>', '', title).strip()
            if clean and any(kw.lower() in clean.lower() for kw in ["gold", "xau", "fed", "fomc", "rate", "inflation"]):
                news_items.append({
                    "title": clean[:150],
                    "source": "ForexLive",
                    "age_m": 0,
                })
    except Exception:
        pass

    # Source 3: Economic calendar (high-impact events for gold)
    try:
        r = http_get("https://cdn-nfs-prod.Forexfactory.com/ff-calendar.xml", timeout=8)
        events = _re.findall(r'<event>(.*?)</event>', r.text, _re.DOTALL)
        for ev in events[:15]:
            title_m = _re.search(r'<title>(.*?)</title>', ev)
            impact_m = _re.search(r'<impact>(.*?)</impact>', ev)
            if title_m and impact_m and impact_m.group(1) == "High":
                title = title_m.group(1)
                if any(kw.lower() in title.lower() for kw in ["fed", "fomc", "gdp", "cpi", "ppi", "nfp", "nonfarm",
                    "unemployment", "retail sales", "industrial", "consumer", "inflation", "ism", "pmi"]):
                    news_items.append({
                        "title": f"[经济日历] {title}",
                        "source": "ForexFactory",
                        "age_m": 0,
                    })
    except Exception:
        pass

    return news_items if news_items else None


# ── RSI Calculator ──
def calc_rsi(prices, period=14):
    if len(prices) < period + 1:
        return 50
    gains, losses = [], []
    for i in range(1, len(prices)):
        chg = prices[i] - prices[i-1]
        gains.append(max(chg, 0))
        losses.append(max(-chg, 0))
    avg_gain = sum(gains[-period:]) / period
    avg_loss = sum(losses[-period:]) / period
    if avg_loss == 0:
        return 100
    rs = avg_gain / avg_loss
    return 100 - 100 / (1 + rs)


# ── Bollinger Bands ──
def calc_bb(prices, period=20, std=2):
    if len(prices) < period:
        return None
    recent = prices[-period:]
    ma = sum(recent) / period
    variance = sum((p - ma) ** 2 for p in recent) / period
    stddev = math.sqrt(variance)
    upper = ma + std * stddev
    lower = ma - std * stddev
    last = prices[-1]
    pct_b = (last - lower) / (upper - lower) * 100 if (upper - lower) > 0 else 50
    bandwidth = (upper - lower) / ma * 100 if ma > 0 else 0
    return {"ma": round(ma, 2), "upper": round(upper, 2), "lower": round(lower, 2),
            "pct_b": round(pct_b, 1), "bandwidth": round(bandwidth, 2)}
