"""Tests for the sandbox-mirror write guard in agent/file_safety.

The guard fires when a tool tries to write into the per-task mirror
directory created by a non-local terminal backend (Docker, Daytona, etc.).
Those paths look like ``…/sandboxes/<backend>/<task>/home/.hermes/…`` and
they accumulate divergent copies of authoritative profile state (SOUL.md,
config.yaml, memories/*.md) because the host Hermes process never reads
them. Soft guard — defense in depth, NOT a security boundary.

Reference: #32049 — under ``terminal.backend: docker``, the agent's
``write_file`` / ``patch`` calls landed on the sandbox mirror of SOUL.md
while the host process kept loading the untouched authoritative file.
The agent reported success; the rule never took effect.
"""
from __future__ import annotations

from pathlib import Path

import pytest


# ---------------------------------------------------------------------------
# classify_sandbox_mirror_target — pure path-shape detection
# ---------------------------------------------------------------------------


class TestClassifySandboxMirrorTarget:
    def test_docker_mirror_soul_md_classified(self, tmp_path):
        """The exact path shape reported in #32049."""
        from agent.file_safety import classify_sandbox_mirror_target

        target = (
            tmp_path
            / "profiles" / "group1"
            / "sandboxes" / "docker" / "default" / "home" / ".hermes"
            / "profiles" / "group1" / "SOUL.md"
        )
        target.parent.mkdir(parents=True)
        target.write_text("# mirror copy\n")

        result = classify_sandbox_mirror_target(str(target))
        assert result is not None
        assert result["target_path"] == str(target.resolve())
        assert result["mirror_root"].endswith(
            "sandboxes/docker/default/home/.hermes"
        )
        assert result["inner_path"] == "profiles/group1/SOUL.md"

    @pytest.mark.parametrize(
        "backend,inner",
        [
            ("docker", "profiles/coder/memories/MEMORY.md"),
            ("daytona", "profiles/default/cron/jobs.json"),
            ("podman", ".env"),
        ],
    )
    def test_other_backends_and_inner_files_match(self, tmp_path, backend, inner):
        """The detector is backend-agnostic — sandbox-mirror shape is what matters."""
        from agent.file_safety import classify_sandbox_mirror_target

        target = (
            tmp_path
            / "sandboxes" / backend / "task-42" / "home" / ".hermes"
            / Path(inner)
        )
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text("x")

        result = classify_sandbox_mirror_target(str(target))
        assert result is not None
        assert result["inner_path"] == inner
        assert backend in result["mirror_root"]

    def test_path_outside_sandbox_returns_none(self, tmp_path):
        """A plain Hermes path is not a mirror."""
        from agent.file_safety import classify_sandbox_mirror_target

        target = tmp_path / ".hermes" / "profiles" / "group1" / "SOUL.md"
        target.parent.mkdir(parents=True)
        target.write_text("# real SOUL\n")

        assert classify_sandbox_mirror_target(str(target)) is None

    def test_sandboxes_segment_without_home_hermes_returns_none(self, tmp_path):
        """A ``sandboxes/`` directory unrelated to Hermes-state mirroring (e.g.
        the sandbox workspace itself) is not flagged."""
        from agent.file_safety import classify_sandbox_mirror_target

        target = (
            tmp_path
            / "sandboxes" / "docker" / "task-42" / "workspace" / "main.py"
        )
        target.parent.mkdir(parents=True)
        target.write_text("print('hi')\n")

        assert classify_sandbox_mirror_target(str(target)) is None

    def test_sandboxes_segment_with_home_but_no_hermes_returns_none(self, tmp_path):
        """``sandboxes/<backend>/<task>/home/anything-not-hermes`` is not a mirror."""
        from agent.file_safety import classify_sandbox_mirror_target

        target = (
            tmp_path
            / "sandboxes" / "docker" / "task-42" / "home" / ".bashrc"
        )
        target.parent.mkdir(parents=True)
        target.write_text("alias ll='ls -la'\n")

        assert classify_sandbox_mirror_target(str(target)) is None

    def test_truncated_sandbox_path_returns_none(self, tmp_path):
        """``…/sandboxes/<backend>/<task>`` without ``home/.hermes/<thing>`` is not a mirror."""
        from agent.file_safety import classify_sandbox_mirror_target

        target = tmp_path / "sandboxes" / "docker" / "task-42"
        target.mkdir(parents=True)

        assert classify_sandbox_mirror_target(str(target)) is None

    def test_non_existent_path_still_classifies_by_shape(self, tmp_path):
        """Detection is path-shape only — it must not require the file to exist
        (the agent is about to CREATE the mirror file, that's the bug)."""
        from agent.file_safety import classify_sandbox_mirror_target

        target = (
            tmp_path
            / "profiles" / "group1"
            / "sandboxes" / "docker" / "default" / "home" / ".hermes"
            / "profiles" / "group1" / "SOUL.md"
        )
        # Parent directory exists so .resolve() doesn't strip the tail
        # under strict mode, but the file itself does NOT exist.
        target.parent.mkdir(parents=True)
        assert not target.exists()

        result = classify_sandbox_mirror_target(str(target))
        assert result is not None
        assert result["inner_path"] == "profiles/group1/SOUL.md"


