"""
RMI News Network - World's Best Crypto News Aggregator

Architecture:
  RSS/API Sources → MCP News Pipeline → Sentiment Analysis → Categorization → Feed

Sources: 25+ trusted crypto news outlets, newsletters, blogs, and mirrors.
"""

import asyncio
import hashlib
import logging
import re
import time
from dataclasses import dataclass, field
from datetime import UTC, datetime

import feedparser
import httpx

logger = logging.getLogger("news_network")

# ═══════════════════════════════════════════════════════════════════════════
# 25+ NEWS SOURCES - Mainstream, independent, newsletters, blogs
# ═══════════════════════════════════════════════════════════════════════════

NEWS_SOURCES = [
    {
        "name": "CoinDesk",
        "url": "https://www.coindesk.com/arc/outboundfeeds/rss/?outputType=xml",
        "tier": 1,
    },
    {"name": "CoinTelegraph", "url": "https://cointelegraph.com/rss", "tier": 1},
    {"name": "The Block", "url": "https://www.theblock.co/rss/", "tier": 1},
    {"name": "Decrypt", "url": "https://decrypt.co/feed", "tier": 1},
    {"name": "Blockworks", "url": "https://blockworks.co/feed", "tier": 1},
    {"name": "DL News", "url": "https://www.dlnews.com/feed", "tier": 1},
    {
        "name": "Bloomberg Crypto",
        "url": "https://feeds.bloomberg.com/markets/crypto.rss",
        "tier": 1,
    },
    {"name": "Forbes Crypto", "url": "https://www.forbes.com/digital-assets/feed/", "tier": 1},
    {
        "name": "CoinDesk Markets",
        "url": "https://www.coindesk.com/arc/outboundfeeds/markets/?outputType=xml",
        "tier": 1,
    },
    {"name": "CoinDesk Policy", "url": "https://www.coindesk.com/policy/feed/", "tier": 1},
    {
        "name": "Cointelegraph Bitcoin",
        "url": "https://cointelegraph.com/rss/tag/bitcoin",
        "tier": 1,
    },
    {
        "name": "Cointelegraph Ethereum",
        "url": "https://cointelegraph.com/rss/tag/ethereum",
        "tier": 1,
    },
    {"name": "Cointelegraph DeFi", "url": "https://cointelegraph.com/rss/tag/defi", "tier": 1},
    {
        "name": "Cointelegraph Regulation",
        "url": "https://cointelegraph.com/rss/tag/regulation",
        "tier": 1,
    },
    {"name": "The Block Policy", "url": "https://www.theblock.co/policy/feed", "tier": 1},
    {"name": "Bankless", "url": "https://www.bankless.com/feed", "tier": 2},
    {"name": "The Defiant", "url": "https://thedefiant.io/feed", "tier": 2},
    {"name": "CryptoSlate", "url": "https://cryptoslate.com/feed/", "tier": 2},
    {"name": "BeInCrypto", "url": "https://beincrypto.com/feed/", "tier": 2},
    {"name": "Crypto Briefing", "url": "https://cryptobriefing.com/feed/", "tier": 2},
    {"name": "AMB Crypto", "url": "https://ambcrypto.com/feed/", "tier": 2},
    {"name": "Bitcoin Magazine", "url": "https://bitcoinmagazine.com/feed", "tier": 2},
    {"name": "NewsBTC", "url": "https://www.newsbtc.com/feed/", "tier": 2},
    {"name": "CryptoPotato", "url": "https://cryptopotato.com/feed/", "tier": 2},
    {"name": "CryptoNews", "url": "https://cryptonews.com/news/feed/", "tier": 2},
    {"name": "U.Today", "url": "https://u.today/rss", "tier": 2},
    {"name": "CoinGape", "url": "https://coingape.com/feed/", "tier": 2},
    {"name": "CoinJournal", "url": "https://coinjournal.net/feed/", "tier": 2},
    {"name": "CoinBureau", "url": "https://www.coinbureau.com/feed/", "tier": 2},
    {"name": "Bitcoinist", "url": "https://bitcoinist.com/feed/", "tier": 2},
    {"name": "DailyCoin", "url": "https://dailycoin.com/feed/", "tier": 2},
    {"name": "Crypto Daily", "url": "https://cryptodaily.co.uk/feed/", "tier": 2},
    {"name": "CoinCodex", "url": "https://coincodex.com/feed/", "tier": 2},
    {"name": "Coinpedia", "url": "https://coinpedia.org/feed/", "tier": 2},
    {"name": "ZyCrypto", "url": "https://zycrypto.com/feed/", "tier": 2},
    {"name": "CoinSpeaker", "url": "https://www.coinspeaker.com/feed/", "tier": 2},
    {"name": "Altcoin Buzz", "url": "https://www.altcoinbuzz.io/feed/", "tier": 2},
    {"name": "CryptoGlobe", "url": "https://www.cryptoglobe.com/feed/", "tier": 2},
    {"name": "Crypto Economy", "url": "https://crypto-economy.com/feed/", "tier": 2},
    {"name": "Crypto News Flash", "url": "https://www.crypto-news-flash.com/feed/", "tier": 2},
    {"name": "TrustNodes", "url": "https://www.trustnodes.com/feed", "tier": 3},
    {"name": "Watcher Guru", "url": "https://watcher.guru/news/feed", "tier": 3},
    {"name": "Bitcoin News", "url": "https://news.bitcoin.com/feed/", "tier": 3},
    {"name": "Ethereum World News", "url": "https://en.ethereumworldnews.com/feed/", "tier": 3},
    {"name": "The Daily Hodl", "url": "https://dailyhodl.com/feed/", "tier": 3},
    {"name": "NullTX", "url": "https://nulltx.com/feed/", "tier": 3},
    {"name": "Crypto Briefing Pro", "url": "https://cryptobriefing.com/feed/", "tier": 3},
    {"name": "Coin Rivet", "url": "https://coinrivet.com/feed/", "tier": 3},
    {"name": "Crypto Mode", "url": "https://cryptomode.com/feed/", "tier": 3},
    {"name": "CoinRepublic", "url": "https://www.coinrepublic.com/feed/", "tier": 3},
    {"name": "Blockonomi", "url": "https://blockonomi.com/feed/", "tier": 3},
    {"name": "UseTheBitcoin", "url": "https://usethebitcoin.com/feed/", "tier": 3},
    {"name": "CryptoAdventure", "url": "https://cryptoadventure.com/feed/", "tier": 3},
    {"name": "The Coin Republic", "url": "https://www.thecoinrepublic.com/feed/", "tier": 3},
    {"name": "Bitcoin Exchange Guide", "url": "https://bitcoinexchangeguide.com/feed/", "tier": 3},
    {"name": "CryptoVibes", "url": "https://cryptovibes.com/feed/", "tier": 3},
    {"name": "CoinCu", "url": "https://coincu.com/feed/", "tier": 3},
    {"name": "Todayq", "url": "https://todayq.com/feed/", "tier": 3},
    {"name": "CoinChapter", "url": "https://coinchapter.com/feed/", "tier": 3},
    {"name": "Cryptopolitan", "url": "https://www.cryptopolitan.com/feed/", "tier": 3},
    {"name": "Rekt News", "url": "https://rekt.news/rss/", "tier": 4},
    {"name": "SlowMist", "url": "https://slowmist.medium.com/feed", "tier": 4},
    {"name": "PeckShield", "url": "https://peckshield.medium.com/feed", "tier": 4},
    {"name": "Chainalysis", "url": "https://blog.chainalysis.com/feed/", "tier": 4},
    {"name": "CertiK", "url": "https://www.certik.com/blog/feed", "tier": 4},
    {"name": "OpenZeppelin", "url": "https://blog.openzeppelin.com/feed/", "tier": 4},
    {"name": "Trail of Bits", "url": "https://blog.trailofbits.com/feed/", "tier": 4},
    {"name": "Immunefi", "url": "https://immunefi.medium.com/feed", "tier": 4},
    {"name": "BlockSec", "url": "https://blocksecteam.medium.com/feed", "tier": 4},
    {"name": "Halborn", "url": "https://www.halborn.com/blog/feed", "tier": 4},
    {"name": "Quantstamp", "url": "https://quantstamp.com/blog/feed", "tier": 4},
    {"name": "Consensys Diligence", "url": "https://consensys.io/diligence/blog/feed", "tier": 4},
    {"name": "Hexens", "url": "https://hexens.io/blog/feed", "tier": 4},
    {"name": "Zellic", "url": "https://www.zellic.io/blog/feed", "tier": 4},
    {"name": "Spearbit", "url": "https://spearbit.medium.com/feed", "tier": 4},
    {"name": "DefiLlama", "url": "https://defillama.com/rss", "tier": 5},
    {"name": "Messari", "url": "https://messari.io/feed", "tier": 5},
    {"name": "Nansen", "url": "https://www.nansen.ai/feed", "tier": 5},
    {"name": "Dune Analytics", "url": "https://dune.com/blog/rss.xml", "tier": 5},
    {"name": "Coin Metrics", "url": "https://coinmetrics.io/feed/", "tier": 5},
    {"name": "Glassnode", "url": "https://insights.glassnode.com/feed/", "tier": 5},
    {"name": "The Tie", "url": "https://www.thetie.is/blog/feed", "tier": 5},
    {"name": "Kaiko", "url": "https://blog.kaiko.com/feed", "tier": 5},
    {"name": "IntoTheBlock", "url": "https://blog.intotheblock.com/feed", "tier": 5},
    {"name": "LunarCrush", "url": "https://lunarcrush.com/blog/feed", "tier": 5},
    {"name": "Santiment", "url": "https://santiment.medium.com/feed", "tier": 5},
    {"name": "Arkham Intel", "url": "https://www.arkhamintelligence.com/blog/feed", "tier": 5},
    {"name": "Token Terminal", "url": "https://tokenterminal.com/blog/feed", "tier": 5},
    {"name": "DeFi Dad", "url": "https://defidad.medium.com/feed", "tier": 5},
    {"name": "Our Network", "url": "https://ournetwork.substack.com/feed", "tier": 5},
    {"name": "a16z Crypto", "url": "https://a16zcrypto.com/feed/", "tier": 6},
    {"name": "Paradigm", "url": "https://www.paradigm.xyz/feed", "tier": 6},
    {"name": "Pantera Capital", "url": "https://panteracapital.com/feed/", "tier": 6},
    {"name": "Multicoin Capital", "url": "https://multicoin.capital/feed/", "tier": 6},
    {"name": "Dragonfly", "url": "https://www.dragonfly.xyz/feed", "tier": 6},
    {"name": "Electric Capital", "url": "https://www.electriccapital.com/rss.xml", "tier": 6},
    {"name": "Variant Fund", "url": "https://variant.fund/feed/", "tier": 6},
    {"name": "1Confirmation", "url": "https://1confirmation.com/feed/", "tier": 6},
    {"name": "Delphi Digital", "url": "https://members.delphidigital.io/feed", "tier": 6},
    {"name": "Framework Ventures", "url": "https://framework.ventures/feed", "tier": 6},
    {"name": "Milk Road", "url": "https://www.milkroad.com/feed", "tier": 7},
    {"name": "The Pomp Letter", "url": "https://pomp.substack.com/feed", "tier": 7},
    {"name": "Bankless Weekly", "url": "https://newsletter.banklesshq.com/feed", "tier": 7},
    {"name": "Week in Ethereum", "url": "https://weekinethereum.substack.com/feed", "tier": 7},
    {"name": "Proof of Work", "url": "https://proofofwork.news/feed", "tier": 7},
    {"name": "EthHub Weekly", "url": "https://ethhub.substack.com/feed", "tier": 7},
    {"name": "CoinGecko Buzz", "url": "https://www.coingecko.com/en/rss/news", "tier": 7},
    {"name": "CoinMarketCap Blog", "url": "https://coinmarketcap.com/alexandria/feed", "tier": 7},
    {"name": "Bitcoin Optech", "url": "https://bitcoinops.org/feed.xml", "tier": 7},
    {"name": "Bitcoin Dev", "url": "https://bitcointechtalk.com/feed/", "tier": 7},
    {"name": "The Daily Gwei", "url": "https://thedailygwei.substack.com/feed", "tier": 7},
    {"name": "DeFi Weekly", "url": "https://defiweekly.substack.com/feed", "tier": 7},
    {"name": "Messari Unqualified", "url": "https://messari.substack.com/feed", "tier": 7},
    {"name": "Not Boring", "url": "https://www.notboring.co/feed", "tier": 7},
    {"name": "Nic Carter", "url": "https://niccarter.substack.com/feed", "tier": 7},
    {"name": "Lyn Alden", "url": "https://www.lynalden.com/feed/", "tier": 7},
    {"name": "Ryan Selkis", "url": "https://ryanselkis.substack.com/feed", "tier": 7},
    {"name": "Hayden Adams", "url": "https://hayden.substack.com/feed", "tier": 7},
    {"name": "Vitalik Buterin", "url": "https://vitalik.eth.limo/feed.xml", "tier": 7},
    {"name": "CoinBureau Newsletter", "url": "https://www.coinbureau.com/feed/", "tier": 7},
    {"name": "Coinspeaker Japan", "url": "https://www.coinspeaker.com/ja/feed/", "tier": 8},
    {"name": "Bitcoin.com News", "url": "https://news.bitcoin.com/feed/", "tier": 8},
    {"name": "Crypto.com News", "url": "https://crypto.com/rss/feed", "tier": 8},
    {"name": "Binance Blog", "url": "https://www.binance.com/en/feed", "tier": 8},
    {"name": "Coinbase Blog", "url": "https://blog.coinbase.com/feed", "tier": 8},
    {"name": "Kraken Blog", "url": "https://blog.kraken.com/feed", "tier": 8},
    {"name": "Gemini Blog", "url": "https://www.gemini.com/blog/feed", "tier": 8},
    {"name": "OKX Insights", "url": "https://www.okx.com/feed/insights", "tier": 8},
    {"name": "Bybit Learn", "url": "https://learn.bybit.com/feed/", "tier": 8},
    {"name": "BitMEX Research", "url": "https://blog.bitmex.com/feed/", "tier": 8},
    {"name": "Bitfinex Blog", "url": "https://blog.bitfinex.com/feed/", "tier": 8},
    {"name": "KuCoin Blog", "url": "https://www.kucoin.com/blog/feed", "tier": 8},
    {"name": "Gate.io Blog", "url": "https://www.gate.io/blog/feed", "tier": 8},
    {"name": "Bitstamp Blog", "url": "https://blog.bitstamp.net/feed/", "tier": 8},
    {"name": "Uniswap Blog", "url": "https://blog.uniswap.org/feed", "tier": 8},
]


