"""Regression tests: the approval guard path reads config via
load_config_readonly() (no per-call deepcopy).

The guard path runs per terminal command. load_config() pays a defensive
deepcopy on every call (~356us of the ~376us warm-cache cost, measured on
a real config.yaml) and the guard path loaded config 2-3x per command.
Every swapped call site was audited read-only (all callers take scalar
reads or iterate; none mutate the returned dict or any nested structure),
so they now use load_config_readonly() — the API built for exactly this
(hermes_cli/config.py docstring; precedent: #74211, #74322).

These tests drive the REAL functions against a temp HERMES_HOME config
(AGENTS.md: E2E with real imports), not mocks of the seam under test.
"""
import pytest

import hermes_cli.config as hc
from tools.approval import (
    _get_approval_config,
    _get_approval_mode,
    _get_cron_approval_mode,
    check_all_command_guards,
    load_permanent_allowlist,
)
from tools.tirith_security import _load_security_config


@pytest.fixture
def config_home(tmp_path, monkeypatch):
    home = tmp_path / "hermes"
    home.mkdir()
    (home / "config.yaml").write_text(
        "model:\n  default: test-model\n"
        "approvals:\n  mode: manual\n  timeout: 300\n  cron_mode: deny\n"
        "command_allowlist: []\n"
        "security:\n  tirith_enabled: false\n"
    )
    monkeypatch.setenv("HERMES_HOME", str(home))
    hc._LOAD_CONFIG_CACHE.clear()
    yield home
    hc._LOAD_CONFIG_CACHE.clear()


def _patched_loaders(monkeypatch):
    """Count BOTH loader variants. (A boom on load_config is useless here —
    every call site wraps the load in try/except and would swallow it; a
    pass-through counter is the robust form. The pins are: legacy
    load_config == 0 calls, load_config_readonly == the expected count,
    and cache identity — none satisfiable by the pre-fix code.)"""
    calls = {"readonly": 0, "legacy": 0}

    real_ro = hc.load_config_readonly
    real_legacy = hc.load_config

    def counting_ro():
        calls["readonly"] += 1
        return real_ro()

    def counting_legacy():
        calls["legacy"] += 1
        return real_legacy()

    monkeypatch.setattr(hc, "load_config_readonly", counting_ro)
    monkeypatch.setattr(hc, "load_config", counting_legacy)
    return calls


def test_guard_never_calls_deepcopy_variant(config_home, monkeypatch):
    """Pin: a full guard pass must not pay one deepcopying load_config.
    Fails pre-fix (the guard called load_config 2x per invocation)."""
    calls = _patched_loaders(monkeypatch)
    check_all_command_guards("ls -la", "local")
    assert calls["legacy"] == 0, (
        f"guard path called deepcopying load_config "
        f"{calls['legacy']}x — regression reintroduces the deepcopy cost")
    assert calls["readonly"] >= 1


def test_config_readers_never_call_deepcopy_variant(config_home, monkeypatch):
    calls = _patched_loaders(monkeypatch)
    assert _get_approval_mode() == "manual"
    assert _get_approval_config().get("timeout") == 300
    assert _get_cron_approval_mode() == "deny"
    assert load_permanent_allowlist() == set()
    sec = _load_security_config()
    assert sec["tirith_enabled"] is False
    assert calls["legacy"] == 0
    assert calls["readonly"] == 5  # one readonly load per function


def test_readers_return_live_cache_without_corrupting_it(
        config_home, monkeypatch):
    """Guard-population check for the readonly swap: repeated reads return
    the same cached object and the cache stays intact — no swapped site
    may mutate what it returns."""
    first = _get_approval_config()
    second = _get_approval_config()
    assert first is second  # live cache object, no deepcopy
    # a full guard pass must leave the cache values untouched
    before = dict(first)
    check_all_command_guards("ls -la", "local")
    _get_cron_approval_mode()
    load_permanent_allowlist()
    _load_security_config()
    assert _get_approval_config() == before
    assert hc.load_config_readonly()["approvals"]["mode"] == "manual"
