import abc
import datetime as dt
from typing import Literal

import pandas as pd

from bayesline.api._src.equity.exposure_settings import (
    ExposureSettings,
    ExposureSettingsMenu,
)
from bayesline.api._src.equity.universe import AssetUniverseApi, AsyncAssetUniverseApi
from bayesline.api._src.equity.universe_settings import UniverseSettings
from bayesline.api._src.registry import AsyncRegistryBasedApi, RegistryBasedApi
from bayesline.api._src.types import DateLike, IdType

FactorType = Literal["Market", "Style", "Industry", "Region"]


class AssetExposureApi(abc.ABC):

    @property
    @abc.abstractmethod
    def settings(self) -> ExposureSettings:
        """
        Returns
        -------
        The settings used to create these exposures.
        """
        ...

    @abc.abstractmethod
    def dates(
        self,
        universe: str | int | UniverseSettings | AssetUniverseApi,
        *,
        range_only: bool = False,
    ) -> list[dt.date]:
        """
        Parameters
        ----------
        universe: str | int | UniverseSettings | AssetUniverseApi
            The universe to use for the exposure calculation.
        range_only: bool, optional
            If True, returns the first and last date only.

        Returns
        -------
        list of all covered dates.
        """

    @abc.abstractmethod
    def coverage_stats(
        self,
        universe: str | int | UniverseSettings | AsyncAssetUniverseApi,
        *,
        id_type: IdType | None = None,
        by: Literal["date", "asset"] = "date",
    ) -> pd.DataFrame:
        """
        Parameters
        ----------
        universe: str | int | UniverseSettings | AssetUniverseApi
            The universe to use for the exposure calculation.
        id_type: IdType, optional
            The id type to return asset ids in, e.g. `ticker`.
            The given id type must be supported by the linked universe.
        by: str, optional
            The aggregation, either by date or by asset

        Returns
        -------
        pd.DataFrame
            A dataframe with date index and multi column index where the first
            level is the style name and the second level is the substyle name.
            The values are the counts of the underlying data before it was imputed.
        """

    @abc.abstractmethod
    def get(
        self,
        universe: str | int | UniverseSettings | AssetUniverseApi,
        *,
        start: DateLike | None = None,
        end: DateLike | None = None,
        id_type: IdType | None = None,
    ) -> pd.DataFrame:
        """
        Parameters
        ----------
        universe: str | int | UniverseSettings | AssetUniverseApi
            The universe to use for the exposure calculation.
        start: DateLike, optional
            The start date of the universe to return, inclusive.
        end: DateLike, optional
            The end date of the data to return, inclusive.
        id_type: IdType, optional
            The id type to return asset ids in, e.g. `ticker`.
            The given id type must be supported by the linked universe.

        Raises
        ------
        ValueError
            If the given id type is not supported or date range is invalid.

        Returns
        -------
        pd.DataFrame
            The data for the given date range with a multi-index where the date
            is the first level and the asset id is the second level.
            The columns are the individual styles.
        """
        ...


class AsyncAssetExposureApi(abc.ABC):

    @property
    @abc.abstractmethod
    def settings(self) -> ExposureSettings:
        """
        Returns
        -------
        The settings used to create these exposures.
        """
        ...

    @abc.abstractmethod
    async def dates(
        self,
        universe: str | int | UniverseSettings | AsyncAssetUniverseApi,
        *,
        range_only: bool = False,
    ) -> list[dt.date]:
        """
        Parameters
        ----------
        universe: str | int | UniverseSettings | AssetUniverseApi
            The universe to use for the exposure calculation.
        range_only: bool, optional
            If True, returns the first and last date only.

        Returns
        -------
        list of all covered dates.
        """

    @abc.abstractmethod
    async def coverage_stats(
        self,
        universe: str | int | UniverseSettings | AsyncAssetUniverseApi,
        *,
        id_type: IdType | None = None,
        by: Literal["date", "asset"] = "date",
    ) -> pd.DataFrame:
        """
        Parameters
        ----------
        universe: str | int | UniverseSettings | AssetUniverseApi
            The universe to use for the exposure calculation.
        id_type: IdType, optional
            The id type to return asset ids in, e.g. `ticker`.
            The given id type must be supported by the linked universe.
        by: str, optional
            The aggregation, either by date or by asset

        Returns
        -------
        pd.DataFrame
            A dataframe with date index and multi column index where the first
            level is the style name and the second level is the substyle name.
            The values are the counts of the underlying data before it was imputed.
        """

    @abc.abstractmethod
    async def get(
        self,
        universe: str | int | UniverseSettings | AsyncAssetUniverseApi,
        *,
        start: DateLike | None = None,
        end: DateLike | None = None,
        id_type: IdType | None = None,
    ) -> pd.DataFrame:
        """
        Parameters
        ----------
        universe: str | int | UniverseSettings | AssetUniverseApi
            The universe to use for the exposure calculation.
        start: DateLike, optional
            The start date of the universe to return, inclusive.
        end: DateLike, optional
            The end date of the data to return, inclusive.
        id_type: IdType, optional
            The id type to return asset ids in, e.g. `ticker`.
            The given id type must be supported by the linked universe.

        Raises
        ------
        ValueError
            If the given id type is not supported or date range is invalid.

        Returns
        -------
        pd.DataFrame
            The data for the given date range with a multi-index where the date
            is the first level and the asset id is the second level.
            The columns are the individual styles.
        """
        ...


class BayeslineEquityExposureApi(
    RegistryBasedApi[ExposureSettings, ExposureSettingsMenu, AssetExposureApi],
): ...


class AsyncBayeslineEquityExposureApi(
    AsyncRegistryBasedApi[
        ExposureSettings, ExposureSettingsMenu, AsyncAssetExposureApi
    ],
): ...