# Extended category detection with 12 categories
CATEGORY_KEYWORDS = {
    "Bitcoin": ["bitcoin", "btc", "satoshi", "halving", "ordinals", "lightning network", "taproot"],
    "Ethereum": ["ethereum", "eth", "vitalik", "layer 2", "l2", "staking", "merge", "eip", "erc"],
    "Solana": ["solana", "sol", "phantom", "jupiter", "raydium", "pump.fun", "bonk"],
    "DeFi": ["defi", "yield", "liquidity pool", "amm", "swap", "lending", "borrowing", "tvl"],
    "Regulation": [
        "sec",
        "regulation",
        "lawsuit",
        "compliance",
        "cftc",
        "doj",
        "legal",
        "court",
        "ban",
    ],
    "Security": [
        "hack",
        "exploit",
        "rug pull",
        "scam",
        "phishing",
        "vulnerability",
        "audit",
        "stolen",
    ],
    "Markets": ["price", "market", "bull", "bear", "rally", "crash", "dump", "pump", "trading"],
    "NFTs": ["nft", "collectible", "mint", "opensea", "blur", "magic eden", "pudgy"],
    "AI": ["ai", "artificial intelligence", "machine learning", "agent", "llm", "gpt", "copilot"],
    "Memecoins": ["meme", "dogecoin", "shiba", "pepe", "bonk", "wojak", "cum"],
    "Adoption": ["adoption", "partnership", "enterprise", "institutional", "bank", "etf"],
    "Privacy": ["privacy", "zk", "zero knowledge", "mixer", "tornado", "monero", "zec"],
}


