"""
VirelaiX / SUBSTRATE-001 — Fixed-Point Rejection Test Suite
Prevents regression on core Watkins invariants by rejecting known-incorrect
historical values.

Phase 87.0 | CSID: SUBSTRATE-001 | 2026-03-11

Origin: Grok expansion (adversarial generation of bad-value catalog)
Audit: Claude Opus 4.6 (3 bugs fixed — see inline comments)

Pattern: For each canonical invariant, maintain a list of historically
incorrect values that have appeared in prior versions. Assert that the
canonical value is NOT equal to any of them. This prevents silent
regression forever.

This version imports constants from watkins_nn.constants (package-under-test).

Usage: pytest tests/test_reject_incorrect_invariants.py -v
"""

import math
import pytest

from watkins_nn.constants import (
    PHI, T_STAR, W_SQUARED, M_PHI,
    LAM_STAR as LAMBDA_STAR,
    KAP_STAR, ETA_STAR,
    MU_SLOW, MU_FAST,
    F_STAR,
)

# ==============================================================================
# DERIVED QUANTITIES (not exported by the package)
# ==============================================================================

TAU_GLOBAL = 1 / (math.sqrt(MU_SLOW * MU_FAST))   # geometric mean mixing time
TAU_STEP = 1 / M_PHI                                # step-level convergence rate

# Default initialization state
LAMBDA_0 = 0.6180339887498949
KAPPA_0 = 0.3000000000000000
ETA_0 = 0.0819660112501051

# Tolerance: well above machine epsilon but catches any real regression
REJECTION_TOL = 1e-6


# ==============================================================================
# SELF-CONSISTENCY (verify our own constants before testing anything else)
# ==============================================================================

class TestCanonicalSelfConsistency:
    """Verify the test file's own constants are correct."""

    def test_phi_identity(self):
        assert abs(PHI**2 - PHI - 1) < 1e-15, "phi^2 != phi + 1"

    def test_lambda_star_is_phi_inverse(self):
        assert abs(LAMBDA_STAR - (PHI - 1)) < 1e-15, "lambda* != phi - 1"

    def test_conservation_default_state(self):
        total = LAMBDA_0 + KAPPA_0 + ETA_0
        assert abs(total - 1.0) < 1e-15, f"Default state violates conservation: {total}"

    def test_conservation_equilibrium(self):
        total = LAMBDA_STAR + KAP_STAR + ETA_STAR
        assert abs(total - 1.0) < 1e-15, f"Equilibrium violates conservation: {total}"

    def test_eigenvalues_positive(self):
        assert MU_SLOW > 0, "mu_slow must be positive (stable minimum)"
        assert MU_FAST > 0, "mu_fast must be positive (stable minimum)"
        assert MU_SLOW < MU_FAST, "mu_slow must be the smaller eigenvalue"

    def test_watkins_bridge(self):
        assert abs(2**W_SQUARED - PHI) < 1e-12, "2^W^2 != phi"


# ==============================================================================
# REJECTION TESTS — known-incorrect historical values
# ==============================================================================

class TestRejectIncorrectLambdaStar:
    """Reject historically incorrect values for the coherence threshold."""

    @pytest.mark.parametrize("bad,source", [
        (1/3,          "uniform simplex point"),
        (0.5,          "naive symmetry assumption"),
        (2/3,          "wrong golden ratio branch"),
        (0.666666,     "truncated 2/3"),
        (0.618,        "3-digit truncation"),
        (0.610,        "misremembered value"),
        (PHI - 1.001,  "corrupted phi computation"),
    ])
    def test_reject(self, bad, source):
        assert abs(LAMBDA_STAR - bad) > REJECTION_TOL, (
            f"lambda* regression from '{source}': canonical={LAMBDA_STAR:.16f}, bad={bad}"
        )


class TestRejectIncorrectTStar:
    """Reject historically incorrect values for the Watkins Temperature."""

    @pytest.mark.parametrize("bad,source", [
        (PHI / math.log(PHI),     "ln(phi) instead of ln(2*phi) — the classic error"),
        (PHI / math.log(2),       "ln(2) instead of ln(2*phi)"),
        (1.386,                    "approximate ln(phi) substitution"),
        (1.442,                    "approximate ln(2) substitution"),
        (1.3778,                   "4-digit truncation"),
        (1.0,                      "unit temperature assumption"),
        (math.log(2),              "ln(2) confused for T*"),
    ])
    def test_reject(self, bad, source):
        assert abs(T_STAR - bad) > REJECTION_TOL, (
            f"T* regression from '{source}': canonical={T_STAR:.16f}, bad={bad}"
        )


