"""
Central bank publication tracker — Fed + ECB RSS feeds.
No API key required.
"""
from __future__ import annotations

import logging
import re
import xml.etree.ElementTree as ET
from datetime import datetime, timedelta, timezone
from email.utils import parsedate_to_datetime
from typing import Any, Dict, List, Optional, Tuple

import requests

logger = logging.getLogger(__name__)

FED_FEEDS: List[Tuple[str, str]] = [
    ("https://www.federalreserve.gov/feeds/press_monetary.xml", "FOMC/Monetary Policy"),
    ("https://www.federalreserve.gov/feeds/speeches.xml", "Fed Speeches"),
]

ECB_FEEDS: List[Tuple[str, str]] = [
    ("https://www.ecb.europa.eu/rss/press.html", "ECB Press"),
    ("https://www.ecb.europa.eu/press/govcounc/monpol/html/index.en.html", "ECB Monetary Policy"),
]

MONETARY_KEYWORDS = [
    "rate", "policy", "inflation", "fomc", "minutes", "statement",
    "monetary", "decision", "outlook", "hike", "cut", "pause", "hold",
    "employment", "balance sheet", "quantitative",
]

HAWKISH_WORDS = ["hike", "hawkish", "raised", "tightening", "tighter"]
DOVISH_WORDS  = ["cut", "dovish", "easing", "lowered", "accommodative"]

_HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; OpenFin/1.0)"}
_LOOKBACK_DAYS = 14
_MAX_KEY_POINTS = 8

_ATOM_NS = "http://www.w3.org/2005/Atom"


def _parse_date(raw: Optional[str]) -> Optional[datetime]:
    if not raw:
        return None
    raw = raw.strip()
    try:
        dt = parsedate_to_datetime(raw)
        return dt.astimezone(timezone.utc)
    except Exception:
        pass
    for fmt in ("%Y-%m-%dT%H:%M:%SZ", "%Y-%m-%dT%H:%M:%S%z", "%Y-%m-%d"):
        try:
            dt = datetime.strptime(raw[: len(fmt)], fmt)
            if dt.tzinfo is None:
                dt = dt.replace(tzinfo=timezone.utc)
            return dt.astimezone(timezone.utc)
        except ValueError:
            continue
    logger.debug(f"[CB] Could not parse date: {raw!r}")
    return None


def _text(element: Optional[ET.Element]) -> str:
    if element is None:
        return ""
    return (element.text or "").strip()


def _fetch_feed(url: str, label: str) -> List[Dict[str, str]]:
    try:
        resp = requests.get(url, timeout=15, headers=_HEADERS)
        resp.raise_for_status()
    except Exception as e:
        logger.warning(f"[CB] Failed to fetch feed {label} ({url}): {e}")
        return []

    try:
        root = ET.fromstring(resp.text)
    except ET.ParseError as e:
        logger.warning(f"[CB] XML parse error for {label}: {e}")
        return []

    items: List[Dict[str, str]] = []
    tag = root.tag

    if "Atom" in tag or tag.endswith("}feed"):
        for entry in root.findall(f"{{{_ATOM_NS}}}entry"):
            title_el   = entry.find(f"{{{_ATOM_NS}}}title")
            link_el    = entry.find(f"{{{_ATOM_NS}}}link")
            updated_el = entry.find(f"{{{_ATOM_NS}}}updated")
            summary_el = entry.find(f"{{{_ATOM_NS}}}summary")
            content_el = entry.find(f"{{{_ATOM_NS}}}content")

            link_href = ""
            if link_el is not None:
                link_href = link_el.get("href", "") or _text(link_el)

            description = _text(summary_el) or _text(content_el)

            items.append({
                "title":        _text(title_el),
                "link":         link_href,
                "pub_date_raw": _text(updated_el),
                "description":  description[:500],
                "label":        label,
            })
    else:
        channel = root.find("channel") or root
        for item in channel.findall("item"):
            items.append({
                "title":        _text(item.find("title")),
                "link":         _text(item.find("link")),
                "pub_date_raw": _text(item.find("pubDate")),
                "description":  _text(item.find("description"))[:500],
                "label":        label,
            })

    logger.info(f"[CB] Fetched {len(items)} items from {label}")
    return items


def _is_relevant(title: str, description: str) -> bool:
    text = (title + " " + description).lower()
    return any(kw in text for kw in MONETARY_KEYWORDS)


def _within_lookback(pub_date_raw: str, days: int = _LOOKBACK_DAYS) -> bool:
    dt = _parse_date(pub_date_raw)
    if dt is None:
        return True
    cutoff = datetime.now(timezone.utc) - timedelta(days=days)
    return dt >= cutoff


_RATE_RE     = re.compile(r"(\d+\.?\d*)\s*%")
_ACTION_RE   = re.compile(r"\b(raised|lowered|held\s+steady|unanimous|dissent)\b", re.IGNORECASE)
_TARGET_RE   = re.compile(r"\b(2%\s+inflation\s+target|maximum\s+employment)\b", re.IGNORECASE)
_GUIDANCE_RE = re.compile(r"\b(gradual|data[-\s]dependent|patient|vigilant)\b", re.IGNORECASE)


