"""
FMP (Financial Modeling Prep) Data Fetcher
Fallback source for financial metrics when yfinance/yahooquery fail

This module provides access to FMP API as a backup data source for ex-US stocks
where Yahoo Finance coverage is insufficient.

Configuration:
    Set FMP_API_KEY in .env file

Error Handling:
    - Invalid API key: Raises ValueError (configuration error - must fix)
    - Data not found: Returns None (expected - log at DEBUG)
    - Network errors: Returns None (transient - log at DEBUG)

Usage:
    from src.data.fmp_fetcher import get_fmp_fetcher

    fmp = get_fmp_fetcher()
    if fmp.is_available():
        data = await fmp.get_financial_metrics("005930.KS")
"""

from datetime import datetime, timedelta
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)
_COOLDOWN_MINUTES = 15

_FMP_SUBSCRIPTION_MARKERS = (
    "not available under your current subscription",
    "upgrade your plan",
    "premium query parameter",
    "subscription page",
)


class FMPSubscriptionUnavailableError(ValueError):
    """Raised when the configured FMP plan does not cover the request."""


def _is_subscription_preview(preview: str) -> bool:
    normalized = preview.lower()
    return any(marker in normalized for marker in _FMP_SUBSCRIPTION_MARKERS)


class FMPFetcher(FinancialFetcher):
    """
    Minimal FMP API client for financial metrics.

    Implements waterfall fallback:
        yfinance -> yahooquery -> FMP -> partial data
    """

    def __init__(self, api_key: str | None = None):
        """
        Initialize FMP fetcher.

        Args:
            api_key: FMP API key (or None to read from config)
        """
        self.api_key = api_key or config.get_fmp_api_key()
        self.base_url = "https://financialmodelingprep.com/stable"
        self._key_validated = False
        self._cooldown_until: datetime | None = None
        self._subscription_blocked_endpoints: set[str] = set()

    def is_available(self) -> bool:
        """Check if FMP is configured (API key present)."""
        if not self.api_key:
            return False
        if self._cooldown_until and datetime.now() < self._cooldown_until:
            logger.debug(
                "fmp_unavailable",
                reason="cooldown_active",
                cooldown_until=self._cooldown_until.isoformat(),
            )
            return False
        return True

    def _start_cooldown(self, status: int, endpoint: str) -> None:
        self._cooldown_until = datetime.now() + timedelta(minutes=_COOLDOWN_MINUTES)
        logger.warning(
            "fmp_cooldown_started",
            status=status,
            endpoint=endpoint,
            cooldown_minutes=_COOLDOWN_MINUTES,
            cooldown_until=self._cooldown_until.isoformat(),
        )

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

    async def _get(self, endpoint: str, params: dict) -> Any | None:
        """
        Make API request with error handling.

        Args:
            endpoint: API endpoint (e.g., 'ratios', 'key-metrics')
            params: Query parameters

        Returns:
            JSON response data or None if request failed

        Raises:
            ValueError: If API key is invalid (403 on first request)
        """
        if not self.is_available():
            return None
        if endpoint in self._subscription_blocked_endpoints:
            logger.debug(
                "fmp_subscription_unavailable_cached",
                endpoint=endpoint,
                reason="subscription_paywall",
            )
            return None

        url = f"{self.base_url}/{endpoint}"
        params["apikey"] = self.api_key

        try:
            async with aiohttp.ClientSession(timeout=_TIMEOUT) as session:
                async with session.get(
                    url, params=params, timeout=_TIMEOUT
                ) as response:
                    if response.status == 200:
                        try:
                            data = await response.json()
                        except (ValueError, aiohttp.ContentTypeError) as e:
                            preview = await self._response_preview(response)
                            logger.warning(
                                "fmp_malformed_json",
                                endpoint=endpoint,
                                status=response.status,
                                error=str(e),
                                preview=preview,
                            )
                            return None
                        self._key_validated = True
                        return data

                    elif response.status == 403:
                        response_preview = await self._response_preview(response)
                        if not self._key_validated:
                            # Key is invalid - this is a configuration error
                            logger.error(
                                "fmp_auth_error",
                                endpoint=endpoint,
                                status=response.status,
                                key_state="unvalidated",
                                preview=response_preview,
                            )
                            raise ValueError(
                                "FMP_API_KEY is invalid or expired. Check your configuration."
                            )
                        else:
                            # Key was valid before, might be rate limit
                            self._start_cooldown(403, endpoint)
                            logger.warning(
                                "fmp_request_denied",
                                endpoint=endpoint,
                                status=response.status,
                                reason="possible_rate_limit_or_policy_change",
                                preview=response_preview,
                            )
                            return None

                    elif response.status in {402, 429}:
                        response_preview = await self._response_preview(response)
                        if response.status == 402 and _is_subscription_preview(
                            response_preview
                        ):
                            self._subscription_blocked_endpoints.add(endpoint)
                            logger.info(
                                "fmp_subscription_unavailable",
                                endpoint=endpoint,
                                status=response.status,
                                reason="subscription_paywall",
                                preview=response_preview,
                            )
                            raise FMPSubscriptionUnavailableError(
                                "current FMP plan does not cover this ticker or endpoint"
                            )
                        self._start_cooldown(response.status, endpoint)
                        logger.warning(
                            "fmp_request_limited",
                            endpoint=endpoint,
                            status=response.status,
                            reason="quota_or_rate_limit",
                            preview=response_preview,
                        )
                        return None

                    else:
                        # Other HTTP errors - log at debug level
                        preview = await self._response_preview(response)
                        logger.debug(
                            "fmp_http_error",
                            endpoint=endpoint,
                            status=response.status,
                            preview=preview,
                        )
                        return None

        except ValueError:
            # Re-raise API key validation errors
            raise
        except aiohttp.ClientError as e:
            # Network errors - log at debug level
            logger.debug(
                "fmp_network_error",
                endpoint=endpoint,
                error_type=type(e).__name__,
                error=str(e),
            )
            return None
        except Exception as e:
            # Unexpected errors - log at debug level
            logger.warning(
                "fmp_request_failed",
                endpoint=endpoint,
                error_type=type(e).__name__,
                error=str(e),
            )
            return None

    async def get_company_name(self, ticker: str) -> str | None:
        """Fetch company name from FMP profile endpoint."""
        try:
            data = await self._get("profile", {"symbol": ticker})
        except FMPSubscriptionUnavailableError:
            return None
        if data and isinstance(data, list) and len(data) > 0:
            name = data[0].get("companyName")
            if name:
                return name
        return None

    async def get_price_history(self, ticker: str, period: str = "1y") -> pd.DataFrame:
        """
        Returns OHLC DataFrame with standard columns.
        Currently not implemented for FMP to save API calls.
        """
        return pd.DataFrame()

    async def get_financial_metrics(self, symbol: str) -> dict[str, Any] | None:
        """
        Get comprehensive financial metrics for a symbol.

        Fetches data from multiple FMP endpoints and combines them into a single dict.
        Returns standardized keys: trailingPE, priceToBook, returnOnEquity, etc.
        """
        result = {}

        # Fetch ratios endpoint (has P/E, P/B, PEG, current ratio, D/E, margins)
        try:
            ratios = await self._get("ratios", {"symbol": symbol, "limit": 1})
        except FMPSubscriptionUnavailableError:
            return None
        if ratios and isinstance(ratios, list) and len(ratios) > 0:
            r = ratios[0]
            # Map to standard interface keys
            result["trailingPE"] = r.get("priceToEarningsRatio")
            result["priceToBook"] = r.get("priceToBookRatio")
            result["pegRatio"] = r.get("priceToEarningsGrowthRatio")
            result["currentRatio"] = r.get("currentRatio")
            result["debtToEquity"] = r.get("debtToEquityRatio")
            result["profitMargins"] = r.get("netProfitMargin")
            result["freeCashflow"] = r.get(
                "freeCashFlowPerShare"
            )  # Ratio endpoint often has per share
            result["operatingCashflow"] = r.get("operatingCashFlowPerShare")

        # Fetch key-metrics endpoint (has ROE, ROA, Cash Flows)
        try:
            metrics = await self._get("key-metrics", {"symbol": symbol, "limit": 1})
        except FMPSubscriptionUnavailableError:
            return result or None
        if metrics and isinstance(metrics, list) and len(metrics) > 0:
            m = metrics[0]
            result["returnOnEquity"] = m.get("returnOnEquity")
            result["returnOnAssets"] = m.get("returnOnAssets")
            # Prefer absolute values if available
            if m.get("freeCashFlowPerShare"):
                result["freeCashflow"] = m.get("freeCashFlowPerShare")
            if m.get("operatingCashFlowPerShare"):
                result["operatingCashflow"] = m.get("operatingCashFlowPerShare")
            if m.get("marketCap"):
                result["marketCap"] = m.get("marketCap")

        # Fetch income statement growth endpoint (has revenue/EPS growth)
        try:
            growth = await self._get(
                "income-statement-growth", {"symbol": symbol, "limit": 1}
            )
        except FMPSubscriptionUnavailableError:
            return result or None
        if growth and isinstance(growth, list) and len(growth) > 0:
            g = growth[0]
            result["revenueGrowth"] = g.get("growthRevenue")
            result["earningsGrowth"] = g.get("growthEPS")

        # Log if we got no data at all
        if not result:
            logger.debug("fmp_no_data", symbol=symbol)
            return None

        result["_source"] = "fmp"
        return result


# Global singleton instance
_fmp_fetcher: FMPFetcher | None = None


def get_fmp_fetcher() -> FMPFetcher:
    """
    Get or create global FMP fetcher instance.

    Returns:
        FMPFetcher instance (may not be available if no API key)
    """
    global _fmp_fetcher
    if _fmp_fetcher is None:
        _fmp_fetcher = FMPFetcher()
    return _fmp_fetcher


async def fetch_fmp_metrics(symbol: str) -> dict[str, float | None] | None:
    """
    Convenience function to fetch FMP metrics.

    Args:
        symbol: Stock ticker symbol

    Returns:
        Dict of financial metrics or None if FMP not available

    Raises:
        ValueError: If API key is invalid
    """
    fmp = get_fmp_fetcher()
    if not fmp.is_available():
        return None

    return await fmp.get_financial_metrics(symbol)
