"""Tests for the bash execution sandbox (_sandbox_check).

The sandbox is the last-resort hard block for catastrophic commands
(rm -rf /, mkfs, fork bombs). It runs AFTER the approval system and
blocks commands even in YOLO mode.

These tests verify:
- Catastrophic commands are always blocked
- Display commands (echo/printf) with dangerous strings as TEXT are allowed
- Display commands with shell expansion are still blocked
- Display commands piped to shell interpreters are still blocked
- Sequential operators (&&, ||, ;) are split correctly
"""

import pytest

from autodebug.agent.langgraph_runner import _sandbox_check


# ---------------------------------------------------------------------------
# Catastrophic commands  -  always blocked
# ---------------------------------------------------------------------------


class TestSandboxBlocks:
    """Verify catastrophic commands are hard-blocked."""

    def test_rm_rf_root(self):
        blocked, desc = _sandbox_check("rm -rf /")
        assert blocked is True
        assert "root" in desc

    def test_rm_rf_home(self):
        blocked, _ = _sandbox_check("rm -rf ~")
        assert blocked is True

    def test_rm_rf_dot(self):
        blocked, _ = _sandbox_check("rm -rf .")
        assert blocked is True

    def test_rm_rf_root_in_parens(self):
        """rm -rf /)  -  pattern must match / followed by )."""
        blocked, _ = _sandbox_check("rm -rf /)")
        assert blocked is True

    def test_rm_rf_root_in_quotes(self):
        """rm -rf /"  -  pattern must match / followed by quote."""
        blocked, _ = _sandbox_check('rm -rf /"')
        assert blocked is True

    def test_rm_rf_root_in_single_quotes(self):
        blocked, _ = _sandbox_check("rm -rf /'")
        assert blocked is True

    def test_rm_rf_root_in_backtick(self):
        blocked, _ = _sandbox_check("rm -rf /`")
        assert blocked is True

    def test_fork_bomb(self):
        blocked, _ = _sandbox_check(":(){ :|:& };:")
        assert blocked is True

    def test_mkfs(self):
        blocked, _ = _sandbox_check("mkfs.ext4 /dev/sda1")
        assert blocked is True

    def test_dd_to_device(self):
        blocked, _ = _sandbox_check("dd if=/dev/zero of=/dev/sda")
        assert blocked is True

    def test_redirect_to_device(self):
        blocked, _ = _sandbox_check("> /dev/sda")
        assert blocked is True

    def test_safe_command_not_blocked(self):
        blocked, _ = _sandbox_check("ls -la")
        assert blocked is False

    def test_git_status_not_blocked(self):
        blocked, _ = _sandbox_check("git status")
        assert blocked is False


# ---------------------------------------------------------------------------
# Display command exemption  -  the h6/h7/h8/h9 false positive fix
# ---------------------------------------------------------------------------


class TestSandboxDisplayExemption:
    """Echo/printf with dangerous strings as TEXT should not be blocked."""

    def test_echo_rm_string(self):
        """The h9 false positive: echo with rm -rf / as display text."""
        blocked, _ = _sandbox_check('echo "rm -rf / -> dangerous=True"')
        assert blocked is False

    def test_echo_multiword_report(self):
        """Echo displaying test results mentioning dangerous commands."""
        blocked, _ = _sandbox_check(
            'echo "[ok] time rm -rf / detected as dangerous"'
        )
        assert blocked is False

    def test_printf_rm_string(self):
        blocked, _ = _sandbox_check('printf "rm -rf / was blocked\\n"')
        assert blocked is False

    def test_echo_mkfs_string(self):
        blocked, _ = _sandbox_check('echo "mkfs.ext4 is always blocked"')
        assert blocked is False

    def test_echo_dd_string(self):
        blocked, _ = _sandbox_check('echo "dd if=/dev/zero of=/dev/sda blocked"')
        assert blocked is False

    def test_cat_heredoc_rm_string(self):
        """The h12 false positive: cat heredoc with rm -rf / as text data."""
        blocked, _ = _sandbox_check("cat << 'EOF'\nrm -rf /\nEOF")
        assert blocked is False

    def test_cat_heredoc_mkfs_string(self):
        blocked, _ = _sandbox_check("cat << 'EOF'\nmkfs.ext4 /dev/sda\nEOF")
        assert blocked is False

    def test_cat_file_not_blocked(self):
        blocked, _ = _sandbox_check("cat file.txt")
        assert blocked is False