SENTIMENT_DICT = {
    "surge": 0.8,
    "soar": 0.9,
    "rally": 0.7,
    "breakout": 0.8,
    "pump": 0.6,
    "bullish": 0.9,
    "gain": 0.5,
    "profit": 0.6,
    "growth": 0.6,
    "adoption": 0.7,
    "partnership": 0.6,
    "launch": 0.5,
    "mainnet": 0.6,
    "upgrade": 0.5,
    "record": 0.7,
    "crash": -0.9,
    "dump": -0.7,
    "hack": -0.95,
    "exploit": -0.95,
    "scam": -0.9,
    "rug pull": -0.95,
    "bearish": -0.9,
    "loss": -0.6,
    "decline": -0.5,
    "lawsuit": -0.7,
    "ban": -0.8,
    "crackdown": -0.7,
    "liquidation": -0.8,
}


@dataclass
class Article:
    id: str
    title: str
    source: str
    source_tier: int
    url: str
    summary: str = ""
    image: str = ""
    published: str = ""
    categories: list[str] = field(default_factory=list)
    sentiment_score: float = 0.0
    sentiment_label: str = "neutral"
    impact_level: str = "low"
    reading_time: int = 1
    votes_up: int = 0
    votes_down: int = 0
    comments: list[dict] = field(default_factory=list)
    bookmarks: int = 0


