from __future__ import annotations

import json
from dataclasses import dataclass
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from pathlib import Path
from typing import Any

HIGH_RISK_KEYWORDS = (
    "hack", "exploit", "halt", "outage", "delist", "lawsuit", "sec", "cftc",
    "unlock", "token unlock", "bridge", "depeg", "bankruptcy", "emergency",
)
SYSTEMIC_RISK_KEYWORDS = ("exchange outage", "stablecoin", "depeg", "bankruptcy", "market crash")
MACRO_KEYWORDS = ("cpi", "fomc", "fed", "rate decision", "powell", "jobs report", "ppi")


@dataclass(frozen=True)
class EventRiskContext:
    coin: str
    status: str = "not_loaded"
    risk_level: str = "unknown"  # green/yellow/red/unknown
    score: str = "70.00"  # higher is safer
    reasons: tuple[str, ...] = ()
    source: str = "news_search_research_context"
    research_only: bool = True
    live_order_allowed: bool = False
    mainnet_signed_action: bool = False

    def to_dict(self) -> dict[str, Any]:
        return {
            "source": self.source,
            "status": self.status,
            "risk_level": self.risk_level,
            "score": self.score,
            "reasons": list(self.reasons),
            "research_only": self.research_only,
            "live_order_allowed": self.live_order_allowed,
            "mainnet_signed_action": self.mainnet_signed_action,
        }


def _score_from_reasons(reasons: list[str]) -> tuple[str, str]:
    if any(reason.startswith("red_") for reason in reasons):
        return "red", "20.00"
    if reasons:
        return "yellow", "50.00"
    return "green", "80.00"


def _parse_item_time(value: Any) -> datetime | None:
    if not value:
        return None
    text = str(value)
    try:
        parsed = parsedate_to_datetime(text)
    except Exception:
        try:
            parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
        except Exception:
            return None
    return parsed if parsed.tzinfo else parsed.replace(tzinfo=timezone.utc)


def event_risk_from_news_items(coin: str, items: list[dict[str, Any]], *, now: datetime | None = None) -> EventRiskContext:
    """Classify news/event risk from already-collected research items.

    This is intentionally conservative and side-effect-free. It does not fetch
    news itself; MCP/search collectors may write sanitized inputs into runtime.
    """
    coin = coin.upper()
    reasons: list[str] = []
    now = now or datetime.now(timezone.utc)
    for item in items[:100]:
        if not isinstance(item, dict):
            continue
        symbols = [str(x).upper() for x in item.get("symbols", [])] if isinstance(item.get("symbols"), list) else []
        item_ts = _parse_item_time(item.get("published_at") or item.get("timestamp") or item.get("pubDate"))
        stale_hours = ((now - item_ts).total_seconds() / 3600) if item_ts is not None else None
        if stale_hours is not None and stale_hours > 72:
            continue
        text = " ".join(str(item.get(k) or "") for k in ("title", "summary", "description", "category")).lower()
        coin_specific = coin in symbols or coin.lower() in text
        market_wide = "MARKET" in symbols or "market" in symbols or "crypto" in str(item.get("category", "")).lower()
        applies = coin_specific or market_wide
        if not applies:
            continue
        if any(word in text for word in HIGH_RISK_KEYWORDS):
            if coin_specific:
                reasons.append("red_coin_or_market_incident")
            elif any(word in text for word in SYSTEMIC_RISK_KEYWORDS):
                reasons.append("macro_event_risk")
        elif any(word in text for word in MACRO_KEYWORDS):
            reasons.append("macro_event_risk")
    risk_level, score = _score_from_reasons(list(dict.fromkeys(reasons)))
    return EventRiskContext(
        coin=coin,
        status="loaded_research_only" if items else "not_loaded",
        risk_level=risk_level if items else "unknown",
        score=score if items else "70.00",
        reasons=tuple(dict.fromkeys(reasons)),
    )


def load_event_risk_context(path: str | Path = "runtime/research/news_event_risk_latest.json", *, coins: tuple[str, ...]) -> tuple[dict[str, dict[str, Any]], dict[str, Any]]:
    path = Path(path)
    if not path.exists():
        rows = {coin.upper(): EventRiskContext(coin.upper()).to_dict() for coin in coins}
        return rows, {"source": "news_search_research_context", "status": "not_loaded", "research_only": True, "live_order_allowed": False, "mainnet_signed_action": False}
    try:
        raw = json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        rows = {coin.upper(): EventRiskContext(coin.upper(), status="unavailable", risk_level="unknown", reasons=("event_risk_file_unreadable",)).to_dict() for coin in coins}
        return rows, {"source": "news_search_research_context", "status": "unavailable", "error": "unreadable_json", "research_only": True, "live_order_allowed": False, "mainnet_signed_action": False}
    raw_items = raw.get("items") if isinstance(raw, dict) else []
    items: list[dict[str, Any]] = [item for item in raw_items if isinstance(item, dict)] if isinstance(raw_items, list) else []
    rows = {coin.upper(): event_risk_from_news_items(coin.upper(), items).to_dict() for coin in coins}
    return rows, {"source": "news_search_research_context", "status": "loaded_research_only", "items_seen": len(items), "research_only": True, "live_order_allowed": False, "mainnet_signed_action": False}
