"""Main FOMO analysis orchestrator with parallel data fetching."""

import asyncio
import logging
from typing import Any

from ..config import get_config
from ..providers.base import Provider, ProviderError, CoinNotFoundError, RateLimitError
from ..providers.coingecko import CoinGeckoProvider, CoinGeckoData, validate_symbol
from ..providers.coinmarketcap import CoinMarketCapProvider
from ..signals.base import SignalResult
from ..signals.price import PriceSignal
from ..signals.social import SocialSignal
from ..signals.onchain import OnChainSignal
from .scorer import FOMOScore, FOMOScorer

logger = logging.getLogger(__name__)


class AnalysisError(Exception):
    """Error during FOMO analysis."""

    pass


class FOMOAnalyzer:
    """Main orchestrator for FOMO analysis with parallel data fetching."""

    def __init__(self):
        """Initialize the analyzer."""
        self.config = get_config()
        self.scorer = FOMOScorer()
        self._providers: dict[str, Provider] = {}
        self._errors: list[str] = []
        self._init_providers()

    def _init_providers(self) -> None:
        """Initialize available providers based on config."""
        cache_ttl = self.config.settings.cache_ttl_minutes

        # Price providers
        coingecko_key = self.config.get_api_key("coingecko")
        self._providers["coingecko"] = CoinGeckoProvider(coingecko_key, cache_ttl=cache_ttl)

        cmc_key = self.config.get_api_key("coinmarketcap")
        if cmc_key:
            self._providers["coinmarketcap"] = CoinMarketCapProvider(cmc_key)

    async def analyze(self, symbol: str, detailed: bool = False) -> FOMOScore:
        """Perform full FOMO analysis for a symbol."""
        self._errors = []

        # Validate symbol first
        try:
            symbol = validate_symbol(symbol)
        except ValueError as e:
            raise AnalysisError(f"Invalid symbol: {e}")

        # Collect data from all available providers (in parallel where possible)
        data = await self._collect_data(symbol)

        # Check if we got any price data
        if not data.get("current_price"):
            if self._errors:
                raise AnalysisError(f"Failed to fetch data: {'; '.join(self._errors)}")
            raise AnalysisError(f"No price data available for {symbol}")

        # Run all signal analyses
        signal_results = await self._analyze_signals(symbol, data)

        # Calculate final score
        fomo_score = self.scorer.calculate(symbol, signal_results)

        # Attach any warnings
        if self._errors:
            fomo_score.warnings = self._errors

        return fomo_score

    async def _collect_data(self, symbol: str) -> dict[str, Any]:
        """Collect data from all available providers in parallel."""
        data: dict[str, Any] = {}

        # Fetch price data first (required)
        price_data = await self._collect_price_data(symbol)
        if price_data:
            data.update(price_data)

        # Fetch social and on-chain data in parallel
        social_task = asyncio.create_task(self._collect_social_data(symbol))
        onchain_task = asyncio.create_task(self._collect_onchain_data(symbol))

        social_data, onchain_data = await asyncio.gather(
            social_task, onchain_task, return_exceptions=True
        )

        # Handle results
        if isinstance(social_data, Exception):
            self._errors.append(f"Social data: {social_data}")
            social_data = None
        if isinstance(onchain_data, Exception):
            self._errors.append(f"On-chain data: {onchain_data}")
            onchain_data = None

        if social_data:
            data["social"] = social_data
        if onchain_data:
            data["onchain"] = onchain_data

        return data

    async def _collect_price_data(self, symbol: str) -> dict[str, Any] | None:
        """Collect price data with fallback to secondary provider."""
        # Try CoinGecko first
        if "coingecko" in self._providers:
            try:
                provider = self._providers["coingecko"]
                price_data = await provider.fetch_data(symbol)
                if isinstance(price_data, CoinGeckoData) and price_data.current_price:
                    return price_data.model_dump()
            except CoinNotFoundError as e:
                self._errors.append(str(e))
                # Don't fallback for not found - the coin doesn't exist
                return None
            except RateLimitError as e:
                self._errors.append(f"CoinGecko rate limited: {e}")
                # Try fallback
            except ProviderError as e:
                self._errors.append(f"CoinGecko error: {e}")
                # Try fallback

        # Try CoinMarketCap as fallback
        if "coinmarketcap" in self._providers:
            try:
                cmc_data = await self._providers["coinmarketcap"].fetch_data(symbol)
                if cmc_data.current_price:
                    return cmc_data.model_dump()
            except ProviderError as e:
                self._errors.append(f"CoinMarketCap error: {e}")

        return None

    async def _collect_social_data(self, symbol: str) -> dict[str, Any] | None:
        """Collect social sentiment data from multiple providers in parallel."""
        tasks = []
        task_names = []

        # Twitter data
        twitter_key = self.config.get_api_key("twitter")
        if twitter_key:
            tasks.append(self._fetch_twitter_data(symbol, twitter_key))
            task_names.append("twitter")

        # Reddit data
        reddit_id = self.config.get_api_key("reddit_client_id")
        reddit_secret = self.config.get_api_key("reddit_client_secret")
        if reddit_id and reddit_secret:
            tasks.append(self._fetch_reddit_data(symbol, reddit_id, reddit_secret))
            task_names.append("reddit")

        # LunarCrush data
        lunarcrush_key = self.config.get_api_key("lunarcrush")
        if lunarcrush_key:
            tasks.append(self._fetch_lunarcrush_data(symbol, lunarcrush_key))
            task_names.append("lunarcrush")

        if not tasks:
            return None

        # Run all social fetches in parallel
        results = await asyncio.gather(*tasks, return_exceptions=True)

        social_data: dict[str, Any] = {}
        for name, result in zip(task_names, results):
            if isinstance(result, Exception):
                logger.debug(f"Failed to fetch {name} data: {result}")
                continue
            if result:
                social_data[name] = result

        return social_data if social_data else None

    async def _fetch_twitter_data(self, symbol: str, api_key: str) -> dict[str, Any] | None:
        """Fetch Twitter data."""
        from ..providers.twitter import TwitterProvider
        try:
            async with TwitterProvider(api_key) as provider:
                data = await provider.fetch_data(symbol)
                return data.model_dump()
        except ProviderError:
            return None

    async def _fetch_reddit_data(self, symbol: str, client_id: str, client_secret: str) -> dict[str, Any] | None:
        """Fetch Reddit data."""
        from ..providers.reddit import RedditProvider
        try:
            async with RedditProvider(client_id, client_secret) as provider:
                data = await provider.fetch_data(symbol)
                return data.model_dump()
        except ProviderError:
            return None

    async def _fetch_lunarcrush_data(self, symbol: str, api_key: str) -> dict[str, Any] | None:
        """Fetch LunarCrush data."""
        from ..providers.lunarcrush import LunarCrushProvider
        try:
            async with LunarCrushProvider(api_key) as provider:
                data = await provider.fetch_data(symbol)
                return data.model_dump()
        except ProviderError:
            return None

    async def _collect_onchain_data(self, symbol: str) -> dict[str, Any] | None:
        """Collect on-chain metrics data from providers in parallel."""
        tasks = []
        task_names = []

        # Glassnode data
        glassnode_key = self.config.get_api_key("glassnode")
        if glassnode_key:
            tasks.append(self._fetch_glassnode_data(symbol, glassnode_key))
            task_names.append("glassnode")

        # Etherscan data (for ETH and ERC-20)
        etherscan_key = self.config.get_api_key("etherscan")
        if etherscan_key and symbol.upper() in ["ETH", "ETHEREUM"]:
            tasks.append(self._fetch_etherscan_data(symbol, etherscan_key))
            task_names.append("etherscan")

        if not tasks:
            return None

        # Run all on-chain fetches in parallel
        results = await asyncio.gather(*tasks, return_exceptions=True)

        onchain_data: dict[str, Any] = {}
        for name, result in zip(task_names, results):
            if isinstance(result, Exception):
                logger.debug(f"Failed to fetch {name} data: {result}")
                continue
            if result:
                onchain_data[name] = result

        return onchain_data if onchain_data else None

    async def _fetch_glassnode_data(self, symbol: str, api_key: str) -> dict[str, Any] | None:
        """Fetch Glassnode data."""
        from ..providers.glassnode import GlassnodeProvider
        try:
            async with GlassnodeProvider(api_key) as provider:
                data = await provider.fetch_data(symbol)
                return data.model_dump()
        except ProviderError:
            return None

    async def _fetch_etherscan_data(self, symbol: str, api_key: str) -> dict[str, Any] | None:
        """Fetch Etherscan data."""
        from ..providers.etherscan import EtherscanProvider
        try:
            async with EtherscanProvider(api_key) as provider:
                data = await provider.fetch_data(symbol)
                return data.model_dump()
        except ProviderError:
            return None

    async def _analyze_signals(
        self, symbol: str, data: dict[str, Any]
    ) -> list[SignalResult]:
        """Run all signal analyses in parallel."""
        # Create signal analyzers
        price_signal = PriceSignal()
        social_signal = SocialSignal()
        onchain_signal = OnChainSignal()

        # Run analyses in parallel
        results = await asyncio.gather(
            price_signal.analyze(symbol, data),
            social_signal.analyze(symbol, data.get("social", {})),
            onchain_signal.analyze(symbol, data.get("onchain", {})),
        )

        return list(results)

    async def get_quick_score(self, symbol: str) -> float:
        """Get just the FOMO score without full analysis."""
        fomo_score = await self.analyze(symbol)
        return fomo_score.score

    async def validate_symbol(self, symbol: str) -> bool:
        """Check if a symbol is valid and exists."""
        try:
            validate_symbol(symbol)
            if "coingecko" in self._providers:
                provider = self._providers["coingecko"]
                if isinstance(provider, CoinGeckoProvider):
                    return await provider.validate_coin_exists(symbol)
            return True
        except (ValueError, ProviderError):
            return False

    async def close(self) -> None:
        """Close all provider connections."""
        close_tasks = [provider.close() for provider in self._providers.values()]
        await asyncio.gather(*close_tasks, return_exceptions=True)

    async def __aenter__(self) -> "FOMOAnalyzer":
        """Async context manager entry."""
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        """Async context manager exit."""
        await self.close()