_db: dict[str, Article] = {}


async def fetch_all(max_per_source: int = 8) -> list[Article]:
    """Fetch from all 30 sources in parallel."""
    new_articles = []

    async def fetch_source(source):
        try:
            async with httpx.AsyncClient(timeout=8) as c:
                r = await c.get(source["url"], follow_redirects=True)
                if r.status_code == 200:
                    feed = feedparser.parse(r.text)
                    for entry in feed.entries[:max_per_source]:
                        aid = hashlib.md5((entry.link or entry.title).encode()).hexdigest()[:12]
                        if aid in _db:
                            continue

                        summary = entry.get("summary", entry.get("description", ""))
                        summary = re.sub(r"<[^>]+>", "", summary)[:400]
                        title = entry.title or "Untitled"

                        article = Article(
                            id=aid,
                            title=title,
                            source=source["name"],
                            source_tier=source["tier"],
                            url=entry.link,
                            summary=summary,
                            image=entry.get("media_content", [{}])[0].get("url", ""),
                            published=entry.get("published", ""),
                        )

                        # Classify
                        text = (title + " " + summary).lower()
                        for cat, keywords in CATEGORY_KEYWORDS.items():
                            if any(kw in text for kw in keywords):
                                article.categories.append(cat)
                        if not article.categories:
                            article.categories = ["General"]

                        # Sentiment
                        score = 0.0
                        for word, weight in SENTIMENT_DICT.items():
                            if word in text:
                                score += weight
                        article.sentiment_score = round(score / max(abs(score), 1), 2) if score != 0 else 0
                        article.sentiment_label = (
                            "bullish"
                            if article.sentiment_score > 0.15
                            else "bearish"
                            if article.sentiment_score < -0.15
                            else "neutral"
                        )

                        # Reading time
                        words = len(text.split())
                        article.reading_time = max(1, round(words / 200))

                        # Impact level
                        article.impact_level = (
                            "high"
                            if abs(article.sentiment_score) > 0.5
                            or any(kw in text for kw in ["hack", "exploit", "crash", "surge", "breakout"])
                            else "medium"
                            if abs(article.sentiment_score) > 0.2
                            else "low"
                        )

                        _db[aid] = article
                        new_articles.append(article)
        except Exception:
            pass

    await asyncio.gather(*[fetch_source(s) for s in NEWS_SOURCES])
    return new_articles


