"""News collector for crypto sentiment analysis.

Collects headlines from multiple sources:
- RSS feeds (CoinDesk, CoinTelegraph, Decrypt, etc.)
- CryptoPanic API (optional, requires API key)

Headlines are cached and refreshed periodically to avoid rate limits.
"""

import asyncio
import re
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
from enum import Enum

from src.core.logger import get_logger

logger = get_logger(__name__)


class NewsSource(str, Enum):
    """Supported news sources."""
    COINDESK = "coindesk"
    COINTELEGRAPH = "cointelegraph"
    DECRYPT = "decrypt"
    BITCOIN_MAGAZINE = "bitcoin_magazine"
    THE_BLOCK = "the_block"
    MESSARI = "messari"
    CRYPTOPANIC = "cryptopanic"


@dataclass
class NewsItem:
    """A single news item."""
    title: str
    source: NewsSource
    published: datetime
    url: Optional[str] = None
    summary: Optional[str] = None
    symbols: List[str] = field(default_factory=list)


# RSS feed URLs for major crypto news sources
RSS_FEEDS: Dict[NewsSource, str] = {
    NewsSource.COINDESK: "https://www.coindesk.com/arc/outboundfeeds/rss/",
    NewsSource.COINTELEGRAPH: "https://cointelegraph.com/rss",
    NewsSource.DECRYPT: "https://decrypt.co/feed",
    NewsSource.BITCOIN_MAGAZINE: "https://bitcoinmagazine.com/.rss/full/",
    NewsSource.THE_BLOCK: "https://www.theblock.co/rss.xml",
    NewsSource.MESSARI: "https://messari.io/rss",
}

# Common crypto symbols to detect in headlines
CRYPTO_SYMBOLS = {
    "BTC": ["bitcoin", "btc"],
    "ETH": ["ethereum", "eth", "ether"],
    "SOL": ["solana", "sol"],
    "XRP": ["ripple", "xrp"],
    "ADA": ["cardano", "ada"],
    "DOGE": ["dogecoin", "doge"],
    "DOT": ["polkadot", "dot"],
    "AVAX": ["avalanche", "avax"],
    "MATIC": ["polygon", "matic"],
    "LINK": ["chainlink", "link"],
    "UNI": ["uniswap", "uni"],
    "ATOM": ["cosmos", "atom"],
    "LTC": ["litecoin", "ltc"],
}