class TestRejectIncorrectEigenvalues:
    """Reject v0.7.0 Copilot eigenvalues and other historical bugs.

    ROOT CAUSE OF v0.7.0 BUG: Hessian cross-term d2F/dlam*dkap was set to 0
    instead of T*/eta* ~ 7.214. This gave diagonal-only eigenvalues that
    were completely wrong.
    """

    @pytest.mark.parametrize("bad,source", [
        (4.847,  "v0.7.0 diagonal-only H11 (no cross-term)"),
        (7.215,  "v0.7.0 diagonal-only H22 (no cross-term)"),
        (1.49,   "v0.7.0 condition number (from wrong eigenvalues)"),
    ])
    def test_reject_slow_eigenvalue(self, bad, source):
        assert abs(MU_SLOW - bad) > REJECTION_TOL, (
            f"mu_slow regression from '{source}': canonical={MU_SLOW:.6f}, bad={bad}"
        )

    @pytest.mark.parametrize("bad,source", [
        (4.847,  "v0.7.0 diagonal-only H11"),
        (7.215,  "v0.7.0 diagonal-only H22"),
    ])
    def test_reject_fast_eigenvalue(self, bad, source):
        assert abs(MU_FAST - bad) > REJECTION_TOL, (
            f"mu_fast regression from '{source}': canonical={MU_FAST:.6f}, bad={bad}"
        )

    def test_cross_term_nonzero(self):
        """The Hessian cross-term must NEVER be zero. This was the v0.7.0 root cause."""
        cross_term = T_STAR / ETA_STAR
        assert cross_term > 1.0, (
            f"Hessian cross-term is {cross_term} — must be >> 0 (expect ~7.214)"
        )


class TestRejectIncorrectMixingTime:
    """Reject mixing times derived from wrong eigenvalues."""

    @pytest.mark.parametrize("bad,source", [
        (1 / 4.847,  "v0.7.0: 1/mu_slow with wrong mu_slow"),
        (0.206,       "v0.7.0 reported mixing time"),
        (1 / 7.215,   "v0.7.0: 1/mu_fast with wrong mu_fast"),
    ])
    def test_reject(self, bad, source):
        canonical = 1 / MU_SLOW
        assert abs(canonical - bad) > REJECTION_TOL, (
            f"Mixing time regression from '{source}': canonical={canonical:.6f}, bad={bad}"
        )


class TestRejectIncorrectConservation:
    """Reject known-broken equilibrium triples."""

    @pytest.mark.parametrize("lam,kap,eta,source", [
        (0.618, 0.191, 0.191,     "3-digit rounding (sums to 1.000, but imprecise)"),
        (0.618, 0.200, 0.182,     "arbitrary kap/eta split"),
        (0.500, 0.250, 0.250,     "symmetric but wrong lambda*"),
        (1/3,   1/3,   1/3,       "uniform — not an equilibrium"),
    ])
    def test_reject_equilibrium_triple(self, lam, kap, eta, source):
        """These are not the true equilibrium, even if they sum to ~1."""
        assert (
            abs(lam - LAMBDA_STAR) > REJECTION_TOL
            or abs(kap - KAP_STAR) > REJECTION_TOL
            or abs(eta - ETA_STAR) > REJECTION_TOL
        ), f"False equilibrium from '{source}': ({lam}, {kap}, {eta})"


class TestRejectIncorrectFStar:
    """Reject incorrect free energy values at equilibrium."""

    @pytest.mark.parametrize("bad,source", [
        (0.0,    "zero — F* is negative at equilibrium"),
        (-1.0,   "round number guess"),
        (-0.693, "-ln(2) confusion"),
        (-0.800, "2-digit rounding of F*"),
    ])
    def test_reject(self, bad, source):
        assert abs(F_STAR - bad) > REJECTION_TOL, (
            f"F* regression from '{source}': canonical={F_STAR:.10f}, bad={bad}"
        )


class TestRejectIncorrectBridgeConstant:
    """Reject incorrect Watkins bridge W^2 = ln(phi)/ln(2)."""

    @pytest.mark.parametrize("bad,source", [
        (0.5,            "naive half"),
        (1.0,            "unit assumption"),
        (math.log(PHI),  "ln(phi) without /ln(2) — missing normalization"),
        (0.694,          "3-digit truncation"),
    ])
    def test_reject(self, bad, source):
        assert abs(W_SQUARED - bad) > REJECTION_TOL, (
            f"W^2 regression from '{source}': canonical={W_SQUARED:.10f}, bad={bad}"
        )