def _extract_key_points(items: List[Dict[str, str]]) -> List[str]:
    points: List[str] = []
    for item in items:
        label_lower = item["label"].lower()
        prefix = "[FED]" if ("fed" in label_lower or "fomc" in label_lower) else "[ECB]"
        title    = item["title"]
        combined = f"{title} {item['description']}"

        headline = f"{prefix} {title[:100]}"

        details: List[str] = []
        rate_matches = _RATE_RE.findall(combined)
        if rate_matches:
            details.append(f"Rate ref: {', '.join(set(rate_matches[:3]))}%")
        action = _ACTION_RE.search(combined)
        if action:
            details.append(action.group(0).lower())
        target = _TARGET_RE.search(combined)
        if target:
            details.append(target.group(0))
        guidance = _GUIDANCE_RE.search(combined)
        if guidance:
            details.append(guidance.group(0).lower())

        if details:
            headline += f" [{'; '.join(details)}]"

        points.append(headline)
        if len(points) >= _MAX_KEY_POINTS:
            break

    return points


def _derive_signals(items: List[Dict[str, str]]) -> Tuple[str, str]:
    combined = " ".join(i["title"] for i in items).lower()

    hawkish_hit = any(w in combined for w in HAWKISH_WORDS)
    dovish_hit  = any(w in combined for w in DOVISH_WORDS)

    if hawkish_hit and not dovish_hit:
        return "bullish", "bearish"
    if dovish_hit and not hawkish_hit:
        return "bearish", "bullish"
    return "neutral", "neutral"


def fetch_central_bank_reports() -> Optional[Dict[str, Any]]:
    """
    Fetch Fed + ECB RSS feeds and return a single structured report dict
    compatible with the institutional_reports table.
    Returns None if no relevant items are found within the lookback window.
    """
    all_items: List[Dict[str, str]] = []

    for url, label in FED_FEEDS + ECB_FEEDS:
        raw_items = _fetch_feed(url, label)
        for item in raw_items:
            if _within_lookback(item["pub_date_raw"]) and _is_relevant(
                item["title"], item["description"]
            ):
                all_items.append(item)

    if not all_items:
        logger.warning("[CB] No relevant central bank items found in last 14 days")
        return None

    seen: set = set()
    unique_items: List[Dict[str, str]] = []
    for item in all_items:
        key = item["title"].strip().lower()[:80]
        if key not in seen:
            seen.add(key)
            unique_items.append(item)

    report_date = datetime.utcnow().strftime("%Y-%m-%d")
    key_points   = _extract_key_points(unique_items)
    signal_forex, signal_indices = _derive_signals(unique_items)

    implications: List[str] = []
    combined_titles = " ".join(i["title"] for i in unique_items).lower()

    if signal_forex == "bullish":
        implications.append(
            "Hawkish Fed/ECB rhetoric — supportive of USD strength, headwind for risk assets"
        )
    elif signal_forex == "bearish":
        implications.append(
            "Dovish pivot signals — USD pressure, potential tailwind for equities and EM"
        )

    if "balance sheet" in combined_titles or "quantitative" in combined_titles:
        implications.append(
            "Balance sheet / QT language present — watch long-end rates and credit spreads"
        )
    if "inflation" in combined_titles:
        implications.append(
            "Inflation remains a focal point — monitor breakevens and real yields"
        )
    if "minutes" in combined_titles:
        implications.append(
            "FOMC/ECB minutes release — expect intra-day vol spike on language parsing"
        )

    if not implications:
        implications = ["Central bank communication in focus — monitor for forward guidance shifts"]

    fed_count = sum(
        1 for i in unique_items
        if "fed" in i["label"].lower() or "fomc" in i["label"].lower()
    )
    ecb_count = len(unique_items) - fed_count
    source_parts: List[str] = []
    if fed_count:
        source_parts.append(f"Fed ({fed_count})")
    if ecb_count:
        source_parts.append(f"ECB ({ecb_count})")
    source_label = " + ".join(source_parts) + " via RSS" if source_parts else "Fed/ECB RSS"

    importance = 3 if any(
        w in combined_titles for w in ["hike", "cut", "minutes", "decision", "statement"]
    ) else 2

    ai_summary = (
        f"Central Bank Watch ({report_date}). "
        f"{len(unique_items)} relevant items: {source_label}. "
        f"USD signal: {signal_forex.upper()}, Indices: {signal_indices.upper()}. "
        + implications[0]
    )

    return {
        "report_type": "central_bank",
        "report_date": report_date,
        "title": f"Central Bank Monitor — Fed + ECB — {report_date}",
        "source": source_label,
        "importance": importance,
        "category": "macro",
        "raw_data": {
            "items": [
                {
                    "title":    i["title"],
                    "link":     i["link"],
                    "pub_date": i["pub_date_raw"],
                    "label":    i["label"],
                }
                for i in unique_items
            ],
            "fed_count": fed_count,
            "ecb_count": ecb_count,
        },
        "key_points": key_points,
        "trading_implications": " | ".join(implications),
        "signal_energy": "neutral",
        "signal_metals": "neutral",
        "signal_indices": signal_indices,
        "signal_forex": signal_forex,
        "ai_summary": ai_summary,
    }
