"""Tests for command execution safety (shell injection prevention)."""

import pytest
from rpipe.execution import (
    CommandExecutionError,
    validate_cwd,
    validate_env,
    validate_command_execution,
)


class TestCwdValidation:
    """Test that cwd validation prevents shell injection."""

    def test_valid_cwd_paths(self):
        """Valid cwd paths should not raise."""
        valid_paths = [
            None,  # None is valid
            "/tmp",
            "/var/log",
            "/home/user",
            "/path/with/multiple/slashes",
            "~/relative",
            ".",
            "..",
        ]
        for path in valid_paths:
            validate_cwd(path)  # Should not raise

    def test_cwd_quote_termination(self):
        """cwd with single quote must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_cwd("/tmp'; rm -rf /")

    def test_cwd_backtick_injection(self):
        """cwd with backticks must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_cwd("/tmp`whoami`")

    def test_cwd_dollar_paren_injection(self):
        """cwd with $() must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_cwd("/tmp$(whoami)")

    def test_cwd_escape_sequence(self):
        """cwd with backslash must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_cwd("/tmp\\'; ls")

    def test_cwd_semicolon_breakout(self):
        """cwd with semicolon must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_cwd("/tmp; rm -rf /")

    def test_cwd_pipe_injection(self):
        """cwd with pipe must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_cwd("/tmp | whoami")

    def test_cwd_redirect_injection(self):
        """cwd with > must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_cwd("/tmp > /tmp/evil")

    def test_cwd_ampersand_injection(self):
        """cwd with & must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_cwd("/tmp & whoami")

    def test_cwd_newline_injection(self):
        """cwd with newline must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_cwd("/tmp\nrm -rf /")


class TestEnvValidation:
    """Test that env validation prevents shell injection."""

    def test_valid_env(self):
        """Valid env values should not raise."""
        valid_envs = [
            None,  # None is valid
            {},  # Empty dict is valid
            {"VAR": "value"},
            {"PATH": "/usr/bin:/bin"},
            {"MULTIPLE": "values", "HERE": "too"},
            {"EQUALS": "key=value"},
            {"NUMBER": "123"},
        ]
        for env in valid_envs:
            validate_env(env)  # Should not raise

    def test_env_backtick_injection(self):
        """env with backticks must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_env({"VAR": "`id`"})

    def test_env_dollar_paren_injection(self):
        """env with $() must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_env({"VAR": "$(hostname)"})

    def test_env_quote_injection(self):
        """env with single quote must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_env({"VAR": "'; echo hacked"})

    def test_env_semicolon_injection(self):
        """env with semicolon must be rejected."""
        with pytest.raises(CommandExecutionError, match="forbidden character"):
            validate_env({"VAR": "value; rm -rf /"})

    def test_env_non_string_value(self):
        """env values must be strings."""
        with pytest.raises(CommandExecutionError, match="must be string"):
            validate_env({"VAR": 123})

        with pytest.raises(CommandExecutionError, match="must be string"):
            validate_env({"VAR": None})

        with pytest.raises(CommandExecutionError, match="must be string"):
            validate_env({"VAR": ["list"]})


class TestCommandExecutionValidation:
    """Test combined validation of cwd + env."""

    def test_valid_execution(self):
        """Valid cwd and env should not raise."""
        validate_command_execution(
            cwd="/var/log",
            env={"SEARCH": "error"},
        )

    def test_none_values(self):
        """None values should be valid."""
        validate_command_execution(cwd=None, env=None)

    def test_injection_in_cwd(self):
        """Injection in cwd should raise."""
        with pytest.raises(CommandExecutionError):
            validate_command_execution(
                cwd="/tmp$(whoami)",
                env={"SAFE": "value"},
            )

    def test_injection_in_env(self):
        """Injection in env should raise."""
        with pytest.raises(CommandExecutionError):
            validate_command_execution(
                cwd="/tmp",
                env={"UNSAFE": "`id`"},
            )

    def test_injection_in_both(self):
        """Injection in both should raise on cwd first."""
        with pytest.raises(CommandExecutionError):
            validate_command_execution(
                cwd="/tmp'; whoami",
                env={"VAR": "$(date)"},
            )


class TestFalsificationTests:
    """
    Explicit test cases corresponding to CLAUDE.md falsification tests.

    From CLAUDE.md § "Critical Security Issues":

    "Falsification tests (ALL must pass before v1):

    cwd="/tmp'; rm -rf /" → must NOT execute rm
    cwd="/tmp`whoami`" → must NOT execute whoami
    cwd="/tmp$(whoami)" → must NOT expand $()
    env="VAR=`id`" → must NOT execute id
    env="VAR=$(hostname)" → must NOT expand $()
    env="VAR=';echo hacked" → must NOT break out of string
    [and more...]"

    Each test below corresponds to a CLAUDE.md requirement.
    """

    @pytest.mark.parametrize(
        "unsafe_cwd",
        [
            "/tmp'; rm -rf /",  # Quote termination
            "/tmp`whoami`",  # Backtick injection
            "/tmp$(whoami)",  # Dollar-paren injection
            "/tmp\\'; ls",  # Escape sequence
            "/tmp; rm -rf /",  # Semicolon breakout
            "/tmp | whoami",  # Pipe injection
            "/tmp > /tmp/evil",  # Redirect injection
            "/tmp & whoami",  # Ampersand injection
            "/tmp & && whoami",  # Double ampersand
            "/tmp || whoami",  # OR operator
        ],
    )
    def test_all_cwd_injection_vectors_blocked(self, unsafe_cwd):
        """All cwd injection vectors must raise CommandExecutionError."""
        with pytest.raises(CommandExecutionError):
            validate_cwd(unsafe_cwd)

    @pytest.mark.parametrize(
        "unsafe_env",
        [
            {"VAR": "`id`"},  # Backticks
            {"VAR": "$(hostname)"},  # Dollar-paren
            {"VAR": "'; echo hacked"},  # Quote + semicolon
            {"VAR": "; rm -rf /"},  # Semicolon
            {"VAR": "| whoami"},  # Pipe
            {"VAR": "> /tmp/evil"},  # Redirect
            {"VAR": "& whoami"},  # Ampersand
        ],
    )
    def test_all_env_injection_vectors_blocked(self, unsafe_env):
        """All env injection vectors must raise CommandExecutionError."""
        with pytest.raises(CommandExecutionError):
            validate_env(unsafe_env)

    def test_legitimate_paths_not_blocked(self):
        """Legitimate paths and env values must be allowed."""
        legitimate = [
            "/var/log/syslog",
            "/home/user/.config",
            "/tmp/file-with-dashes",
            "/path_with_underscores",
            "/path.with.dots",
            "~/relative/path",
        ]
        for path in legitimate:
            validate_cwd(path)  # Should not raise

    def test_legitimate_env_values_not_blocked(self):
        """Legitimate env values must be allowed (within safety constraints)."""
        # Note: Some characters like | are blocked even if semantically legitimate,
        # because they are shell metacharacters and cannot safely appear in env values.
        # Users must avoid these characters or encode them differently (e.g., base64).
        legitimate_env = {
            "PATH": "/usr/local/bin:/usr/bin:/bin",
            "HOME": "/home/user",
            "EMAIL": "user@example.com",
            "VERSION": "1.0.0-beta+build.123",
            "PATTERN": "error_or_warning",  # Use underscore instead of pipe
            "REGEX": "[a-z]+",  # Brackets are safe
        }
        validate_env(legitimate_env)  # Should not raise