# ---------------------------------------------------------------------------
# Display + shell expansion  -  still blocked
# ---------------------------------------------------------------------------


class TestSandboxDisplayWithExpansion:
    """Echo/printf with shell expansion should NOT get display exemption.

    When expansion chars are present, the display exemption is revoked and
    the pattern check runs. Whether the patterns actually MATCH depends on
    the pattern syntax (e.g. rm pattern requires / followed by space or EOL,
    so rm -rf /) won't match because / is followed by ')').

    The key invariant: display exemption is DISABLED, so patterns get a
    chance to match. This tests that the exemption logic is correct.
    """

    def test_echo_expansion_not_exempted(self):
        """echo $(rm -rf /)  -  exemption revoked AND pattern now matches /)."""
        blocked, _ = _sandbox_check("echo $(rm -rf /)")
        assert blocked is True

    def test_echo_expansion_with_space(self):
        blocked, _ = _sandbox_check("echo $(rm -rf / )")
        assert blocked is True

    def test_echo_backtick_not_exempted(self):
        blocked, _ = _sandbox_check("echo `rm -rf /`")
        assert blocked is True

    def test_echo_expansion_mkfs(self):
        """mkfs pattern uses \\b word boundary  -  matches inside $()."""
        blocked, _ = _sandbox_check("echo $(mkfs.ext4 /dev/sda)")
        assert blocked is True

    def test_echo_expansion_dd(self):
        blocked, _ = _sandbox_check("echo $(dd if=/dev/zero of=/dev/sda)")
        assert blocked is True

    def test_printf_expansion_not_exempted(self):
        blocked, _ = _sandbox_check("printf $(mkfs /dev/sda)")
        assert blocked is True

    def test_cat_expansion_not_exempted(self):
        """cat <(rm -rf /)  -  process substitution revokes display exemption."""
        blocked, _ = _sandbox_check("cat <(rm -rf / )")
        assert blocked is True


# ---------------------------------------------------------------------------
# Display + pipe to shell  -  still blocked
# ---------------------------------------------------------------------------