def get_feed(
    category: str | None = None,
    sentiment: str | None = None,
    tier: int | None = None,
    sort: str = "latest",
    limit: int = 50,
    offset: int = 0,
    impact: str | None = None,
    source: str | None = None,
) -> dict:
    """Get the news feed with all filters."""
    articles = list(_db.values())

    if category and category != "All":
        articles = [a for a in articles if category in a.categories]
    if sentiment:
        articles = [a for a in articles if a.sentiment_label == sentiment]
    if tier:
        articles = [a for a in articles if a.source_tier == tier]
    if impact:
        articles = [a for a in articles if a.impact_level == impact]
    if source:
        articles = [a for a in articles if a.source == source]

    if sort == "popular":
        articles.sort(key=lambda a: a.votes_up - a.votes_down, reverse=True)
    elif sort == "bullish":
        articles.sort(key=lambda a: a.sentiment_score, reverse=True)
    elif sort == "bearish":
        articles.sort(key=lambda a: a.sentiment_score)
    elif sort == "impact":
        impact_order = {"high": 3, "medium": 2, "low": 1}
        articles.sort(key=lambda a: impact_order.get(a.impact_level, 0), reverse=True)
    else:
        articles.sort(key=lambda a: a.published, reverse=True)

    total = len(articles)
    page = articles[offset : offset + limit]

    return {
        "articles": [
            {
                "id": a.id,
                "title": a.title,
                "source": a.source,
                "source_tier": a.source_tier,
                "url": a.url,
                "summary": a.summary,
                "image": a.image,
                "published": a.published,
                "categories": a.categories,
                "sentiment_score": a.sentiment_score,
                "sentiment_label": a.sentiment_label,
                "impact_level": a.impact_level,
                "reading_time": a.reading_time,
                "votes_up": a.votes_up,
                "votes_down": a.votes_down,
                "comment_count": len(a.comments),
                "bookmarks": a.bookmarks,
            }
            for a in page
        ],
        "total": total,
        "all_categories": sorted({c for a in articles for c in a.categories}),
        "all_sources": sorted({a.source for a in articles}),
        "stats": {
            "total_articles": len(_db),
            "sources_indexed": len({a.source for a in _db.values()}),
            "sentiment": {
                "bullish": sum(1 for a in _db.values() if a.sentiment_label == "bullish"),
                "bearish": sum(1 for a in _db.values() if a.sentiment_label == "bearish"),
                "neutral": sum(1 for a in _db.values() if a.sentiment_label == "neutral"),
            },
            "high_impact": sum(1 for a in _db.values() if a.impact_level == "high"),
            "latest_update": datetime.now(UTC).isoformat(),
        },
    }


