"""Property-based tests for v3 posterior state projection.

Validates:
- Property 16: Projection evidence state decays toward zero

Requirements: 11.1, 11.3
"""
from __future__ import annotations

from hypothesis import given, settings
from hypothesis import strategies as st

from services.aggregation.projection import compute_v3_projection
from services.aggregation.regime import MarketRegime, V3RegimeClassification

# ---------------------------------------------------------------------------
# Strategies
# ---------------------------------------------------------------------------

# Initial evidence states (non-zero)
a_t_values = st.floats(
    min_value=-10.0, max_value=10.0, allow_nan=False, allow_infinity=False
).filter(lambda x: abs(x) > 0.01)

# Horizons >= 1
horizons = st.integers(min_value=1, max_value=50)

# Regimes
regimes = st.sampled_from(["panic", "trend_following", "mean_reversion", "uncertainty"])


def _make_regime(regime_name: str) -> V3RegimeClassification:
    """Create a V3RegimeClassification with the given regime name."""
    return V3RegimeClassification(
        regime=MarketRegime(regime_name),
        trend_z=0.0,
        vol_ratio=1.0,
        evidence_multiplier=1.0,
        confidence_multiplier=1.0,
        phi_decay={"panic": 0.35, "trend_following": 0.80, "mean_reversion": 0.55, "uncertainty": 0.50}[regime_name],
        atr_multiplier=2.0,
    )


# ---------------------------------------------------------------------------
# Feature: math-core-v3-engine, Property 16: Projection evidence state
#   decays toward zero
# ---------------------------------------------------------------------------
# **Validates: Requirements 11.1, 11.3**


@settings(max_examples=100)
@given(
    a_t=a_t_values,
    regime_name=regimes,
    h=horizons,
)
def test_property_16_projection_evidence_state_decays_toward_zero(
    a_t: float,
    regime_name: str,
    h: int,
) -> None:
    """Property 16: Projection evidence state decays toward zero.

    For any initial evidence state A_t and regime decay phi in (0, 1),
    the projected state A_projected_h = phi^h * A_t SHALL have
    |A_projected_h| < |A_t| for all h >= 1, converging toward 0 as h
    increases.
    """
    regime = _make_regime(regime_name)

    # Call compute_v3_projection with a_prev=a_t, cluster_llrs=[] (no new evidence),
    # known_catalyst_llr=0.0. This gives A_t = phi * a_prev (since no new LLRs).
    result = compute_v3_projection(
        a_prev=a_t,
        cluster_llrs=[],
        regime=regime,
        p_prior=0.50,
        projection_horizon=h,
        known_catalyst_llr=0.0,
    )

    # After update: A_t_new = phi * a_prev + 0 = phi * a_prev
    # After projection: A_projected = phi^h * A_t_new = phi^h * (phi * a_prev) = phi^(h+1) * a_prev
    # The phi values are all in (0, 1), so phi^(h+1) < 1 for h >= 1
    # Therefore |A_projected| < |a_prev|

    phi = regime.phi_decay
    # The evidence state after update (no new LLRs): A_t_new = phi * a_prev
    a_t_new = result.a_t
    # The projected alpha: A_projected = phi^h * A_t_new
    a_projected = (phi ** h) * a_t_new

    # |A_projected| must be less than |a_prev| because phi is in (0, 1)
    # and A_projected = phi^(h+1) * a_prev
    assert abs(a_projected) < abs(a_t), (
        f"|A_projected|={abs(a_projected)} should be < |a_prev|={abs(a_t)} "
        f"for phi={phi}, h={h}, regime={regime_name}"
    )

    # Verify convergence: larger h → smaller magnitude
    # Compute projected at h+10 and verify it's smaller than at h
    a_projected_larger_h = (phi ** (h + 10)) * a_t_new
    assert abs(a_projected_larger_h) <= abs(a_projected), (
        f"|A_projected(h+10)|={abs(a_projected_larger_h)} should be <= "
        f"|A_projected(h)|={abs(a_projected)} for phi={phi}, regime={regime_name}"
    )
