"""Fair-value helpers for fitted bond curves.

The fair-value helpers distinguish curve-implied dirty and clean prices from
regression-adjusted fair values. Regression adjustments are additive on dirty
price before the accrued-interest subtraction is applied.
"""

from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal

from fuggers_py.bonds.pricing import BondPricer
from fuggers_py.bonds.traits import Bond
from fuggers_py.core.traits import YieldCurve
from fuggers_py.core.types import Date, Price
from fuggers_py.data.ids import InstrumentId
from fuggers_py.data.reference_data import BondReferenceData

from .model import FittedBondFitResult
from .regression import RegressionExposure, evaluate_regression_adjustment


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


@dataclass(frozen=True, slots=True)
class BondFairValueRequest:
    """Input request for a fitted-bond fair value calculation."""

    bond: Bond
    settlement_date: Date
    instrument_id: InstrumentId | None = None
    regression_exposures: tuple[RegressionExposure, ...] = ()
    reference_data: BondReferenceData | None = None

    def __post_init__(self) -> None:
        if self.instrument_id is not None:
            object.__setattr__(self, "instrument_id", InstrumentId.parse(self.instrument_id))


@dataclass(frozen=True, slots=True)
class BondFairValueResult:
    """Fair-value output for a single bond."""

    instrument_id: InstrumentId | None
    curve_dirty_price: Decimal
    curve_clean_price: Decimal
    fair_value_dirty_price: Decimal
    fair_value_clean_price: Decimal
    fair_value_yield: Decimal
    regression_adjustment: Decimal


def dirty_price_from_curve(bond: Bond, curve: YieldCurve, settlement_date: Date) -> Decimal:
    """Return the curve-implied dirty price in percent of par."""
    present_value = Decimal(0)
    for cash_flow in bond.cash_flows(from_date=settlement_date):
        present_value += cash_flow.factored_amount() * curve.discount_factor(cash_flow.date)
    return present_value


def clean_price_from_curve(bond: Bond, curve: YieldCurve, settlement_date: Date) -> Decimal:
    """Return the curve-implied clean price in percent of par."""
    return dirty_price_from_curve(bond, curve, settlement_date) - bond.accrued_interest(settlement_date)


def fair_value_from_curve(bond: Bond, curve: YieldCurve, settlement_date: Date) -> BondFairValueResult:
    """Return a fair-value result without any regression adjustment."""
    dirty = dirty_price_from_curve(bond, curve, settlement_date)
    clean = dirty - bond.accrued_interest(settlement_date)
    fair_yield = BondPricer().yield_from_price(
        bond,
        Price.new(clean, bond.currency()),
        settlement_date,
    ).ytm.value()
    return BondFairValueResult(
        instrument_id=None,
        curve_dirty_price=dirty,
        curve_clean_price=clean,
        fair_value_dirty_price=dirty,
        fair_value_clean_price=clean,
        fair_value_yield=fair_yield,
        regression_adjustment=Decimal(0),
    )


def fair_value_from_fit(
    fit_result: FittedBondFitResult,
    request: BondFairValueRequest,
) -> BondFairValueResult:
    """Return the fitted fair value including the regression adjustment."""
    curve_dirty = dirty_price_from_curve(request.bond, fit_result.curve, request.settlement_date)
    curve_clean = curve_dirty - request.bond.accrued_interest(request.settlement_date)
    regression_adjustment = evaluate_regression_adjustment(
        request.regression_exposures,
        variable_names=tuple(coefficient.name for coefficient in fit_result.coefficients),
        coefficients=tuple(coefficient.value for coefficient in fit_result.coefficients),
        reference_data=request.reference_data,
        metadata_fields=fit_result.metadata_fields,
    )
    fair_dirty = curve_dirty + regression_adjustment
    fair_clean = fair_dirty - request.bond.accrued_interest(request.settlement_date)
    fair_yield = BondPricer().yield_from_price(
        request.bond,
        Price.new(fair_clean, request.bond.currency()),
        request.settlement_date,
    ).ytm.value()
    return BondFairValueResult(
        instrument_id=request.instrument_id,
        curve_dirty_price=curve_dirty,
        curve_clean_price=curve_clean,
        fair_value_dirty_price=fair_dirty,
        fair_value_clean_price=fair_clean,
        fair_value_yield=fair_yield,
        regression_adjustment=regression_adjustment,
    )


__all__ = [
    "BondFairValueRequest",
    "BondFairValueResult",
    "clean_price_from_curve",
    "dirty_price_from_curve",
    "fair_value_from_curve",
    "fair_value_from_fit",
]
