from __future__ import annotations

from decimal import Decimal

import pytest

from fuggers_py.curves import (
    BondFairValueRequest,
    FittedBondCurveFitter,
    FittedBondFitConfig,
    FittedBondObjective,
    RegressionExposure,
    build_design_matrix,
    clean_price_from_curve,
    fair_value_from_curve,
    fair_value_from_fit,
    merge_regression_exposures,
    metadata_regression_exposures,
)

from ._fitted_bond_helpers import cubic_model, exponential_model, fit_result, make_observations


def test_exponential_spline_fitted_curve_recovers_prices_yields_and_regression_coefficients() -> None:
    result = fit_result(
        curve_model=exponential_model(),
        regression_coefficient=Decimal("0.35"),
    )

    assert result.diagnostics.converged is True
    assert float(result.diagnostics.weighted_rmse_price) == pytest.approx(0.0, abs=1e-6)
    assert float(result.coefficient_map()["liquidity"]) == pytest.approx(0.35, abs=1e-5)
    point = result.get_bond("UST6Y")
    assert float(point.fitted_dirty_price) == pytest.approx(float(point.observed_dirty_price), abs=1e-6)
    assert float(point.fitted_yield) == pytest.approx(float(point.observed_yield), abs=1e-8)


def test_cubic_spline_discount_factor_fit_matches_a_synthetic_cross_section() -> None:
    observations, _ = make_observations(curve_model=cubic_model(), regression_coefficient=Decimal("0.10"))
    fitter = FittedBondCurveFitter(
        curve_model=cubic_model(),
        config=FittedBondFitConfig(objective=FittedBondObjective.L2),
    )
    result = fitter.fit(observations)

    assert result.diagnostics.converged is True
    assert float(result.diagnostics.weighted_rmse_price) == pytest.approx(0.0, abs=1e-6)
    assert len(result.curve_parameter_names) == 5
    assert float(result.coefficient_map()["liquidity"]) == pytest.approx(0.10, abs=1e-5)


def test_l1_fit_path_runs_and_returns_stable_residual_outputs() -> None:
    result = fit_result(
        curve_model=exponential_model(),
        objective=FittedBondObjective.L1,
        regression_coefficient=Decimal("0.20"),
    )

    assert result.objective is FittedBondObjective.L1
    assert result.diagnostics.converged is True
    assert result.diagnostics.max_abs_price_residual < Decimal("0.001")


def test_fitted_bond_helper_exports_support_design_matrix_and_fair_value_workflows() -> None:
    observations, curve = make_observations(regression_coefficient=Decimal("0.25"))
    observation = observations[4]
    merged = merge_regression_exposures(
        observation.regression_exposures,
        (RegressionExposure("liquidity", Decimal("0.99")),),
    )
    metadata = metadata_regression_exposures(
        observation.reference_data,
        fields=("benchmark_flag", "futures_deliverable", "futures_deliverable_count"),
    )
    matrix = build_design_matrix(
        observations[:2],
        variable_names=("liquidity", "benchmark_flag"),
        metadata_fields=("benchmark_flag",),
    )
    curve_value = fair_value_from_curve(observation.bond, curve, observation.settlement_date)
    fitted = fit_result(regression_coefficient=Decimal("0.25"))
    fair_value = fair_value_from_fit(
        fitted,
        BondFairValueRequest(
            bond=observation.bond,
            settlement_date=observation.settlement_date,
            instrument_id=observation.instrument_id,
            regression_exposures=observation.regression_exposures,
            reference_data=observation.reference_data,
        ),
    )

    assert [exposure.name for exposure in merged] == ["liquidity"]
    assert [exposure.name for exposure in metadata] == [
        "benchmark_flag",
        "futures_deliverable",
        "futures_deliverable_count",
    ]
    assert matrix.variable_names == ("liquidity", "benchmark_flag")
    assert matrix.rows[0][1] == Decimal(0)
    assert matrix.rows[1][1] == Decimal(0)
    assert clean_price_from_curve(observation.bond, curve, observation.settlement_date) == curve_value.curve_clean_price
    assert fair_value.instrument_id == observation.instrument_id
    assert fair_value.regression_adjustment != Decimal(0)
