"""Dashboard routers that read a named profile's config/credentials run under that profile's FULL
scope (home + secrets) and never mutate the dashboard process environment.

Regression for the cross-profile leak class in ``hermes dashboard`` / ``hermes serve``:
``_config_profile_scope`` bound only HERMES_HOME, so ``GET /api/config?profile=B`` expanded B's
``${VAR}`` refs to the DEFAULT profile's plaintext credentials (its ``os.environ``), and console
``send`` for B (``send_cmd._load_hermes_env``) copied B's ``.env`` into the shared process env with
``override=True``, so every later default-profile read saw B's tokens.
"""

from __future__ import annotations

import os
from pathlib import Path

import pytest

pytest.importorskip("fastapi")
from starlette.testclient import TestClient  # noqa: E402

A_VAL = "a-only-secret-0001"
B_VAL = "b-only-secret-0002"


@pytest.fixture
def two_homes(tmp_path, monkeypatch):
    root = tmp_path / "hermes_home"
    b = root / "profiles" / "b"
    b.mkdir(parents=True)
    (root / ".env").write_text(f"A_ONLY_TOKEN={A_VAL}\n", encoding="utf-8")
    (b / ".env").write_text(f"B_ONLY_TOKEN={B_VAL}\nTELEGRAM_BOT_TOKEN=b-telegram-token\n", encoding="utf-8")
    for home in (root, b):
        (home / "config.yaml").write_text(
            "model:\n  default: openai/gpt-4o-mini\n  api_key: ${A_ONLY_TOKEN}\n"
            "custom_probe:\n  a_ref: ${A_ONLY_TOKEN}\n  b_ref: ${B_ONLY_TOKEN}\n",
            encoding="utf-8")
    monkeypatch.setenv("HERMES_HOME", str(root))
    monkeypatch.setenv("A_ONLY_TOKEN", A_VAL)  # the dashboard process loaded its own .env
    monkeypatch.delenv("TELEGRAM_BOT_TOKEN", raising=False)
    from agent import secret_scope
    from tui_gateway import launch_profile_policy as lpp
    monkeypatch.setattr(secret_scope, "_MULTIPLEX_ACTIVE", False)
    monkeypatch.setattr(lpp, "_snapshot", None)
    from hermes_cli import config as cfg_mod
    for attr in ("_CONFIG_CACHE", "_config_cache"):
        if hasattr(cfg_mod, attr):
            monkeypatch.setattr(cfg_mod, attr, None if not isinstance(getattr(cfg_mod, attr), dict) else {})
    return root, b


@pytest.fixture
def client(two_homes):
    from hermes_cli.web_server import _SESSION_HEADER_NAME, _SESSION_TOKEN, app
    c = TestClient(app)
    c.headers[_SESSION_HEADER_NAME] = _SESSION_TOKEN
    return c


def test_get_config_for_named_profile_expands_only_its_own_secrets(client, two_homes):
    from agent.secret_scope import is_multiplex_active

    resp = client.get("/api/config?profile=b")
    assert resp.status_code == 200, resp.text
    probe = resp.json()["custom_probe"]
    assert probe["b_ref"] == B_VAL
    assert probe["a_ref"] == "${A_ONLY_TOKEN}"  # not the dashboard profile's value
    # Hosting a second profile flipped the process to fail-closed; environ is untouched.
    assert is_multiplex_active()
    assert os.environ["A_ONLY_TOKEN"] == A_VAL and "B_ONLY_TOKEN" not in os.environ

    # The dashboard's own profile still resolves its own value (frozen launch env).
    probe_a = client.get("/api/config").json()["custom_probe"]
    assert probe_a["a_ref"] == A_VAL and probe_a["b_ref"] == "${B_ONLY_TOKEN}"


def test_console_send_for_named_profile_does_not_write_process_env(two_homes, monkeypatch):
    """``send`` loads the target profile's ``.env`` for the gateway config loader; inside a
    multi-profile host that must land in the request's scope, never ``os.environ``."""
    from hermes_cli.web_routers.chat_ws import _execute_console_line

    root, b = two_homes
    seen = {}

    def fake_send(args):
        import hermes_cli.send_cmd as send_cmd
        from gateway.config import _getenv
        send_cmd._load_hermes_env()
        seen["loader_sees"] = _getenv("TELEGRAM_BOT_TOKEN")
        seen["environ_has"] = "TELEGRAM_BOT_TOKEN" in os.environ
        seen["home"] = Path(os.environ.get("HERMES_HOME", ""))
        return '{"success": true}'

    class Engine:
        def execute(self, line, *, confirmed=False):
            import argparse
            return fake_send(argparse.Namespace())

    _execute_console_line(Engine(), "send --to telegram hi", confirmed=False, profile="b")
    assert seen["loader_sees"] == "b-telegram-token"  # the loader gets B's token through the scope
    assert seen["environ_has"] is False  # and the dashboard process env never learns it
    assert "B_ONLY_TOKEN" not in os.environ
