"""
Model versioning and reproducibility (final master pass, §39).

`MODEL_VERSION` is a claim; `model_fingerprint()` is the evidence. These tests exist to prove the
fingerprint is not decorative — that it really does change when a weight, threshold or assumption
changes, and really does NOT change for deployment settings that do not affect any number.

Executable here because `model_version.py` depends only on `pydantic_settings` (installed) and the
pure engine modules. Dual-mode: pytest, or `PYTHONPATH=. python3 tests/test_model_version.py`.
"""
from __future__ import annotations

from app.core.model_version import (
    MODEL_VERSION,
    MODEL_VERSION_HISTORY,
    model_configuration,
    model_fingerprint,
    model_stamp,
)


def test_fingerprint_is_deterministic():
    """Two calls in the same process, and any call in any other process, must agree — otherwise
    the fingerprint cannot be used to compare a stored score against the current model."""
    assert model_fingerprint() == model_fingerprint()


def test_fingerprint_is_independent_of_dict_ordering():
    """Serialisation uses sorted keys, so a reordered configuration hashes identically. Without
    this, the fingerprint would change whenever someone moved a line in model_configuration()."""
    config = model_configuration()
    reordered = dict(reversed(list(config.items())))
    assert model_fingerprint(config) == model_fingerprint(reordered)


def test_fingerprint_changes_when_a_pillar_weight_changes():
    """The central claim. If this fails, the fingerprint is decorative and every 'model version'
    label in the database is unverifiable."""
    config = model_configuration()
    before = model_fingerprint(config)
    config["pillar_weights"]["quality"] = config["pillar_weights"]["quality"] + 0.01
    assert model_fingerprint(config) != before


def test_fingerprint_changes_for_every_versioned_group():
    """Each group is versioned for a reason: change any of them and historical scores stop being
    comparable. This asserts none of them is silently excluded from the hash."""
    base = model_configuration()
    baseline = model_fingerprint(base)

    mutations = [
        ("metric formula version", lambda c: c.__setitem__("metric_formula_version", "v99")),
        ("renormalisation cap", lambda c: c.__setitem__("max_missing_weight_for_renormalization", 0.99)),
        ("min peer group size", lambda c: c.__setitem__("min_peer_group_size", 999)),
        ("industry multiple min group", lambda c: c.__setitem__("industry_multiple_min_group_size", 999)),
        ("multiples reference preference", lambda c: c.__setitem__("multiples_reference_preference", "self_only")),
        ("DCF terminal growth", lambda c: c["dcf"].__setitem__("terminal_growth", 0.99)),
        ("DCF scenario mode", lambda c: c["dcf"].__setitem__("scenario_mode", "multiplicative")),
        ("DCF explicit years", lambda c: c["dcf"].__setitem__("explicit_years", 5)),
        ("WACC risk-free rate", lambda c: c["wacc"].__setitem__("risk_free_rate", 0.99)),
        ("WACC equity risk premium", lambda c: c["wacc"].__setitem__("equity_risk_premium", 0.99)),
        ("margin of safety band", lambda c: c["margin_of_safety"].__setitem__("buy", 0.99)),
        ("reporting basis", lambda c: c.__setitem__("reporting_basis", "TTM")),
    ]
    for label, mutate in mutations:
        config = model_configuration()
        mutate(config)
        assert model_fingerprint(config) != baseline, f"{label} does not affect the fingerprint"


def test_fingerprint_does_not_include_deployment_settings():
    """A fingerprint that changed when someone resized a connection pool would be useless as a
    comparability signal. These keys must simply not be in the configuration at all."""
    config = model_configuration()
    flat = str(config)
    for forbidden in ("DATABASE_URL", "REDIS_URL", "JWT_SECRET", "POOL_SIZE", "CELERY_",
                      "RATE_LIMIT", "CORS"):
        assert forbidden not in flat, f"{forbidden} leaked into the model fingerprint"


def test_model_stamp_carries_all_three_identifiers():
    stamp = model_stamp()
    assert stamp["model_version"] == MODEL_VERSION
    assert len(stamp["model_fingerprint"]) == 16
    assert int(stamp["model_fingerprint"], 16) >= 0  # is hex
    assert stamp["metric_formula_version"]


def test_version_history_is_ordered_newest_first_and_documents_the_current_version():
    assert MODEL_VERSION_HISTORY[0][0] == MODEL_VERSION
    for version, note in MODEL_VERSION_HISTORY:
        assert version.count(".") == 2, f"{version} is not semantic"
        assert len(note) > 60, f"{version} has no real explanation of what changed"


def test_the_current_version_explains_why_older_scores_are_not_comparable():
    """A version bump with no stated consequence is a version bump nobody can act on."""
    _, note = MODEL_VERSION_HISTORY[0]
    assert "NOT comparable" in note or "not comparable" in note


ALL_TESTS = [v for k, v in sorted(globals().items()) if k.startswith("test_")]

if __name__ == "__main__":
    passed = failed = 0
    for t in ALL_TESTS:
        try:
            t()
            print(f"PASS  {t.__name__}")
            passed += 1
        except Exception as exc:  # noqa: BLE001
            print(f"FAIL  {t.__name__}: {exc}")
            failed += 1
    print(f"\n{passed}/{passed + failed} passed")
    raise SystemExit(1 if failed else 0)