class NewsCollector:
    """Collects crypto news headlines for sentiment analysis.
    
    Features:
    - Aggregates news from multiple RSS feeds
    - Caches headlines to reduce network requests
    - Filters by crypto symbol
    - Optional CryptoPanic integration for more sources
    
    Usage:
        collector = NewsCollector()
        headlines = await collector.fetch_headlines()
        # Or filter by symbol
        btc_headlines = await collector.fetch_headlines(symbols=["BTC"])
    """
    
    # Minimum time between fetches (in seconds)
    MIN_FETCH_INTERVAL = 300  # 5 minutes
    
    def __init__(
        self,
        sources: Optional[List[NewsSource]] = None,
        cryptopanic_api_key: Optional[str] = None,
        cache_duration: int = 600,  # 10 minutes
        max_headlines: int = 50
    ):
        """Initialize NewsCollector.
        
        Args:
            sources: List of news sources to use. Defaults to all RSS feeds.
            cryptopanic_api_key: Optional API key for CryptoPanic.
            cache_duration: How long to cache headlines (seconds).
            max_headlines: Maximum headlines to keep in cache.
        """
        self.sources = sources or list(RSS_FEEDS.keys())
        self.cryptopanic_api_key = cryptopanic_api_key
        self.cache_duration = cache_duration
        self.max_headlines = max_headlines
        
        self._cache: List[NewsItem] = []
        self._last_fetch: Optional[datetime] = None
        self._fetching = False
        
        self.logger = get_logger(__name__)
        
        # Check if feedparser is available
        try:
            import feedparser
            self._feedparser = feedparser
            self._feedparser_available = True
        except ImportError:
            self._feedparser_available = False
            self.logger.warning(
                "feedparser not installed. Install with: pip install feedparser"
            )
    
    def _extract_symbols(self, text: str) -> List[str]:
        """Extract crypto symbols mentioned in text.
        
        Args:
            text: Text to search (headline, summary)
        
        Returns:
            List of detected symbol codes (e.g., ["BTC", "ETH"])
        """
        text_lower = text.lower()
        detected = []
        
        for symbol, keywords in CRYPTO_SYMBOLS.items():
            for keyword in keywords:
                if keyword in text_lower:
                    detected.append(symbol)
                    break
        
        return detected
    
    async def _fetch_rss_feed(self, source: NewsSource) -> List[NewsItem]:
        """Fetch and parse a single RSS feed.
        
        Args:
            source: News source to fetch
        
        Returns:
            List of NewsItems from the feed
        """
        if not self._feedparser_available:
            return []
        
        url = RSS_FEEDS.get(source)
        if not url:
            return []
        
        try:
            # Run feedparser in thread pool to avoid blocking
            loop = asyncio.get_event_loop()
            feed = await loop.run_in_executor(
                None, 
                self._feedparser.parse, 
                url
            )
            
            items = []
            for entry in feed.entries[:20]:  # Limit entries per feed
                # Parse publication date
                published = datetime.now()
                if hasattr(entry, 'published_parsed') and entry.published_parsed:
                    try:
                        published = datetime(*entry.published_parsed[:6])
                    except (TypeError, ValueError):
                        pass
                
                title = entry.get('title', '')
                summary = entry.get('summary', '')
                
                # Clean HTML from summary
                summary = re.sub(r'<[^>]+>', '', summary)[:200]
                
                item = NewsItem(
                    title=title,
                    source=source,
                    published=published,
                    url=entry.get('link'),
                    summary=summary,
                    symbols=self._extract_symbols(f"{title} {summary}")
                )
                items.append(item)
            
            self.logger.debug(f"Fetched {len(items)} items from {source.value}")
            return items
            
        except Exception as e:
            self.logger.warning(f"Failed to fetch {source.value} RSS: {e}")
            return []
    
    async def _fetch_cryptopanic(self, symbols: Optional[List[str]] = None) -> List[NewsItem]:
        """Fetch news from CryptoPanic API.
        
        Args:
            symbols: Optional list of symbols to filter
        
        Returns:
            List of NewsItems from CryptoPanic
        """
        if not self.cryptopanic_api_key:
            return []
        
        try:
            import aiohttp
            
            url = "https://cryptopanic.com/api/v1/posts/"
            params = {
                "auth_token": self.cryptopanic_api_key,
                "public": "true",
            }
            
            if symbols:
                params["currencies"] = ",".join(symbols)
            
            async with aiohttp.ClientSession() as session:
                async with session.get(url, params=params, timeout=10) as response:
                    if response.status != 200:
                        self.logger.warning(f"CryptoPanic API error: {response.status}")
                        return []
                    
                    data = await response.json()
            
            items = []
            for post in data.get("results", [])[:20]:
                published = datetime.now()
                if post.get("published_at"):
                    try:
                        published = datetime.fromisoformat(
                            post["published_at"].replace("Z", "+00:00")
                        )
                    except (ValueError, TypeError):
                        pass
                
                item = NewsItem(
                    title=post.get("title", ""),
                    source=NewsSource.CRYPTOPANIC,
                    published=published,
                    url=post.get("url"),
                    symbols=[c["code"] for c in post.get("currencies", [])]
                )
                items.append(item)
            
            self.logger.debug(f"Fetched {len(items)} items from CryptoPanic")
            return items
            
        except ImportError:
            self.logger.warning("aiohttp not installed for CryptoPanic API")
            return []
        except Exception as e:
            self.logger.warning(f"Failed to fetch CryptoPanic: {e}")
            return []
    
    async def fetch_news(
        self,
        symbols: Optional[List[str]] = None,
        force_refresh: bool = False
    ) -> List[NewsItem]:
        """Fetch news items from all sources.
        
        Args:
            symbols: Optional list of symbols to filter (e.g., ["BTC", "ETH"])
            force_refresh: Force a refresh even if cache is valid
        
        Returns:
            List of NewsItems sorted by publication date (newest first)
        """
        now = datetime.now()
        
        # Check cache validity
        cache_valid = (
            self._last_fetch is not None and
            (now - self._last_fetch).total_seconds() < self.cache_duration and
            len(self._cache) > 0
        )
        
        if cache_valid and not force_refresh:
            self.logger.debug("Using cached news items")
            items = self._cache
        else:
            # Prevent concurrent fetches
            if self._fetching:
                self.logger.debug("Fetch already in progress, using cache")
                items = self._cache
            else:
                self._fetching = True
                try:
                    items = await self._fetch_all_sources()
                    self._cache = items
                    self._last_fetch = now
                finally:
                    self._fetching = False
        
        # Filter by symbols if specified
        if symbols:
            symbols_upper = [s.upper() for s in symbols]
            items = [
                item for item in items
                if any(s in symbols_upper for s in item.symbols) or not item.symbols
            ]
        
        return items
    
    async def _fetch_all_sources(self) -> List[NewsItem]:
        """Fetch from all configured sources concurrently."""
        tasks = []
        
        # RSS feeds
        for source in self.sources:
            if source in RSS_FEEDS:
                tasks.append(self._fetch_rss_feed(source))
        
        # CryptoPanic
        if self.cryptopanic_api_key:
            tasks.append(self._fetch_cryptopanic())
        
        if not tasks:
            self.logger.warning("No news sources configured")
            return []
        
        # Fetch all concurrently
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # Combine results
        all_items = []
        for result in results:
            if isinstance(result, list):
                all_items.extend(result)
            elif isinstance(result, Exception):
                self.logger.warning(f"Source fetch failed: {result}")
        
        # Sort by publication date (newest first)
        all_items.sort(key=lambda x: x.published, reverse=True)
        
        # Limit total items
        all_items = all_items[:self.max_headlines]
        
        self.logger.info(f"Fetched {len(all_items)} total news items")
        return all_items
    
    async def fetch_headlines(
        self,
        symbols: Optional[List[str]] = None,
        max_age_hours: int = 24,
        force_refresh: bool = False
    ) -> List[str]:
        """Fetch headlines as strings for sentiment analysis.
        
        This is the main method to use with SentimentScanner.
        
        Args:
            symbols: Optional list of symbols to filter
            max_age_hours: Only include headlines from the last N hours
            force_refresh: Force a refresh even if cache is valid
        
        Returns:
            List of headline strings
        """
        items = await self.fetch_news(symbols=symbols, force_refresh=force_refresh)
        
        # Filter by age
        cutoff = datetime.now() - timedelta(hours=max_age_hours)
        recent_items = [item for item in items if item.published > cutoff]
        
        # Extract just the titles
        headlines = [item.title for item in recent_items if item.title]
        
        self.logger.debug(f"Returning {len(headlines)} headlines for analysis")
        return headlines
    
    def get_cached_headlines(self, symbols: Optional[List[str]] = None) -> List[str]:
        """Get cached headlines synchronously (no fetch).
        
        Args:
            symbols: Optional list of symbols to filter
        
        Returns:
            List of cached headline strings
        """
        items = self._cache
        
        if symbols:
            symbols_upper = [s.upper() for s in symbols]
            items = [
                item for item in items
                if any(s in symbols_upper for s in item.symbols) or not item.symbols
            ]
        
        return [item.title for item in items if item.title]
    
    def get_status(self) -> Dict[str, Any]:
        """Get collector status information.
        
        Returns:
            Dictionary with status info
        """
        return {
            "sources": [s.value for s in self.sources],
            "cryptopanic_enabled": self.cryptopanic_api_key is not None,
            "feedparser_available": self._feedparser_available,
            "cached_items": len(self._cache),
            "last_fetch": self._last_fetch.isoformat() if self._last_fetch else None,
            "cache_age_seconds": (
                (datetime.now() - self._last_fetch).total_seconds()
                if self._last_fetch else None
            ),
        }
    
    def clear_cache(self):
        """Clear the headline cache."""
        self._cache = []
        self._last_fetch = None
        self.logger.info("News cache cleared")


# Global instance
_news_collector: Optional[NewsCollector] = None


def get_news_collector(**kwargs) -> NewsCollector:
    """Get or create the global NewsCollector instance.
    
    Args:
        **kwargs: Arguments passed to NewsCollector constructor
    
    Returns:
        NewsCollector instance
    """
    global _news_collector
    if _news_collector is None:
        _news_collector = NewsCollector(**kwargs)
        logger.info("Created global NewsCollector instance")
    return _news_collector
