"""
EODHD adapter — secondary/fallback provider (docs/DATA_SOURCES.md §3).

Deliberately a partial implementation: auth wiring, the fundamentals-profile call, and the
price-history call are mapped (EODHD's `/fundamentals/{ticker}.{EXCHANGE}` and
`/eod/{ticker}.{EXCHANGE}` endpoints are stable and well-documented), and `list_universe()` is
mapped against `/exchange-symbol-list/{EXCHANGE}`. Statement-level line-item mapping
(`get_income_statements`/`get_balance_sheets`/`get_cash_flows`), `get_estimates`, and
`get_dividends` are left as `NotImplementedError` rather than guessed, per the project rule
against inventing API shapes that haven't been checked against a live response. Extending this is
the natural next step once a real EODHD key is available to verify field names against (see
docs/SPEC_COVERAGE.md).

Correction (final engineering pass, Part A7): the previous version of this docstring claimed
"universe listing" was among the unimplemented methods and did not mention `get_dividends` at
all. Both statements were wrong — `list_universe()` is implemented (and now has executed test
coverage), `get_dividends()` is not. Found by writing `tests/test_eodhd_adapter.py` against every
public method rather than trusting this docstring. See docs/AUDIT_EODHD_TESTS_A7.md.
"""
from __future__ import annotations

from datetime import date, datetime
from typing import Optional

import httpx

from app.adapters.base import ProviderAdapter, ProviderAuthError, ProviderNotFoundError, ProviderRateLimitError
from app.adapters.validation import ValidationIssue, ValidationReport, validate_price_bar
from app.adapters.schemas import (
    ProviderCompanyProfile, ProviderDividendRow, ProviderEstimateRow, ProviderFinancialPeriod,
    ProviderPriceBar,
)


def _parse_date(s: Optional[str]) -> Optional[date]:
    if not s:
        return None
    return datetime.strptime(s[:10], "%Y-%m-%d").date()


class EODHDAdapter(ProviderAdapter):
    name = "EODHD"
    tier = "SECONDARY"

    def __init__(
        self, api_key: str, base_url: str = "https://eodhd.com/api", timeout: float = 20.0,
        transport: Optional[httpx.BaseTransport] = None,
    ):
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._client = httpx.Client(base_url=self._base_url, timeout=timeout, transport=transport)

    def _get(self, path: str, **params):
        params["api_token"] = self._api_key
        params.setdefault("fmt", "json")
        resp = self._client.get(path, params=params)
        if resp.status_code == 401:
            raise ProviderAuthError(f"EODHD auth failed for {path}")
        if resp.status_code == 429:
            raise ProviderRateLimitError(f"EODHD rate limit hit for {path}")
        if resp.status_code == 404:
            raise ProviderNotFoundError(f"EODHD 404 for {path}")
        resp.raise_for_status()
        return resp.json()

    def get_company_profile(self, ticker: str, exchange_mic: Optional[str] = None) -> ProviderCompanyProfile:
        code = f"{ticker}.{exchange_mic}" if exchange_mic else ticker
        data = self._get(f"/fundamentals/{code}")
        general = data.get("General", {})
        return ProviderCompanyProfile(
            ticker=ticker, exchange_mic=general.get("Exchange"), legal_name=general.get("Name", ticker),
            display_name=general.get("Name", ticker), country_iso2=general.get("CountryISO"),
            sector=general.get("Sector"), industry=general.get("Industry"), currency=general.get("CurrencyCode", "USD"),
            isin=general.get("ISIN"), website=general.get("WebURL"), description=general.get("Description"),
            beta=None,
        )

    def get_prices(self, ticker: str, start: date, end: date) -> list[ProviderPriceBar]:
        """AUDIT FIX (Part D — docs/AUDIT_VALIDATION_D.md). This used to raise `KeyError` on one
        malformed bar, discarding every well-formed bar in the same response, and to pass a
        non-numeric `close` straight through as a `str`. Both behaviours were pinned by
        `tests/test_eodhd_adapter.py` in Part A7 and deliberately left for this part, so the fix
        could be applied to FMP and EODHD together instead of making the two inconsistent.

        Now: each row is validated individually; a bad row is dropped with a recorded reason and
        the good rows are returned. `self.last_price_validation` holds the report."""
        rows = self._get(f"/eod/{ticker}", period="d", **{"from": start.isoformat(), "to": end.isoformat()})
        report = ValidationReport()
        bars = []
        for r in rows:
            clean = validate_price_bar(
                {
                    "date": r.get("date"), "open": r.get("open"), "high": r.get("high"),
                    "low": r.get("low"), "close": r.get("close"),
                    "adjusted_close": r.get("adjusted_close"), "volume": r.get("volume"),
                },
                report,
            )
            if clean is None:
                continue
            bars.append(ProviderPriceBar(
                date=_parse_date(clean["date"]), open=clean["open"], high=clean["high"],
                low=clean["low"], close=clean["close"], adjusted_close=clean["adjusted_close"],
                volume=clean["volume"], currency="USD",
            ))
        self.last_price_validation = report
        return bars

    def get_income_statements(self, ticker: str, period: str = "annual", limit: int = 11) -> list[ProviderFinancialPeriod]:
        raise NotImplementedError(
            "EODHD statement field mapping not yet implemented — see module docstring; use FMP as "
            "primary or contribute the mapping once verified against a live EODHD response."
        )

    def get_balance_sheets(self, ticker: str, period: str = "annual", limit: int = 11) -> list[ProviderFinancialPeriod]:
        raise NotImplementedError("EODHD balance sheet mapping not yet implemented — see module docstring.")

    def get_cash_flows(self, ticker: str, period: str = "annual", limit: int = 11) -> list[ProviderFinancialPeriod]:
        raise NotImplementedError("EODHD cash flow mapping not yet implemented — see module docstring.")

    def get_estimates(self, ticker: str) -> list[ProviderEstimateRow]:
        raise NotImplementedError("EODHD estimates mapping not yet implemented — see module docstring.")

    def get_dividends(self, ticker: str) -> list[ProviderDividendRow]:
        raise NotImplementedError("EODHD dividends mapping not yet implemented — see module docstring.")

    def list_universe(self, exchange_mic: Optional[str] = None, country_iso2: Optional[str] = None) -> list[str]:
        """AUDIT FIX (Part D — docs/AUDIT_VALIDATION_D.md): this silently dropped rows without a
        `Code` key — the exact opposite policy to get_prices(), which used to fail the whole batch
        over one bad row. Part A7 pinned both and recorded the inconsistency. Same policy now
        applies to both: drop the row, keep the rest, and RECORD it.
        `self.last_universe_validation` holds the report."""
        rows = self._get(f"/exchange-symbol-list/{exchange_mic}") if exchange_mic else []
        report = ValidationReport()
        codes = []
        for r in rows:
            report.rows_seen += 1
            code = r.get("Code")
            if not code:
                report.add(ValidationIssue("Code", code, "universe row has no ticker code"))
                continue
            report.rows_accepted += 1
            codes.append(code)
        self.last_universe_validation = report
        return codes
