"""Tests for the #000025 §10.11 SelfModel snapshot-chain — the
persistent lineage that replaces Phase-1a's synthetic parent→child
pairs in the Finetuning sub-battery.

Two surfaces:
  - ``bench/scripts/selfmodel_chain_snapshot.py`` — the bootstrapper
    that appends one chained snapshot per run (capability claims =
    the 5S/5T/5F battery rates).
  - ``bench.batteries.b_5f._chain_finetuning_measure`` + the
    ``run_finetuning`` "shard-chain" dispatch — reads the two latest
    snapshots and measures improvement on ``target_capability``.

All against temp shards (``tmp_path`` / ``tmp_path_factory``) — never
the real ``~/.arborist/shards/selfmodel-chain.db``.
"""
from __future__ import annotations

from pathlib import Path

import pytest

from bench.batteries import b_5f
from bench.scripts.selfmodel_chain_snapshot import append_snapshot

_F5F = Path(__file__).parent.parent / "bench" / "fixtures" / "5f"
_SHARDCHAIN_PACK = _F5F / "finetuning-shardchain-v1.jsonl"


@pytest.fixture(scope="module")
def chain_shard(tmp_path_factory) -> Path:
    """A persistent 2-deep SelfModel chain on a temp shard."""
    shard = tmp_path_factory.mktemp("smchain") / "chain.db"
    s1 = append_snapshot(shard, ts=1_700_000_000)
    s2 = append_snapshot(shard, ts=1_700_000_001)
    assert s1["depth"] == 1 and s1["parent_root"] is None
    assert s2["depth"] == 2 and s2["parent_root"] == s1["root"]
    return shard


# ---- bootstrapper -------------------------------------------------------


def test_bootstrapper_grows_chain_one_per_run(tmp_path):
    shard = tmp_path / "c.db"
    a = append_snapshot(shard, ts=1_700_000_000)
    assert a["depth"] == 1
    assert a["parent_root"] is None
    # 15 canonical sub-batteries → 15 capability claims.
    assert len(a["claims"]) == 15
    assert all(v == 1.0 for v in a["claims"].values())  # packs at ceiling
    b = append_snapshot(shard, ts=1_700_000_001)
    assert b["depth"] == 2
    assert b["parent_root"] == a["root"]
    assert b["root"] != a["root"]  # distinct parent ⇒ distinct root


def test_bootstrapper_claims_carry_real_metric_names(chain_shard: Path):
    a = append_snapshot(chain_shard, ts=1_700_000_002)  # depth 3
    assert a["depth"] == 3
    assert {"5S-syntax", "5T-time", "5F-feedback-loop"} <= set(a["claims"])


# ---- _chain_finetuning_measure -----------------------------------------


def test_chain_measure_reads_two_latest(chain_shard: Path):
    out = b_5f._chain_finetuning_measure(
        {"target_capability": "5F-feedback-loop", "selfmodel_shard": str(chain_shard)}
    )
    assert out["parent_measured"] == 1.0
    assert out["child_measured"] == 1.0
    assert out["parent_root"] != out["child_root"]


def test_chain_measure_env_var_overrides_fixture(chain_shard: Path, monkeypatch):
    monkeypatch.setenv("ARBORIST_SELFMODEL_CHAIN_DB", str(chain_shard))
    out = b_5f._chain_finetuning_measure(
        {"target_capability": "5S-syntax", "selfmodel_shard": "/nonexistent/ignored.db"}
    )
    assert out["child_measured"] == 1.0


def test_chain_measure_absent_shard_raises(tmp_path):
    with pytest.raises(FileNotFoundError, match="chain shard not found"):
        b_5f._chain_finetuning_measure(
            {"target_capability": "5S-syntax", "selfmodel_shard": str(tmp_path / "nope.db")}
        )


def test_chain_measure_single_snapshot_raises(tmp_path):
    shard = tmp_path / "one.db"
    append_snapshot(shard, ts=1_700_000_000)  # depth 1
    with pytest.raises(ValueError, match="only one snapshot"):
        b_5f._chain_finetuning_measure(
            {"target_capability": "5S-syntax", "selfmodel_shard": str(shard)}
        )


def test_chain_measure_unknown_metric_raises(chain_shard: Path):
    with pytest.raises(ValueError, match="no claim for"):
        b_5f._chain_finetuning_measure(
            {"target_capability": "NOT-A-REAL-METRIC", "selfmodel_shard": str(chain_shard)}
        )


# ---- run_finetuning shard-chain dispatch -------------------------------


def test_run_finetuning_shardchain_pack_passes(chain_shard: Path, monkeypatch):
    monkeypatch.setenv("ARBORIST_SELFMODEL_CHAIN_DB", str(chain_shard))
    res = b_5f.run_finetuning(_SHARDCHAIN_PACK)
    assert res.pass_count == 6
    assert res.fail_count == 0
    for t in res.per_task:
        assert t.detail["source"] == "shard-chain"
        assert t.detail["improvement"] == 0.0  # packs at ceiling ⇒ no drift
        assert t.detail["chain_parent_root"] != t.detail["chain_child_root"]


def test_run_finetuning_shardchain_pack_fails_honestly_without_chain(tmp_path, monkeypatch):
    monkeypatch.setenv("ARBORIST_SELFMODEL_CHAIN_DB", str(tmp_path / "absent.db"))
    res = b_5f.run_finetuning(_SHARDCHAIN_PACK)
    assert res.pass_count == 0
    assert res.fail_count == 6
    assert "FileNotFoundError" in res.per_task[0].detail["reason"]


def test_embedded_and_live_finetuning_unaffected():
    """The shard-chain dispatch is gated on ``selfmodel_shard``; the
    embedded + live packs still route through their original paths."""
    emb = b_5f.run_finetuning(_F5F / "finetuning-v1.jsonl")
    assert emb.pass_count == 50
    for t in emb.per_task:
        assert t.detail["source"] == "embedded"
