import abc
from typing import Any, Literal

from pydantic import Field

from bayesline.api._src._utils import docstrings_from_sync
from bayesline.api._src.equity.calendar_settings import CalendarSettings
from bayesline.api._src.equity.portfoliohierarchy_settings import (
    PortfolioHierarchySettings,
)
from bayesline.api._src.equity.portfolioreport import (
    AsyncReportAccessorApi,
    ReportAccessorApi,
)
from bayesline.api._src.equity.report.accessor import (
    AsyncTypedReportAccessorApi,
    TypedReportAccessorApi,
)
from bayesline.api._src.equity.report.api import AsyncReportApi, ReportApi
from bayesline.api._src.equity.report.scenarios import PortfolioHoldingsScenario
from bayesline.api._src.equity.report.settings import (
    FactorCovarianceSettings,
    IdioVolSettings,
    ReportSettings,
)
from bayesline.api._src.equity.riskmodels_settings import FactorRiskModelSettings
from bayesline.api._src.tasks import AsyncTask, Task
from bayesline.api._src.types import DateLike


class XSRReportSettings(ReportSettings):
    """Settings for an XSR (Exposure-Volatility-Correlation-Contribution) report."""

    report_type: Literal["xsr"] = Field(
        default="xsr", description="Report type discriminator"
    )
    portfolio_hierarchy_settings: PortfolioHierarchySettings = Field()
    factor_model_settings: FactorRiskModelSettings = Field()
    factor_cov_settings: FactorCovarianceSettings = Field(
        default_factory=FactorCovarianceSettings,
        description="Settings for the factor covariance matrix forecasts.",
    )
    idio_vol_settings: IdioVolSettings = Field(
        default_factory=IdioVolSettings,
        description="Settings for the idiosyncratic volatility matrix forecasts.",
    )
    calendar: CalendarSettings = Field(default_factory=CalendarSettings)
    currency: str | None = Field(None)
    rescale_bench: bool = Field(True)
    normalize_holdings: bool = Field(True)
    analytics_space: Literal["absolute", "active", "benchmark"] = Field("absolute")
    backfill_holdings: bool = Field(False)

    annualize: bool = Field(default=True)


class XSRReportAccessor(TypedReportAccessorApi):
    """Specific accessor for an XSR report."""

    scenario_types = [PortfolioHoldingsScenario]


@docstrings_from_sync
class AsyncXSRReportAccessor(AsyncTypedReportAccessorApi):
    scenario_types = [PortfolioHoldingsScenario]


class XSRReportApi(
    ReportApi[
        [DateLike | None, DateLike | None],
        XSRReportAccessor,
        XSRReportSettings,
    ]
):
    """API for an XSR report."""

    @abc.abstractmethod
    def get_maybe_cached(
        self,
        start_date: DateLike | None,
        end_date: DateLike | None,
        **kwargs: Any,
    ) -> XSRReportAccessor | None:
        """
        Get the cached XSR report accessor API if it is available.

        Parameters
        ----------
        start_date: DateLike | None
            The start date of the report.
        end_date: DateLike | None
            The end date of the report.
        **kwargs: Any
            Additional keyword arguments.

        Returns
        -------
        XSRReportAccessor | None
            The XSR report accessor if cached, otherwise None.
        """

    @abc.abstractmethod
    def calculate(
        self,
        start_date: DateLike | None,
        end_date: DateLike | None,
        **kwargs: Any,
    ) -> XSRReportAccessor:
        """
        Calculate the XSR report.

        Parameters
        ----------
        start_date: DateLike | None
            The start date of the report.
        end_date: DateLike | None
            The end date of the report.
        **kwargs: Any
            Additional keyword arguments.

        Returns
        -------
        XSRReportAccessor
            The XSR report accessor.
        """

    @abc.abstractmethod
    def calculate_as_task(
        self,
        start_date: DateLike | None,
        end_date: DateLike | None,
        **kwargs: Any,
    ) -> Task[XSRReportAccessor]:
        """
        Calculate the XSR report as a task.

        Parameters
        ----------
        start_date: DateLike | None
            The start date of the report.
        end_date: DateLike | None
            The end date of the report.
        **kwargs: Any
            Additional keyword arguments.

        Returns
        -------
        Task[XSRReportAccessor]
            The task for the XSR report accessor.
        """


@docstrings_from_sync
class AsyncXSRReportApi(
    AsyncReportApi[
        [DateLike | None, DateLike | None],
        AsyncXSRReportAccessor,
        XSRReportSettings,
    ]
):

    @abc.abstractmethod
    async def get_maybe_cached(  # noqa: D102
        self,
        start_date: DateLike | None,
        end_date: DateLike | None,
        **kwargs: Any,
    ) -> AsyncXSRReportAccessor | None: ...

    @abc.abstractmethod
    async def calculate(  # noqa: D102
        self,
        start_date: DateLike | None,
        end_date: DateLike | None,
        **kwargs: Any,
    ) -> AsyncXSRReportAccessor: ...

    @abc.abstractmethod
    async def calculate_as_task(  # noqa: D102
        self,
        start_date: DateLike | None,
        end_date: DateLike | None,
        **kwargs: Any,
    ) -> AsyncTask[AsyncXSRReportAccessor]: ...


class XSRReportAccessorImpl(XSRReportAccessor):  # noqa: D101

    def __init__(self, accessor: ReportAccessorApi):
        self._accessor = accessor

    @property
    def accessor(self) -> ReportAccessorApi:  # noqa: D102
        return self._accessor


class AsyncXSRReportAccessorImpl(AsyncXSRReportAccessor):  # noqa: D101

    def __init__(self, accessor: AsyncReportAccessorApi):
        self._accessor = accessor

    @property
    def accessor(self) -> AsyncReportAccessorApi:  # noqa: D102
        return self._accessor
