"""Financial Modeling Prep (FMP) fundamentals provider using httpx.

Uses the stable API endpoints (https://financialmodelingprep.com/stable/)
which replaced the legacy /api/v3/ endpoints deprecated in Aug 2025.
"""

from __future__ import annotations

import logging
import os
from datetime import datetime, timezone
from pathlib import Path

import httpx

from app.exceptions import ProviderError, RateLimitError
from app.providers.protocol import FundamentalData

logger = logging.getLogger(__name__)

_FMP_STABLE_URL = "https://financialmodelingprep.com/stable"

# Resolve CA bundle for explicit httpx verify
_CA_BUNDLE = os.environ.get("SSL_CERT_FILE", "")
if not _CA_BUNDLE or not Path(_CA_BUNDLE).exists():
    _CA_BUNDLE_PATH: str | bool = True  # use system default
else:
    _CA_BUNDLE_PATH = _CA_BUNDLE


class FMPFundamentalProvider:
    """Fetches fundamental data from Financial Modeling Prep REST API."""

    def __init__(self, api_key: str) -> None:
        if not api_key:
            raise ProviderError("FMP API key is required")
        self._api_key = api_key

    # Mapping from FMP endpoint name to the FundamentalData field it populates
    _ENDPOINT_FIELD_MAP: dict[str, str] = {
        "ratios-ttm": "pe_ratio",
        "financial-growth": "revenue_growth",
        "earnings": "earnings_surprise",
    }

    async def fetch_fundamentals(self, ticker: str) -> FundamentalData:
        """Fetch P/E, revenue growth, earnings surprise, and market cap.

        Fetches from multiple stable endpoints. If a supplementary endpoint
        (ratios, growth, earnings) returns 402 (paid tier), we gracefully
        degrade and return partial data rather than failing entirely, and
        record the affected field in ``unavailable_fields``.
        """
        try:
            endpoints_402: set[str] = set()

            async with httpx.AsyncClient(timeout=30.0, verify=_CA_BUNDLE_PATH) as client:
                params = {"symbol": ticker, "apikey": self._api_key}

                # Profile is the primary source — must succeed
                profile = await self._fetch_json(client, "profile", params, ticker)

                # Supplementary sources — degrade gracefully on 402
                ratios, was_402 = await self._fetch_json_optional(client, "ratios-ttm", params, ticker)
                if was_402:
                    endpoints_402.add("ratios-ttm")

                growth, was_402 = await self._fetch_json_optional(client, "financial-growth", params, ticker)
                if was_402:
                    endpoints_402.add("financial-growth")

                earnings, was_402 = await self._fetch_json_optional(client, "earnings", params, ticker)
                if was_402:
                    endpoints_402.add("earnings")

            pe_ratio = self._safe_float(ratios.get("priceToEarningsRatioTTM"))
            revenue_growth = self._safe_float(growth.get("revenueGrowth"))
            market_cap = self._safe_float(profile.get("marketCap"))
            earnings_surprise = self._compute_earnings_surprise(earnings)

            # Build unavailable_fields from 402 endpoints
            unavailable_fields: dict[str, str] = {
                self._ENDPOINT_FIELD_MAP[ep]: "requires paid plan"
                for ep in endpoints_402
                if ep in self._ENDPOINT_FIELD_MAP
            }

            return FundamentalData(
                ticker=ticker,
                pe_ratio=pe_ratio,
                revenue_growth=revenue_growth,
                earnings_surprise=earnings_surprise,
                market_cap=market_cap,
                fetched_at=datetime.now(timezone.utc),
                unavailable_fields=unavailable_fields,
            )

        except (ProviderError, RateLimitError):
            raise
        except Exception as exc:
            logger.error("FMP provider error for %s: %s", ticker, exc)
            raise ProviderError(f"FMP provider error for {ticker}: {exc}") from exc

    async def _fetch_json(
        self,
        client: httpx.AsyncClient,
        endpoint: str,
        params: dict,
        ticker: str,
    ) -> dict:
        """Fetch a stable endpoint and return the first item (or empty dict)."""
        url = f"{_FMP_STABLE_URL}/{endpoint}"
        resp = await client.get(url, params=params)
        self._check_response(resp, ticker, endpoint)
        data = resp.json()
        if isinstance(data, list):
            return data[0] if data else {}
        return data if isinstance(data, dict) else {}

    async def _fetch_json_optional(
        self,
        client: httpx.AsyncClient,
        endpoint: str,
        params: dict,
        ticker: str,
    ) -> tuple[dict, bool]:
        """Fetch a stable endpoint, returning ``({}, True)`` on 402 (paid tier).

        Returns a tuple of (data_dict, was_402) so callers can track which
        endpoints required a paid plan.
        """
        url = f"{_FMP_STABLE_URL}/{endpoint}"
        resp = await client.get(url, params=params)
        if resp.status_code == 402:
            logger.warning("FMP %s requires paid plan — skipping for %s", endpoint, ticker)
            return {}, True
        self._check_response(resp, ticker, endpoint)
        data = resp.json()
        if isinstance(data, list):
            return (data[0] if data else {}, False)
        return (data if isinstance(data, dict) else {}, False)

    def _compute_earnings_surprise(self, earnings_data: dict) -> float | None:
        """Compute earnings surprise % from the most recent actual vs estimated EPS."""
        actual = self._safe_float(earnings_data.get("epsActual"))
        estimated = self._safe_float(earnings_data.get("epsEstimated"))
        if actual is None or estimated is None or estimated == 0:
            return None
        return ((actual - estimated) / abs(estimated)) * 100

    def _check_response(
        self, resp: httpx.Response, ticker: str, endpoint: str
    ) -> None:
        """Raise appropriate errors for non-200 responses."""
        if resp.status_code == 429:
            raise RateLimitError(f"FMP rate limit hit for {ticker} ({endpoint})")
        if resp.status_code == 403:
            raise ProviderError(
                f"FMP {endpoint} access denied for {ticker}: HTTP 403 — check API key validity and plan tier"
            )
        if resp.status_code != 200:
            raise ProviderError(
                f"FMP {endpoint} error for {ticker}: HTTP {resp.status_code}"
            )

    @staticmethod
    def _safe_float(value: object) -> float | None:
        """Convert a value to float, returning None on failure."""
        if value is None:
            return None
        try:
            return float(value)
        except (TypeError, ValueError):
            return None
