"""
EOD Historical Data (EODHD) Fetcher
High-quality, cost-effective fallback for international/ex-US equities.

Handles:
- Fundamentals (Valuation, Profitability, Growth)
- Smart Ticker Normalization (YFinance -> EODHD format)
- Rate Limit Circuit Breaking (stops requests after 429/402 errors)

Error Codes Handled (per EODHD Docs):
- 401/403: Invalid Token
- 402: Payment Required (Subscription upgrade needed)
- 429: Too Many Requests (Daily limit reached)
"""

from typing import Any

import aiohttp
import pandas as pd
import structlog

from src.config import config
from src.data.interfaces import FinancialFetcher

logger = structlog.get_logger(__name__)

# Default timeout for HTTP requests (session-level safety net + per-request)
_TIMEOUT = aiohttp.ClientTimeout(total=10)
_ANCHOR_TIMEOUT = aiohttp.ClientTimeout(total=5)


class EODHDFetcher(FinancialFetcher):
    """
    Async client for EOD Historical Data.
    Maintains state to stop requests if API limits are hit.
    """

    def __init__(self, api_key: str | None = None):
        self.api_key = api_key or config.get_eodhd_api_key()
        self.base_url = "https://eodhd.com/api"
        self._is_exhausted = False  # Circuit breaker for rate limits

    def is_available(self) -> bool:
        """Check if configured and not rate-limited."""
        return bool(self.api_key) and not self._is_exhausted

    def _normalize_ticker(self, ticker: str) -> str:
        """
        Convert yfinance ticker to EODHD format.

        Mappings:
        - US: "AAPL" -> "AAPL.US"
        - London: "BP.L" -> "BP.LSE" (Sometimes .L works, but .LSE is canonical)
        - Others: Generally match (0005.HK -> 0005.HK)
        """
        ticker = ticker.upper()

        # Specific overrides for common divergences
        if ticker.endswith(".L"):
            return ticker.replace(".L", ".LSE")

        # If no suffix, assume US
        if "." not in ticker:
            return f"{ticker}.US"

        return ticker

    async def _response_preview(self, response: aiohttp.ClientResponse) -> str:
        try:
            text = await response.text()
        except Exception as exc:
            return f"<unavailable: {type(exc).__name__}>"
        return " ".join(text.split())[:200]

    async def get_company_name(self, symbol: str) -> str | None:
        """Fetch company name from EODHD General data (lightweight, ~1kb)."""
        if not self.is_available():
            return None

        eod_symbol = self._normalize_ticker(symbol)
        url = f"{self.base_url}/fundamentals/{eod_symbol}"
        params = {"api_token": self.api_key, "fmt": "json", "filter": "General"}

        try:
            async with aiohttp.ClientSession(timeout=_ANCHOR_TIMEOUT) as session:
                async with session.get(
                    url, params=params, timeout=_ANCHOR_TIMEOUT
                ) as response:
                    if response.status == 200:
                        try:
                            data = await response.json()
                        except (ValueError, aiohttp.ContentTypeError):
                            return None
                        if isinstance(data, dict):
                            return data.get("Name")
                    return None
        except Exception as e:
            logger.debug(
                "eodhd_company_name_fetch_failed", symbol=eod_symbol, error=str(e)
            )
            return None

    async def get_price_history(self, ticker: str, period: str = "1y") -> pd.DataFrame:
        """
        Fetch OHLC data.
        Currently not implemented for EODHD to save API calls.
        """
        return pd.DataFrame()

    async def get_financial_metrics(
        self, symbol: str
    ) -> dict[str, float | None] | None:
        """
        Fetch fundamentals from EODHD.
        Returns processed dictionary or None if failed.
        """
        if not self.is_available():
            return None

        eod_symbol = self._normalize_ticker(symbol)
        url = f"{self.base_url}/fundamentals/{eod_symbol}"
        params = {"api_token": self.api_key, "fmt": "json"}

        try:
            async with aiohttp.ClientSession(timeout=_TIMEOUT) as session:
                async with session.get(
                    url, params=params, timeout=_TIMEOUT
                ) as response:
                    # --- Error Handling & Circuit Breaking ---
                    if response.status == 200:
                        try:
                            data = await response.json()
                        except (ValueError, aiohttp.ContentTypeError) as e:
                            logger.debug(
                                "eodhd_malformed_json", symbol=eod_symbol, error=str(e)
                            )
                            return None
                        return self._parse_fundamentals(data)

                    elif response.status == 429:
                        preview = await self._response_preview(response)
                        logger.error(
                            "eodhd_rate_limit_429", symbol=eod_symbol, preview=preview
                        )
                        self._is_exhausted = True
                        return None

                    elif response.status == 402:
                        preview = await self._response_preview(response)
                        logger.warning(
                            "eodhd_payment_required_402",
                            symbol=eod_symbol,
                            preview=preview,
                        )
                        # Don't disable globally, might just be this specific exchange
                        return None

                    elif response.status == 404:
                        logger.debug("eodhd_not_found", symbol=eod_symbol)
                        return None

                    else:
                        preview = await self._response_preview(response)
                        logger.warning(
                            "eodhd_api_error",
                            status=response.status,
                            symbol=eod_symbol,
                            preview=preview,
                        )
                        return None

        except Exception as e:
            logger.debug("eodhd_request_failed", error=str(e))
            return None

    async def verify_anchor_metrics(self, symbol: str) -> dict | None:
        """
        Lightweight spot-check for 'anchor' metrics (Cap, P/E, Yield).
        Called ONLY when free sources fail validation.

        Uses filter=Highlights to minimize payload (~1kb vs ~50kb).
        Handles 402 Payment Required gracefully for restricted licenses.
        """
        if not self.is_available():
            return None

        eod_symbol = self._normalize_ticker(symbol)
        url = f"{self.base_url}/fundamentals/{eod_symbol}"

        # 2026 optimization: fetch ONLY the Highlights block
        params = {"api_token": self.api_key, "fmt": "json", "filter": "Highlights"}

        try:
            async with aiohttp.ClientSession(timeout=_ANCHOR_TIMEOUT) as session:
                async with session.get(
                    url, params=params, timeout=_ANCHOR_TIMEOUT
                ) as response:
                    # CASE 1: Success
                    if response.status == 200:
                        try:
                            data = await response.json()
                        except (ValueError, aiohttp.ContentTypeError) as e:
                            logger.debug(
                                "eodhd_anchor_malformed_json",
                                symbol=eod_symbol,
                                error=str(e),
                            )
                            return None

                        return {
                            "marketCap": self._safe_float(
                                data.get("MarketCapitalization")
                            ),
                            "trailingPE": self._safe_float(data.get("PERatio")),
                            "dividendYield": self._safe_float(
                                data.get("DividendYield")
                            ),
                            "_source": "EODHD_anchor",
                        }

                    # CASE 2: Unpaid/Restricted License (402)
                    # Common for international data on free/low-tier plans
                    elif response.status == 402:
                        preview = await self._response_preview(response)
                        logger.info(
                            "eodhd_anchor_paywall_402",
                            symbol=eod_symbol,
                            preview=preview,
                        )
                        return None

                    # CASE 3: Rate limit (429) - disable future calls
                    elif response.status == 429:
                        self._is_exhausted = True
                        preview = await self._response_preview(response)
                        logger.error(
                            "eodhd_anchor_rate_limit_429",
                            symbol=eod_symbol,
                            preview=preview,
                        )
                        return None

                    # CASE 4: Auth Error (401/403)
                    elif response.status in [401, 403]:
                        self._is_exhausted = True
                        preview = await self._response_preview(response)
                        logger.warning(
                            "eodhd_anchor_auth_error",
                            status=response.status,
                            symbol=eod_symbol,
                            preview=preview,
                        )
                        return None

                    # CASE 5: Not found
                    elif response.status == 404:
                        logger.debug("eodhd_anchor_not_found", symbol=eod_symbol)
                        return None

                    return None

        except Exception as e:
            logger.debug("eodhd_anchor_check_failed", error=str(e))
            return None

    def _parse_fundamentals(self, data: dict) -> dict[str, float | None]:
        """Map EODHD JSON structure to internal schema."""
        output = {
            "_source": "eodhd",
            # Core Valuation
            "marketCap": None,
            "trailingPE": None,
            "forwardPE": None,
            "priceToBook": None,
            "pegRatio": None,
            # Profitability
            "returnOnEquity": None,
            "returnOnAssets": None,
            "profitMargins": None,
            "operatingMargins": None,
            "grossMargins": None,
            # Growth & Health
            "revenueGrowth": None,
            "earningsGrowth": None,
            "debtToEquity": None,
            "currentRatio": None,
            # Cash Flow
            "freeCashflow": None,
            "operatingCashflow": None,
            # Basics
            "currency": None,
            "currentPrice": None,
        }

        try:
            general = data.get("General", {})
            highlights = data.get("Highlights", {})
            valuation = data.get("Valuation", {})
            technicals = data.get("Technicals", {})
            financials = data.get("Financials", {})

            # --- Basics ---
            output["currency"] = general.get("CurrencyCode")
            # EODHD Technicals often has the 50d MA or close
            output["currentPrice"] = self._safe_float(technicals.get("50DayMA"))

            # --- Valuation ---
            output["marketCap"] = self._safe_float(
                highlights.get("MarketCapitalization")
            )
            output["trailingPE"] = self._safe_float(highlights.get("PERatio"))
            output["forwardPE"] = self._safe_float(valuation.get("ForwardPE"))
            output["priceToBook"] = self._safe_float(valuation.get("PriceBookMRQ"))
            output["pegRatio"] = self._safe_float(highlights.get("PEGRatio"))

            # --- Profitability ---
            output["returnOnEquity"] = self._safe_float(
                highlights.get("ReturnOnEquityTTM")
            )
            output["returnOnAssets"] = self._safe_float(
                highlights.get("ReturnOnAssetsTTM")
            )
            output["profitMargins"] = self._safe_float(highlights.get("ProfitMargin"))
            output["operatingMargins"] = self._safe_float(
                highlights.get("OperatingMarginTTM")
            )
            output["grossMargins"] = self._safe_float(
                highlights.get("GrossProfitMarginTTM")
            )

            # --- Growth ---
            output["revenueGrowth"] = self._safe_float(highlights.get("RevenueTTMYoy"))
            output["earningsGrowth"] = self._safe_float(
                highlights.get("EarningsShareTTMYoy")
            )

            # --- Health ---
            # EODHD Debt/Equity is usually absolute, ensure we don't get %.
            output["debtToEquity"] = self._safe_float(highlights.get("DebtToEquity"))
            output["currentRatio"] = self._safe_float(highlights.get("CurrentRatio"))

            # --- Cash Flow ---
            # Parse from Cash_Flow statement to get absolute numbers
            cash_flow_statement = financials.get("Cash_Flow", {}).get("yearly", {})
            if cash_flow_statement:
                # Keys are dates, sort to get most recent
                dates = sorted(cash_flow_statement.keys())
                if dates:
                    last_report = cash_flow_statement[dates[-1]]
                    output["freeCashflow"] = self._safe_float(
                        last_report.get("freeCashFlow")
                    )
                    output["operatingCashflow"] = self._safe_float(
                        last_report.get("totalCashFromOperatingActivities")
                    )

        except Exception as e:
            logger.warning("eodhd_parse_error", error=str(e))

        return output

    def _safe_float(self, value: Any) -> float | None:
        """Safely convert to float."""
        try:
            if value is None or value == "NA" or value == "NaN":
                return None
            return float(value)
        except (ValueError, TypeError):
            return None


# Singleton Pattern
_eodhd_fetcher = None


def get_eodhd_fetcher() -> EODHDFetcher:
    global _eodhd_fetcher
    if _eodhd_fetcher is None:
        _eodhd_fetcher = EODHDFetcher()
    return _eodhd_fetcher