class TestSandboxDisplayPipeToShell:
    """Echo piped to shell interpreter should NOT get display exemption.

    The pipe-to-shell check revokes the display exemption so patterns
    get a chance to run. Whether they actually match depends on the
    pattern syntax (rm pattern needs / followed by space/EOL).
    """

    def test_echo_pipe_sh_not_exempted(self):
        """Pipe to sh revokes display exemption  -  pattern matches unquoted form."""
        blocked, _ = _sandbox_check("echo rm -rf / | sh")
        assert blocked is True

    def test_echo_pipe_bash_not_exempted(self):
        blocked, _ = _sandbox_check("echo rm -rf / | bash")
        assert blocked is True

    def test_echo_pipe_zsh_not_exempted(self):
        blocked, _ = _sandbox_check("echo rm -rf / | zsh")
        assert blocked is True

    def test_echo_pipe_sh_mkfs(self):
        """mkfs pattern uses \\b  -  matches regardless of quoting."""
        blocked, _ = _sandbox_check('echo "mkfs.ext4 /dev/sda" | sh')
        assert blocked is True

    def test_echo_pipe_grep_ok(self):
        """Pipe to non-shell command is fine  -  grep can't execute."""
        blocked, _ = _sandbox_check('echo "rm -rf /" | grep rm')
        assert blocked is False

    def test_echo_pipe_wc_ok(self):
        blocked, _ = _sandbox_check('echo "rm -rf /" | wc -l')
        assert blocked is False

    def test_cat_pipe_bash_not_exempted(self):
        """cat heredoc piped to bash  -  revokes display exemption."""
        blocked, _ = _sandbox_check("cat << 'EOF'\nrm -rf /\nEOF | bash")
        assert blocked is True

    def test_cat_pipe_grep_ok(self):
        """cat piped to grep  -  safe, grep can't execute."""
        blocked, _ = _sandbox_check("cat << 'EOF'\nrm -rf /\nEOF | grep rm")
        assert blocked is False

    def test_printf_pipe_sh_blocked(self):
        """printf with escaped content piped to sh  -  blocked directly.

        The printf content may contain escapes (like \\n) that prevent
        DANGEROUS_PATTERNS from matching, but pipe-to-shell on a display
        command is always blocked.
        """
        blocked, reason = _sandbox_check('printf "rm -rf /\\n" | sh')
        assert blocked is True
        assert "pipe" in reason.lower() or "shell" in reason.lower()

    def test_cat_script_pipe_bash_blocked(self):
        """cat of a script file piped to bash  -  blocked directly."""
        blocked, reason = _sandbox_check("cat script.sh | bash")
        assert blocked is True

    def test_echo_harmless_pipe_sh_blocked(self):
        """Even harmless-looking echo piped to sh is blocked.

        Any display command piped to a shell interpreter is inherently
        dangerous  -  the content could be anything.
        """
        blocked, _ = _sandbox_check('echo "ls" | sh')
        assert blocked is True


# ---------------------------------------------------------------------------
# Sequential operators  -  split correctly
# ---------------------------------------------------------------------------


class TestSandboxSequentialOperators:
    """Commands joined by &&, ||, ; should be checked independently."""

    def test_echo_then_rm(self):
        """echo is display (skip), but rm -rf / is dangerous (block)."""
        blocked, _ = _sandbox_check('echo "safe" && rm -rf /')
        assert blocked is True

    def test_echo_or_rm(self):
        blocked, _ = _sandbox_check('echo "safe" || rm -rf /')
        assert blocked is True

    def test_echo_semicolon_rm(self):
        blocked, _ = _sandbox_check('echo "safe"; rm -rf /')
        assert blocked is True

    def test_echo_then_echo_rm_text(self):
        """Both sub-commands are echo display  -  skip both."""
        blocked, _ = _sandbox_check(
            'echo "category 1" && echo "rm -rf / -> dangerous"'
        )
        assert blocked is False

    def test_rm_then_echo(self):
        """rm comes first  -  blocked before echo is even checked."""
        blocked, _ = _sandbox_check('rm -rf / && echo "done"')
        assert blocked is True

    def test_ls_then_echo_rm_text(self):
        """ls is not display-exempt but also not dangerous. echo is display."""
        blocked, _ = _sandbox_check('ls && echo "rm -rf / detected"')
        assert blocked is False


# ---------------------------------------------------------------------------
# Non-display commands with dangerous strings  -  still blocked
# ---------------------------------------------------------------------------


class TestSandboxNonDisplayStillBlocked:
    """Python, and other non-display commands stay blocked."""

    def test_python_heredoc_rm(self):
        """python3 heredoc with rm  -  python IS an interpreter, stays blocked."""
        blocked, _ = _sandbox_check("python3 << 'EOF'\nimport os; os.system(\"rm -rf /\")\nEOF")
        assert blocked is True

    def test_python_c_rm_string(self):
        """python -c with rm in string  -  python not in display set.

        Pattern needs / followed by space/EOL. Unquoted form matches.
        """
        blocked, _ = _sandbox_check("python -c 'import os; os.system(\"rm -rf / \")'")
        assert blocked is True

    def test_python_c_mkfs(self):
        """mkfs pattern uses word boundary  -  matches inside any string."""
        blocked, _ = _sandbox_check("python -c 'import os; os.system(\"mkfs /dev/sda\")'")
        assert blocked is True

    def test_time_rm(self):
        """Wrapper commands are not display  -  still blocked."""
        blocked, _ = _sandbox_check("time rm -rf /")
        assert blocked is True

    def test_sudo_rm(self):
        blocked, _ = _sandbox_check("sudo rm -rf /")
        assert blocked is True


