"""pm.features: the frozen bundle feature set (enabled-features.json).

Lazy installs OFF = the bundle's feature list is FROZEN to the file the
bundle wrote (the EXACT extras that installed on that target); pm sync
never deviates and never installs a plugin member.
"""

from __future__ import annotations

import pytest

import pm.features as feats


@pytest.fixture
def rooted(tmp_path, monkeypatch):
    """Point features_path at a temp runtime dir (store_root().parent)."""
    store = tmp_path / "tools"
    store.mkdir()
    monkeypatch.setattr("pm.paths.store_root", lambda: store)
    return tmp_path


def test_write_then_read_roundtrip(rooted):
    assert feats.read_features() is None
    path = feats.write_features(["web", "acp", "web"])
    assert path.is_file()
    got = feats.read_features()
    assert got == ["acp", "web"]  # sorted, deduped
    path.write_text("{ not json", encoding="utf-8")
    assert feats.read_features() is None




def test_features_path_in_bundle_uses_payload_root(rooted):
    payload = rooted / "payload"
    payload.mkdir()
    assert feats.features_path(payload) == payload / "enabled-features.json"


def test_sync_venv_refuses_outside_frozen_extras(rooted, monkeypatch):
    feats.write_features(["web", "acp"])


    import pm.install as ensure_mod
    from pm.package import InstallError

    monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: False)
    with pytest.raises(InstallError) as exc:
        ensure_mod.sync_venv(["slack"], explicit=True)
    assert "frozen" in str(exc.value) or "outside" in str(exc.value)
    from pm import receipt
    saved = receipt.latest()
    assert saved["refusal"]["code"] == "lazy-install"
    assert saved["outcome"] == "failed" and saved["exit_code"] != 0
    assert saved["steps"][-1]["ok"] is False
    assert "slack" in saved["steps"][-1]["detail"]
    assert "hermes pm install" in saved["steps"][-1]["detail"]


def test_sync_venv_allows_frozen_extras_when_lazy_off(rooted, monkeypatch):
    feats.write_features(["web"])

    from pm import paths
    from pm.lock import Facts
    from pm.environments import install_state_dir, runtime_facts_path

    import pm.install as ensure_mod

    # Matching stamp alone cannot certify a vanished environment. Reuse only
    # the recorded selection while retaining the disabled acquisition policy.
    repo = rooted / "repo"
    repo.mkdir()
    monkeypatch.setattr(paths, "repo_root", lambda: repo)
    environment = install_state_dir(repo) / "environments" / "frozen" / "venv"
    environment.mkdir(parents=True)
    (environment / "pyvenv.cfg").write_text("home = fixture\n")
    Facts(runtime_facts_path(repo)).record_state("venv", "stamp", ["web"], environment=environment)
    monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: False)
    venv_pkg = ensure_mod.get_package("venv")
    monkeypatch.setattr(
        venv_pkg, "expected_stamp", lambda extras: "stamp"
    )
    monkeypatch.setattr(venv_pkg, "apply", lambda *args, **kwargs: pytest.fail("current frozen environment rebuilt"))
    ensure_mod.sync_venv(["web"])
    (environment / "pyvenv.cfg").unlink()
    from pm.package import InstallError
    with pytest.raises(InstallError, match="lazy installs are disabled"):
        ensure_mod.sync_venv(["web"])


def test_lazy_sync_never_creates_the_first_selection_for_a_foreign_interpreter(rooted, monkeypatch):
    """A process running from an environment PM did not select (a build_environment test venv,
    a dev venv, nix) imports an adapter whose extra is missing. Letting that lazy sync commit
    the install's FIRST selection strands every later process: they boot into a generation
    that lacks whatever the foreign interpreter carried (the CI "anthropic/aiohttp vanished"
    class). Only an explicit install may create it. Exercised at the client seam every
    ensure_import caller goes through, in the in-process (is_runtime) shape."""
    import pm.client as client
    import pm.install as ensure_mod
    from pm import paths
    from pm.package import InstallError
    from pm.environments import runtime_facts_path

    repo = rooted / "repo"
    repo.mkdir()
    (repo / "venv").mkdir()  # the install's own base venv, which this pytest process is NOT running from
    monkeypatch.setattr(paths, "repo_root", lambda: repo)
    monkeypatch.setattr(client, "is_runtime", lambda: True)
    monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: True)
    venv_pkg = ensure_mod.get_package("venv")
    monkeypatch.setattr(venv_pkg, "expected_stamp", lambda extras, **kwargs: "stamp")
    monkeypatch.setattr(
        venv_pkg, "apply", lambda *args, **kwargs: pytest.fail("lazy sync built a generation for a foreign interpreter"))

    assert not runtime_facts_path(repo).exists()
    with pytest.raises(InstallError, match="not running from the install's dependency environment"):
        client.sync_venv(["bedrock"])
    assert not runtime_facts_path(repo).exists(), "a refused sync must not commit a selection"

    # The remedy the refusal names still works: an explicit install creates the selection.
    monkeypatch.setattr(venv_pkg, "apply", lambda *args, **kwargs: {})
    client.sync_venv(["bedrock"], explicit=True)
    assert runtime_facts_path(repo).is_file()
