"""Research-facing pricing specifications.

The pricing-spec layer separates quote-side selection from benchmark
selection, curve role wiring, and optional risk toggles used by the pricing
engine.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from decimal import Decimal
from enum import Enum

from fuggers_py.bonds.types import ASWType, Tenor

from .ids import CurveId


def _to_decimal(value: object) -> Decimal:
    if isinstance(value, Decimal):
        return value
    return Decimal(str(value))


class QuoteSide(str, Enum):
    """Normalized quote-side selector."""

    BID = "bid"
    ASK = "ask"
    MID = "mid"


@dataclass(frozen=True, slots=True)
class BenchmarkReference:
    """Benchmark selector by curve identifier or tenor string."""

    curve_id: CurveId | None = None
    tenor: str | None = None

    @classmethod
    def by_curve(cls, curve_id: CurveId | str) -> "BenchmarkReference":
        """Create a benchmark reference from a curve identifier."""
        return cls(curve_id=curve_id if isinstance(curve_id, CurveId) else CurveId.parse(curve_id))

    @classmethod
    def by_tenor(cls, tenor: str) -> "BenchmarkReference":
        """Create a benchmark reference from a validated tenor string."""
        Tenor.parse(tenor)
        return cls(tenor=tenor)

    def tenor_object(self) -> Tenor | None:
        """Return the parsed tenor object when a tenor was supplied."""
        return None if self.tenor is None else Tenor.parse(self.tenor)


@dataclass(frozen=True, slots=True)
class BidAskSpreadConfig:
    """Mid-to-side spread adjustments expressed in decimal units."""

    bid_adjustment: Decimal = Decimal(0)
    ask_adjustment: Decimal = Decimal(0)

    def __post_init__(self) -> None:
        object.__setattr__(self, "bid_adjustment", _to_decimal(self.bid_adjustment))
        object.__setattr__(self, "ask_adjustment", _to_decimal(self.ask_adjustment))

    @classmethod
    def symmetric(cls, width: object) -> "BidAskSpreadConfig":
        """Build symmetric bid/ask adjustments from a total width."""
        half_width = _to_decimal(width) / Decimal(2)
        return cls(bid_adjustment=-half_width, ask_adjustment=half_width)

    @classmethod
    def asymmetric(cls, *, bid_adjustment: object, ask_adjustment: object) -> "BidAskSpreadConfig":
        """Build bid and ask adjustments from explicit offsets."""
        return cls(bid_adjustment=_to_decimal(bid_adjustment), ask_adjustment=_to_decimal(ask_adjustment))

    def adjust(self, mid_value: object, side: QuoteSide) -> Decimal:
        """Return the side-specific quote implied by ``mid_value``."""
        mid = _to_decimal(mid_value)
        if side is QuoteSide.BID:
            return mid + self.bid_adjustment
        if side is QuoteSide.ASK:
            return mid + self.ask_adjustment
        return mid


@dataclass(frozen=True, slots=True)
class AnalyticsCurves:
    """Container for curve roles used by analytics and pricing flows."""

    discount_curve: object | None = None
    forward_curve: object | None = None
    government_curve: object | None = None
    benchmark_curve: object | None = None
    credit_curve: object | None = None
    repo_curve: object | None = None
    collateral_curve: object | None = None
    fx_forward_curve: object | None = None
    multicurve_environment: object | None = None
    projection_curves: dict[str, object] = field(default_factory=dict)
    vol_surface: object | None = None

    def __post_init__(self) -> None:
        object.__setattr__(self, "projection_curves", {str(key): value for key, value in self.projection_curves.items()})

    def get(self, role: str) -> object | None:
        """Resolve a curve by canonical role name or projection alias.

        Parameters
        ----------
        role
            Canonical role name such as ``discount_curve`` or a projection
            key of the form ``projection:<name>``.
        """
        normalized_role = role.strip().lower()
        resolved = {
            "discount": self.discount_curve,
            "discount_curve": self.discount_curve,
            "forward": self.forward_curve,
            "forward_curve": self.forward_curve,
            "government": self.government_curve,
            "government_curve": self.government_curve,
            "benchmark": self.benchmark_curve,
            "benchmark_curve": self.benchmark_curve,
            "credit": self.credit_curve,
            "credit_curve": self.credit_curve,
            "repo": self.repo_curve,
            "repo_curve": self.repo_curve,
            "collateral": self.collateral_curve,
            "collateral_curve": self.collateral_curve,
            "fx_forward": self.fx_forward_curve,
            "fx_forward_curve": self.fx_forward_curve,
            "multicurve_environment": self.multicurve_environment,
            "projection_curves": self.projection_curves,
            "vol_surface": self.vol_surface,
        }.get(normalized_role)
        if resolved is not None:
            return resolved
        if normalized_role.startswith("projection:"):
            projection_key = role.split(":", 1)[1].strip()
            if projection_key in self.projection_curves:
                return self.projection_curves[projection_key]
            lowered = projection_key.lower()
            for key, value in self.projection_curves.items():
                if key.lower() == lowered:
                    return value
            return None
        if role in self.projection_curves:
            return self.projection_curves[role]
        return None


@dataclass(frozen=True, slots=True)
class PricingSpec:
    """Pricing engine configuration and output selection toggles."""

    quote_side: QuoteSide = QuoteSide.MID
    market_price_is_dirty: bool | None = None
    compute_spreads: bool = True
    compute_risk: bool = True
    compute_key_rates: bool = False
    compute_current_yield: bool = True
    compute_yield_to_worst: bool = True
    include_asset_swap: bool = False
    asset_swap_type: ASWType = ASWType.PAR_PAR
    route_callable_with_oas: bool = True
    route_floating_with_discount_margin: bool = True
    callable_mean_reversion: Decimal = Decimal("0.03")
    callable_volatility: Decimal = Decimal("0.01")
    benchmark_reference: BenchmarkReference | None = None
    bid_ask: BidAskSpreadConfig | None = None


__all__ = [
    "AnalyticsCurves",
    "BenchmarkReference",
    "BidAskSpreadConfig",
    "PricingSpec",
    "QuoteSide",
]