# ---------------------------------------------------------------------------
# Transcript regression tests  -  exact commands from h9-h12 dogfood sessions
# ---------------------------------------------------------------------------


class TestTranscriptRegression:
    """Regression tests from actual hikaflow dogfood transcripts.

    Each test reproduces an exact command the agent tried in a real session.
    These ensure fixes don't regress as we iterate.
    """

    # -- h9: echo display exemption (was blocked, should be allowed) --

    def test_h9_echo_rm_report(self):
        """h9 false positive: echo reporting test results with rm text."""
        blocked, _ = _sandbox_check('echo "rm -rf / -> dangerous=True"')
        assert blocked is False

    def test_h9_echo_multiline_report(self):
        """h9: echo reporting multiple test results."""
        blocked, _ = _sandbox_check(
            'echo "[ok] time rm -rf / detected as dangerous"'
        )
        assert blocked is False

    # -- h10: echo $(rm -rf /) expansion (was missed, should be blocked) --

    def test_h10_echo_expansion_rm(self):
        """h10 finding: echo $(rm -rf /) must be blocked (expansion executes)."""
        blocked, _ = _sandbox_check("echo $(rm -rf /)")
        assert blocked is True

    # -- h11: python -c with dangerous strings (correctly blocked) --

    def test_h11_python_c_classify_with_space(self):
        """h11: agent tried python -c with dangerous strings. Blocked when
        the rm pattern has a trailing space (common in real agent commands)."""
        blocked, _ = _sandbox_check(
            "python -c 'import os; os.system(\"rm -rf / \")'"
        )
        assert blocked is True

    def test_h11_python_c_classify_mkfs(self):
        """h11: mkfs pattern uses word boundary  -  always matches in python -c."""
        blocked, _ = _sandbox_check(
            'python -c "import os; os.system(\\"mkfs /dev/sda\\")"'
        )
        assert blocked is True

    def test_h11_python_c_safe_input(self):
        """h11 fix: python -c with SAFE inputs should not be blocked."""
        blocked, _ = _sandbox_check(
            'python -c "from autodebug.agent.approval import _is_safe_bash_command; '
            'print(_is_safe_bash_command(\\"ls -la\\"))"'
        )
        assert blocked is False

    # -- h12: cat heredoc (was blocked, should be allowed) --

    def test_h12_cat_heredoc_test_script(self):
        """h12 false positive: cat heredoc creating a test script."""
        blocked, _ = _sandbox_check(
            "cat << 'EOF' > /tmp/test_tiers.py\n"
            "from approval import _is_dangerous\n"
            "print(_is_dangerous('rm -rf /'))\n"
            "EOF"
        )
        assert blocked is False

    def test_h12_python_heredoc_correctly_blocked(self):
        """h12 true positive: python3 heredoc with dangerous code."""
        blocked, _ = _sandbox_check(
            "python3 << 'TESTEOF'\n"
            "import os; os.system('rm -rf /')\n"
            "TESTEOF"
        )
        assert blocked is True

    # -- h12: test suite run (should never be blocked) --

    def test_h12_pytest_run(self):
        """h12: running the test suite should never be sandbox-blocked."""
        blocked, _ = _sandbox_check(
            "python -m pytest tests/test_approval.py tests/test_sandbox.py -v --tb=short"
        )
        assert blocked is False

    # -- Cross-session: echo + safe python -c combined (the recommended pattern) --

    def test_recommended_echo_then_safe_python(self):
        """The verify_claims nudge recommends this pattern."""
        blocked, _ = _sandbox_check(
            'echo "find -exec: not in SAFE list -> gets ASK_USER approval"'
        )
        assert blocked is False