# ---------------------------------------------------------------------------
# get_sandbox_mirror_warning — the model-facing string
# ---------------------------------------------------------------------------


class TestGetSandboxMirrorWarning:
    def test_non_mirror_returns_none(self, tmp_path):
        from agent.file_safety import get_sandbox_mirror_warning

        target = tmp_path / ".hermes" / "profiles" / "group1" / "SOUL.md"
        target.parent.mkdir(parents=True)
        target.write_text("# real SOUL\n")

        assert get_sandbox_mirror_warning(str(target)) is None

    def test_mirror_warning_names_mirror_root_and_inner_path(self, tmp_path):
        from agent.file_safety import get_sandbox_mirror_warning

        target = (
            tmp_path
            / "profiles" / "group1"
            / "sandboxes" / "docker" / "default" / "home" / ".hermes"
            / "profiles" / "group1" / "SOUL.md"
        )
        target.parent.mkdir(parents=True)
        target.write_text("# mirror copy\n")

        warn = get_sandbox_mirror_warning(str(target))
        assert warn is not None
        # Must name the mirror root so the user can locate the sandbox.
        assert "sandboxes/docker/default/home/.hermes" in warn
        # Must hint at what the agent likely meant.
        assert "profiles/group1/SOUL.md" in warn
        # Must name the bypass kwarg shared with the cross-profile guard.
        assert "cross_profile=True" in warn

    def test_warning_is_defense_in_depth_not_boundary(self, tmp_path):
        from agent.file_safety import get_sandbox_mirror_warning

        target = (
            tmp_path
            / "sandboxes" / "docker" / "t" / "home" / ".hermes"
            / "profiles" / "g" / "SOUL.md"
        )
        target.parent.mkdir(parents=True)
        target.write_text("x")

        warn = get_sandbox_mirror_warning(str(target))
        # Must self-document as defense-in-depth so future reviewers
        # don't promote it to a hard block (matches the existing
        # cross-profile guard's contract).
        assert "not a security boundary" in warn.lower()


# ---------------------------------------------------------------------------
# Independence from cross-profile classifier
# ---------------------------------------------------------------------------


class TestSandboxMirrorIsOrthogonalToCrossProfile:
    """The sandbox-mirror guard must fire even when the inner path is
    in-profile from the host's view — the bug is the mirror, not the
    profile mismatch."""

    def test_same_profile_mirror_still_flagged(self, tmp_path, monkeypatch):
        import agent.file_safety as fs
        monkeypatch.setattr(fs, "_hermes_root_path", lambda: tmp_path)
        monkeypatch.setattr(fs, "_hermes_home_path", lambda: tmp_path / "profiles" / "group1")

        target = (
            tmp_path
            / "profiles" / "group1"
            / "sandboxes" / "docker" / "default" / "home" / ".hermes"
            / "profiles" / "group1" / "SOUL.md"
        )
        target.parent.mkdir(parents=True)
        target.write_text("x")

        # cross-profile classifier: active profile == target's inner-mirror
        # profile name; on the existing detector the path's parts[2] is
        # ``sandboxes``, not a scoped area, so it returns None.
        assert fs.classify_cross_profile_target(str(target)) is None
        # sandbox-mirror classifier: fires unconditionally on the shape.
        assert fs.classify_sandbox_mirror_target(str(target)) is not None
