"""
ProviderAdapter interface (spec §7). Every adapter — FMP, EODHD, a future filing-based adapter,
or the DemoDataAdapter — implements this and only this; the ingestion worker and every caller
depend on this interface, never on a concrete provider class, so swapping providers is a config
change (`PROVIDER_PRIMARY` / `PROVIDER_SECONDARY`).
"""
from __future__ import annotations

from abc import ABC, abstractmethod
from datetime import date
from typing import Optional

from app.adapters.schemas import (
    ProviderCompanyProfile, ProviderDividendRow, ProviderEstimateRow, ProviderFinancialPeriod,
    ProviderPriceBar,
)


class ProviderRateLimitError(Exception):
    pass


class ProviderAuthError(Exception):
    pass


class ProviderNotFoundError(Exception):
    pass


class ProviderAdapter(ABC):
    """All methods are synchronous-return dataclass lists — concrete adapters may be async
    internally (FMPAdapter uses httpx under the hood) but this ABC keeps the calling code simple
    for v1; see SPEC_COVERAGE.md for the note on making this fully async in a later pass."""

    name: str
    tier: str  # "PRIMARY" | "SECONDARY" | "OFFICIAL_FILING" | "DEMO"

    @abstractmethod
    def get_company_profile(self, ticker: str, exchange_mic: Optional[str] = None) -> ProviderCompanyProfile: ...

    @abstractmethod
    def get_income_statements(self, ticker: str, period: str = "annual", limit: int = 11) -> list[ProviderFinancialPeriod]: ...

    @abstractmethod
    def get_balance_sheets(self, ticker: str, period: str = "annual", limit: int = 11) -> list[ProviderFinancialPeriod]: ...

    @abstractmethod
    def get_cash_flows(self, ticker: str, period: str = "annual", limit: int = 11) -> list[ProviderFinancialPeriod]: ...

    @abstractmethod
    def get_prices(self, ticker: str, start: date, end: date) -> list[ProviderPriceBar]: ...

    @abstractmethod
    def get_estimates(self, ticker: str) -> list[ProviderEstimateRow]: ...

    @abstractmethod
    def get_dividends(self, ticker: str) -> list[ProviderDividendRow]: ...

    @abstractmethod
    def list_universe(self, exchange_mic: Optional[str] = None, country_iso2: Optional[str] = None) -> list[str]:
        """Return a batch of tickers to ingest for a given exchange/country (spec §5/§6 universe
        + discovery scanning)."""
        ...