# ---------------------------------------------------------------------------
# Fuzz/Adversarial: sandbox rm pattern hardening
# ---------------------------------------------------------------------------


class TestSandboxRmHardening:
    """Tests for rm pattern bypasses found by fuzzing and adversarial testing.

    Covers: path normalization (S3), flag splitting (S4), glob/brace (S5),
    and additional variants from fuzz findings H-2 through H-11.
    """

    # -- Path normalization (S3, H-2, H-5) --

    def test_rm_double_slash_root(self):
        """H-2/S3: rm -rf // resolves to root."""
        blocked, _ = _sandbox_check("rm -rf //")
        assert blocked is True

    def test_rm_dot_path(self):
        """S3: rm -rf /./ resolves to root."""
        blocked, _ = _sandbox_check("rm -rf /./")
        assert blocked is True

    def test_rm_parent_traversal(self):
        """S3: rm -rf /../.. resolves to root."""
        blocked, _ = _sandbox_check("rm -rf /../..")
        assert blocked is True

    def test_rm_tmp_traversal(self):
        """S3: rm -rf /tmp/../ traverses out of tmp to root."""
        blocked, _ = _sandbox_check("rm -rf /tmp/../")
        assert blocked is True

    # -- Flag splitting (S4, H-3, H-4, H-7) --

    def test_rm_split_flags(self):
        """S4: rm -r -f / with split flags."""
        blocked, _ = _sandbox_check("rm -r -f /")
        assert blocked is True

    def test_rm_long_flags(self):
        """H-7/S4: rm --recursive --force /."""
        blocked, _ = _sandbox_check("rm --recursive --force /")
        assert blocked is True

    def test_rm_r_without_f(self):
        """H-4: rm -r / without -f flag."""
        blocked, _ = _sandbox_check("rm -r /")
        assert blocked is True

    def test_rm_option_separator(self):
        """H-3: rm -rf -- / with option separator."""
        blocked, _ = _sandbox_check("rm -rf -- /")
        assert blocked is True

    # -- Glob and brace expansion (S5, H-8) --

    def test_rm_glob_star_root(self):
        """H-8/S5: rm -rf /* equivalent to rm -rf /."""
        blocked, _ = _sandbox_check("rm -rf /*")
        assert blocked is True

    def test_rm_brace_expansion(self):
        """S5: rm -rf /{bin,etc,usr} targets root dirs."""
        blocked, _ = _sandbox_check("rm -rf /{bin,etc,usr}")
        assert blocked is True

    # -- Home directory variants (H-9, H-10) --

    def test_rm_r_home_without_f(self):
        """H-9: rm -r ~ without -f flag."""
        blocked, _ = _sandbox_check("rm -r ~")
        assert blocked is True

    def test_rm_home_env_var(self):
        """H-10: rm -rf $HOME."""
        blocked, _ = _sandbox_check("rm -rf $HOME")
        assert blocked is True

    def test_rm_home_env_var_braces(self):
        """H-10: rm -rf ${HOME}."""
        blocked, _ = _sandbox_check("rm -rf ${HOME}")
        assert blocked is True

    # -- Additional missing patterns --

    def test_mke2fs_blocked(self):
        """S11: mke2fs alternative to mkfs."""
        blocked, _ = _sandbox_check("mke2fs /dev/sda1")
        assert blocked is True

    def test_mkswap_blocked(self):
        """S11: mkswap creates swap partition."""
        blocked, _ = _sandbox_check("mkswap /dev/sda2")
        assert blocked is True

    def test_nvme_device_write(self):
        """H-12: NVMe devices not caught by original sd[a-z] pattern."""
        blocked, _ = _sandbox_check("> /dev/nvme0")
        assert blocked is True

    # -- Named fork bomb (S10) --

    def test_named_fork_bomb(self):
        """S10: Named function fork bomb evades :(){ pattern."""
        blocked, _ = _sandbox_check("bomb(){ bomb|bomb& };bomb")
        assert blocked is True

    # -- Display exemption for grep (M-2) --

    def test_grep_display_exemption(self):
        """M-2: grep for dangerous strings should not be blocked."""
        blocked, _ = _sandbox_check('grep "rm -rf /" script.sh')
        assert blocked is False

    # -- Pipe to scheduler (S6) --

    def test_echo_pipe_to_at(self):
        """S6: echo dangerous command piped to at scheduler."""
        blocked, _ = _sandbox_check('echo "rm -rf /" | at now')
        assert blocked is True

    def test_echo_pipe_to_crontab(self):
        """S6: echo piped to crontab."""
        blocked, _ = _sandbox_check('echo "* * * * * rm -rf /" | crontab')
        assert blocked is True

    # -- Ensure safe commands still work --

    def test_safe_commands_not_blocked(self):
        """Sanity check: normal commands should not be blocked."""
        assert _sandbox_check("ls -la")[0] is False
        assert _sandbox_check("git status")[0] is False
        assert _sandbox_check("pytest tests/")[0] is False
        assert _sandbox_check("cat file.txt")[0] is False
        assert _sandbox_check("grep pattern file.txt")[0] is False


