from typing import Literal

from pydantic import BaseModel, Field

from bayesline.api._src.registry import Settings, SettingsMenu


class ReportSettingsMenu(SettingsMenu, frozen=True, extra="forbid"):
    """Defines the available report types and settings to create a report."""

    pass


class ReportSettings(Settings[ReportSettingsMenu]):
    """Concrete settings for a report.

    Subclasses must override `report_type` with a Literal type for polymorphic
    deserialization via the discriminated union `AnyReportSettings`.
    """

    report_type: str = Field(description="Discriminator field for report type")

    @property
    def menu_type(self) -> type[ReportSettingsMenu]:
        """The menu type for this settings object."""
        return ReportSettingsMenu


class FactorCovarianceSettings(BaseModel, frozen=True, extra="forbid"):
    """Settings for factor covariance matrix forecasts."""

    halflife_vol: float = Field(
        default=60.0, description="The half-life for the factor volatility."
    )
    halflife_cor: float = Field(
        default=120.0, description="The half-life for the factor correlation."
    )
    halflife_vra: float | None = Field(
        default=None,
        description=(
            "The half-life for the factor volatility regime adjustment. If None, no "
            "adjustment is applied."
        ),
    )
    nw_lags_vol: int = Field(
        default=0, description="The Newey-West lags of the factor volatility."
    )
    nw_lags_vol_halflife_override: float | None = Field(
        default=None,
        description=(
            "The Newey-West lags override for the factor volatility for lagged returns."
        ),
    )
    nw_lags_cor: int = Field(
        default=0, description="The Newey-West lags of the factor correlation."
    )
    shrink_cor_method: Literal["LIS", "QIS", "GIS"] | None = Field(
        default=None,
        description=(
            "The method to use for the shrinkage of the factor correlation. If None, "
            "don't shrink."
        ),
    )
    shrink_cor_length: int | None = Field(
        default=None,
        description=(
            "If a shrinkage method is provided, the length of the shrinkage for the "
            "factor correlation."
        ),
    )
    combine_standardized: bool = Field(
        default=False,
        description=(
            "If True, the correlation matrix is combined with an estimate based on the "
            "standardized returns. The halflife_factor_vra is used for the weights."
        ),
    )


class IdioVolSettings(BaseModel, frozen=True, extra="forbid"):
    """Settings for idiosyncratic volatility matrix forecasts."""

    halflife: float = Field(
        default=60.0, description="The half-life for the idiosyncratic volatility."
    )
    nw_lags: int = Field(
        default=0, description="The Newey-West lags of the idiosyncratic volatility."
    )
    nw_lags_halflife_override: float | None = Field(
        default=None,
        description=(
            "The Newey-West lags override for the idiosyncratic volatility for lagged "
            "returns."
        ),
    )
    clip_lb: float | None = Field(
        default=-0.3,
        description=(
            "The lower bound to clip the idiosyncratic return in order to compute "
            "the idiosyncratic volatility. If None, no clipping is applied."
        ),
    )
    clip_ub: float | None = Field(
        default=None,
        description=(
            "The upper bound to clip the idiosyncratic return in order to compute "
            "the idiosyncratic volatility. If None, no clipping is applied."
        ),
    )
    void_lb: float | None = Field(
        default=None,
        description=(
            "The lower bound to remove the idiosyncratic return in order to compute "
            "the idiosyncratic volatility. If None, no removal is applied. Removal of "
            "returns is applied before clipping."
        ),
    )
    void_ub: float | None = Field(
        default=0.5,
        description=(
            "The upper bound to remove the idiosyncratic return in order to compute "
            "the idiosyncratic volatility. If None, no removal is applied. Removal of "
            "returns is applied before clipping."
        ),
    )