def vote_article(article_id: str, direction: str) -> dict:
    a = _db.get(article_id)
    if not a:
        return {"error": "Not found"}
    if direction == "up":
        a.votes_up += 1
    elif direction == "down":
        a.votes_down += 1
    return {"id": article_id, "votes_up": a.votes_up, "votes_down": a.votes_down}


def add_comment(article_id: str, user: str, text: str) -> dict:
    a = _db.get(article_id)
    if not a:
        return {"error": "Not found"}
    comment = {
        "id": hashlib.md5(f"{article_id}{time.time()}".encode()).hexdigest()[:8],
        "user": user[:50],
        "text": text[:500],
        "timestamp": datetime.now(UTC).isoformat(),
        "votes": 0,
    }
    a.comments.append(comment)
    return comment


def get_comments(article_id: str) -> list[dict]:
    a = _db.get(article_id)
    return sorted(a.comments, key=lambda c: c.get("votes", 0), reverse=True) if a else []


def bookmark(article_id: str) -> dict:
    a = _db.get(article_id)
    if a:
        a.bookmarks += 1
        return {"id": article_id, "bookmarks": a.bookmarks}
    return {"error": "Not found"}


def get_categories() -> list:
    cats = set()
    for a in _db.values():
        for c in a.categories:
            cats.add(c)
    icons = {
        "Bitcoin": "₿",
        "Ethereum": "Ξ",
        "Solana": "◎",
        "DeFi": "🏦",
        "Regulation": "⚖️",
        "Security": "🛡️",
        "Markets": "📊",
        "NFTs": "🎨",
        "AI": "🤖",
        "Memecoins": "🐸",
        "Adoption": "🚀",
        "Privacy": "🔐",
        "General": "📰",
    }
    return [{"name": c, "icon": icons.get(c, "📌")} for c in sorted(cats)]


def search_articles(query: str, limit: int = 20) -> list:
    q = query.lower()
    results = []
    for a in _db.values():
        if q in a.title.lower() or q in a.summary.lower() or q in a.source.lower():
            results.append(
                {
                    "id": a.id,
                    "title": a.title,
                    "source": a.source,
                    "url": a.url,
                    "summary": a.summary[:200],
                }
            )
    return results[:limit]
