"""Rates-specific pricing router.

The router keeps rates products on their own pricing path so the engine can
reuse multicurve environments, PV01 helpers, and product-specific output
records without routing them through the bond router.
"""

from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal

from fuggers_py.data.output import BasisSwapQuoteOutput, SwapQuoteOutput
from fuggers_py.data.pricing_specs import AnalyticsCurves
from fuggers_py.data.ids import InstrumentId
from fuggers_py.rates.instruments import BasisSwap, FixedFloatSwap, Fra, Ois
from fuggers_py.rates.pricing import BasisSwapPricer, FraPricer, SwapPricer
from fuggers_py.rates.risk import basis_swap_pv01, fra_pv01, swap_pv01


def _to_decimal(value: object | None) -> Decimal | None:
    if value is None or isinstance(value, Decimal):
        return value
    return Decimal(str(value))


@dataclass(frozen=True, slots=True)
class RoutedFraPricingResult:
    """Structured FRA result used when the generic swap result is not enough."""

    instrument_id: InstrumentId | None = None
    pricing_path: str | None = None
    forward_rate: Decimal | None = None
    present_value: Decimal | None = None
    year_fraction: Decimal | None = None
    discount_factor: Decimal | None = None
    pv01: Decimal | None = None

    def __post_init__(self) -> None:
        """Normalize identifiers and decimal-like fields."""
        if self.instrument_id is not None:
            object.__setattr__(self, "instrument_id", InstrumentId.parse(self.instrument_id))
        for field_name in ("forward_rate", "present_value", "year_fraction", "discount_factor", "pv01"):
            value = getattr(self, field_name)
            if value is not None:
                object.__setattr__(self, field_name, _to_decimal(value))
        if self.pricing_path is not None:
            object.__setattr__(self, "pricing_path", self.pricing_path.strip())


@dataclass(frozen=True, slots=True)
class RatesPricingRouter:
    """Price swaps, FRAs, basis swaps, and OIS instruments."""

    def price_swap(self, instrument: FixedFloatSwap | Ois, *, curves: AnalyticsCurves) -> SwapQuoteOutput:
        """Price a swap-style instrument and attach PV01 as DV01/PV01."""
        result = SwapPricer().price(instrument, curves)
        pv01 = swap_pv01(instrument, curves)
        return SwapQuoteOutput(
            instrument_id=instrument.instrument_id,
            pricing_path="ois" if isinstance(instrument, Ois) else "swap",
            par_rate=result.par_rate,
            present_value=result.present_value,
            fixed_leg_pv=result.fixed_leg_pv,
            floating_leg_pv=result.floating_leg_pv,
            annuity=result.annuity,
            dv01=pv01,
            pv01=pv01,
        )

    def price_fra(self, instrument: Fra, *, curves: AnalyticsCurves) -> RoutedFraPricingResult:
        """Price an FRA and return the routed FRA result record."""
        result = FraPricer().price(instrument, curves)
        return RoutedFraPricingResult(
            instrument_id=instrument.instrument_id,
            pricing_path="fra",
            forward_rate=result.forward_rate,
            present_value=result.present_value,
            year_fraction=result.year_fraction,
            discount_factor=result.discount_factor,
            pv01=fra_pv01(instrument, curves),
        )

    def price_basis_swap(self, instrument: BasisSwap, *, curves: AnalyticsCurves) -> BasisSwapQuoteOutput:
        """Price a basis swap and attach PV01 as both DV01 and PV01."""
        result = BasisSwapPricer().price(instrument, curves)
        pv01 = basis_swap_pv01(instrument, curves)
        return BasisSwapQuoteOutput(
            instrument_id=instrument.instrument_id,
            pricing_path="basis_swap",
            basis_spread=result.par_spread,
            present_value=result.present_value,
            pay_leg_pv=result.pay_leg_pv,
            receive_leg_pv=result.receive_leg_pv,
            dv01=pv01,
            pv01=pv01,
        )

    def price(self, instrument, *, curves: AnalyticsCurves):
        """Dispatch a rates instrument to the appropriate pricer."""
        if isinstance(instrument, (FixedFloatSwap, Ois)):
            return self.price_swap(instrument, curves=curves)
        if isinstance(instrument, Fra):
            return self.price_fra(instrument, curves=curves)
        if isinstance(instrument, BasisSwap):
            return self.price_basis_swap(instrument, curves=curves)
        raise TypeError(f"Unsupported rates instrument type: {type(instrument).__name__}.")

    def price_batch(self, instruments: list[object] | tuple[object, ...], *, curves: AnalyticsCurves) -> tuple[object, ...]:
        """Price a batch of rates instruments using a shared curve bundle."""
        return tuple(self.price(instrument, curves=curves) for instrument in instruments)


__all__ = ["RatesPricingRouter", "RoutedFraPricingResult"]
