import abc
from typing import Any, Literal

from pydantic import Field, field_validator

from bayesline.api._src._utils import docstrings_from_sync
from bayesline.api._src.equity.modelconstruction_settings import WeightingScheme
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.settings import ReportSettings
from bayesline.api._src.equity.universe_settings import UniverseSettings
from bayesline.api._src.tasks import AsyncTask, Task
from bayesline.api._src.types import DateLike


class WeightsReportSettings(ReportSettings):
    """Settings for a weights report."""

    report_type: Literal["weights"] = Field(
        default="weights", description="Report type discriminator"
    )
    universe_settings: UniverseSettings = Field()
    weights: WeightingScheme = Field(
        description="The weighting scheme to use for the weights report.",
        default="InvIdioVar",
        examples=["InvIdioVar"],
    )

    @field_validator("weights", mode="after")
    @classmethod
    def check_valid_weights(cls, weights: WeightingScheme) -> WeightingScheme:
        if weights != "InvIdioVar":
            raise ValueError(f"Weights {weights} are not supported for weights report.")
        return weights


class WeightsReportAccessor(TypedReportAccessorApi):
    """Accessor for a weights report."""

    pass


@docstrings_from_sync
class AsyncWeightsReportAccessor(AsyncTypedReportAccessorApi):
    pass


class WeightsReportApi(
    ReportApi[
        [DateLike | None, DateLike | None],
        WeightsReportAccessor,
        WeightsReportSettings,
    ]
):
    """API for a weights report."""

    @abc.abstractmethod
    def get_maybe_cached(
        self,
        start_date: DateLike | None,
        end_date: DateLike | None,
        **kwargs: Any,
    ) -> WeightsReportAccessor | None:
        """
        Get the cached weights 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
        -------
        WeightsReportAccessor | None
            The cached weights report accessor API, or None if not cached.
        """

    @abc.abstractmethod
    def calculate(
        self,
        start_date: DateLike | None,
        end_date: DateLike | None,
        **kwargs: Any,
    ) -> WeightsReportAccessor:
        """
        Calculate the weights 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
        -------
        WeightsReportAccessor
            The weights report accessor.
        """

    @abc.abstractmethod
    def calculate_as_task(
        self,
        start_date: DateLike | None,
        end_date: DateLike | None,
        **kwargs: Any,
    ) -> Task[WeightsReportAccessor]:
        """
        Calculate the weights 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[WeightsReportAccessor]
            The task for the weights report.
        """


@docstrings_from_sync
class AsyncWeightsReportApi(
    AsyncReportApi[
        [DateLike | None, DateLike | None],
        AsyncWeightsReportAccessor,
        WeightsReportSettings,
    ]
):

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

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

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


class WeightsReportAccessorImpl(WeightsReportAccessor):  # noqa: D101

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

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


class AsyncWeightsReportAccessorImpl(AsyncWeightsReportAccessor):  # noqa: D101

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

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