from __future__ import annotations

import argparse
import json
import re
import urllib.request
import xml.etree.ElementTree as ET
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from src.market.context import DEFAULT_CONTEXT_UNIVERSE
from src.market.event_risk import event_risk_from_news_items

DEFAULT_FEEDS = (
    "https://www.coindesk.com/arc/outboundfeeds/rss/",
    "https://cointelegraph.com/rss",
)
SYMBOL_ALIASES: dict[str, tuple[str, ...]] = {
    "BTC": ("bitcoin", "btc"),
    "ETH": ("ethereum", "ether", "eth"),
    "SOL": ("solana", "sol"),
    "LINK": ("chainlink", "link"),
    "WLD": ("worldcoin", "wld"),
    "SUI": ("sui",),
    "ENA": ("ethena", "ena"),
    "BCH": ("bitcoin cash", "bch"),
    "HYPE": ("hyperliquid", "hype"),
}


def _fetch_text(url: str, *, timeout: int = 10) -> str:
    req = urllib.request.Request(url, headers={"User-Agent": "CryptoTradingBotResearch/1.0"})
    with urllib.request.urlopen(req, timeout=timeout) as response:  # nosec B310 - configured public RSS URLs only by default
        return response.read().decode("utf-8", "replace")


def _strip_html(text: str) -> str:
    return re.sub(r"<[^>]+>", " ", text or "").strip()


def _symbols_for_text(text: str, coins: tuple[str, ...]) -> list[str]:
    lowered = text.lower()
    symbols: list[str] = []
    for coin in coins:
        aliases = SYMBOL_ALIASES.get(coin.upper(), (coin.lower(),))
        if any(re.search(rf"(?<![a-z0-9]){re.escape(alias.lower())}(?![a-z0-9])", lowered) for alias in aliases):
            symbols.append(coin.upper())
    if any(word in lowered for word in ("crypto", "bitcoin", "fed", "fomc", "cpi", "sec")):
        symbols.append("MARKET")
    return sorted(set(symbols))


def parse_rss_items(feed_text: str, *, source: str, coins: tuple[str, ...], limit: int = 30) -> list[dict[str, Any]]:
    try:
        root = ET.fromstring(feed_text)
    except ET.ParseError:
        return []
    items: list[dict[str, Any]] = []
    for item in root.findall(".//item")[:limit]:
        title = item.findtext("title") or ""
        description = _strip_html(item.findtext("description") or "")
        link = item.findtext("link") or ""
        pub_date = item.findtext("pubDate") or item.findtext("published") or ""
        text = f"{title} {description}"
        symbols = _symbols_for_text(text, coins)
        if not symbols:
            continue
        items.append({
            "source": source,
            "title": title.strip(),
            "summary": description[:500],
            "url": link.strip(),
            "published_at": pub_date.strip(),
            "symbols": symbols,
            "category": "crypto_news_rss",
            "research_only": True,
            "live_order_allowed": False,
            "mainnet_signed_action": False,
        })
    return items


def collect_news_event_risk(*, feeds: tuple[str, ...] = DEFAULT_FEEDS, coins: tuple[str, ...] = DEFAULT_CONTEXT_UNIVERSE) -> dict[str, Any]:
    items: list[dict[str, Any]] = []
    errors: list[dict[str, str]] = []
    for feed in feeds:
        try:
            items.extend(parse_rss_items(_fetch_text(feed), source=feed, coins=coins))
        except Exception as exc:
            errors.append({"source": feed, "error_type": type(exc).__name__})
    # Deduplicate by title/url while keeping order.
    seen: set[tuple[str, str]] = set()
    deduped: list[dict[str, Any]] = []
    for item in items:
        key = (str(item.get("title", "")), str(item.get("url", "")))
        if key in seen:
            continue
        seen.add(key)
        deduped.append(item)
    by_coin = {coin.upper(): event_risk_from_news_items(coin.upper(), deduped).to_dict() for coin in coins}
    status = "loaded_research_only" if deduped else "unavailable" if errors else "not_loaded"
    return {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "source": "rss_news_event_risk_collector",
        "status": status,
        "feeds": list(feeds),
        "errors": errors,
        "items_seen": len(deduped),
        "items": deduped[:100],
        "by_coin": by_coin,
        "research_only": True,
        "live_order_allowed": False,
        "mainnet_signed_action": False,
    }


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="Collect crypto news/event risk from public RSS feeds; research-only, no orders.")
    parser.add_argument("--coins", default=",".join(DEFAULT_CONTEXT_UNIVERSE))
    parser.add_argument("--feed", action="append", default=[])
    parser.add_argument("--output", default="runtime/research/news_event_risk_latest.json")
    parser.add_argument("--json", action="store_true")
    args = parser.parse_args(argv)
    coins = tuple(c.strip().upper() for c in args.coins.split(",") if c.strip())
    feeds = tuple(args.feed) if args.feed else DEFAULT_FEEDS
    payload = collect_news_event_risk(feeds=feeds, coins=coins)
    output = Path(args.output)
    output.parent.mkdir(parents=True, exist_ok=True)
    output.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
    print(json.dumps(payload if args.json else {"status": payload["status"], "output": str(output), "items_seen": payload["items_seen"], "live_order_allowed": False, "mainnet_signed_action": False}, indent=2, sort_keys=True))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