# ---------------------------------------------------------------------------
# S8: Newline splitting in sandbox (heredoc-aware)
# ---------------------------------------------------------------------------


class TestSandboxNewlineSplitting:
    """S8: Real newlines should split sub-commands in the sandbox.

    Without this, `echo ok\\nrm -rf /` (real newline) hides `rm -rf /`
    inside the same sub-command string, and the display exemption for
    `echo` causes the sandbox to skip the entire string.

    However, heredocs (`<<`) use newlines as content, not command
    separators. If the command contains `<<`, newlines are NOT split.
    """

    def test_newline_rm_blocked(self):
        """Real newline hides rm -rf / after echo  -  must be blocked."""
        blocked, _ = _sandbox_check("echo ok\nrm -rf /")
        assert blocked is True

    def test_newline_mkfs_blocked(self):
        """Real newline hides mkfs after safe command."""
        blocked, _ = _sandbox_check("ls -la\nmkfs /dev/sda")
        assert blocked is True

    def test_newline_dd_blocked(self):
        """Real newline hides dd after safe command."""
        blocked, _ = _sandbox_check("echo safe\ndd if=/dev/zero of=/dev/sda")
        assert blocked is True

    def test_newline_fork_bomb_blocked(self):
        """Real newline hides fork bomb."""
        blocked, _ = _sandbox_check("echo ok\n:(){ :|:& };:")
        assert blocked is True

    def test_heredoc_not_split_on_newline(self):
        """Heredoc content uses newlines as data  -  should NOT split."""
        blocked, _ = _sandbox_check("cat << 'EOF'\nrm -rf /\nEOF")
        assert blocked is False

    def test_heredoc_mkfs_not_split(self):
        """Heredoc with mkfs as text data  -  should NOT split."""
        blocked, _ = _sandbox_check("cat << 'EOF'\nmkfs /dev/sda\nEOF")
        assert blocked is False

    def test_heredoc_pipe_bash_still_blocked(self):
        """Heredoc piped to bash  -  still blocked (pipe-to-shell check)."""
        blocked, _ = _sandbox_check("cat << 'EOF'\nrm -rf /\nEOF | bash")
        assert blocked is True

    def test_safe_newline_safe(self):
        """Two safe commands separated by newline  -  not blocked."""
        blocked, _ = _sandbox_check("ls -la\ngit status")
        assert blocked is False

    def test_echo_newline_echo(self):
        """Two echo commands separated by newline  -  not blocked."""
        blocked, _ = _sandbox_check("echo hello\necho world")
        assert blocked is False
