"""Command execution safety: prevent shell injection via cwd/env validation."""

import re
from typing import Optional, Dict

# Shell metacharacters that cannot appear in cwd or env values
# These characters would break out of the bash -lc template or cause injection
FORBIDDEN_CWD_CHARS = set("';$`\\|<>&\n\r")
FORBIDDEN_ENV_CHARS = set("';$`\\|<>&\n\r")

class CommandExecutionError(Exception):
    """Raised when command execution is unsafe."""
    pass


def validate_cwd(cwd: Optional[str]) -> None:
    """
    Validate that cwd is safe for shell templating.

    The daemon executes: bash -lc 'cd <cwd> && <cmd>'

    If cwd contains shell metacharacters, this breaks the template.
    For example: cwd="/tmp'; rm -rf /" would become:
        bash -lc 'cd /tmp'; rm -rf / && <cmd>'
    Which executes rm as a separate command.

    This validation rejects all such cwd values.

    Args:
        cwd: Working directory for remote process

    Raises:
        CommandExecutionError: If cwd contains forbidden characters

    Falsification tests (all must pass):
        - cwd="/tmp'; rm -rf /" must raise
        - cwd="/tmp`whoami`" must raise
        - cwd="/tmp$(whoami)" must raise
        - cwd="/tmp\\'; ls" must raise
        - cwd="/tmp; rm -rf /" must raise
        - cwd="/tmp | whoami" must raise
        - cwd="/tmp > /tmp/evil" must raise
        - cwd="/tmp/normal" must NOT raise
    """
    if cwd is None:
        return

    for char in cwd:
        if char in FORBIDDEN_CWD_CHARS:
            raise CommandExecutionError(
                f"cwd contains forbidden character {repr(char)}: {repr(cwd)}\n"
                f"Forbidden chars: {repr(''.join(sorted(FORBIDDEN_CWD_CHARS)))}"
            )


def validate_env(env: Optional[Dict[str, str]]) -> None:
    """
    Validate that env values are safe for shell templating.

    The daemon executes: bash -lc 'export VAR=<value> && ...'

    If env values contain shell metacharacters, injection is possible.
    For example: env={"VAR": "$(whoami)"} would become:
        bash -lc 'export VAR=$(whoami) && ...'
    Which executes whoami.

    This validation rejects all such env values.

    Args:
        env: Environment variables dict

    Raises:
        CommandExecutionError: If any env value contains forbidden characters

    Falsification tests (all must pass):
        - env={"VAR": "`id`"} must raise
        - env={"VAR": "$(hostname)"} must raise
        - env={"VAR": "normal_value"} must NOT raise
    """
    if env is None:
        return

    for key, value in env.items():
        if not isinstance(value, str):
            raise CommandExecutionError(
                f"env[{repr(key)}] must be string, got {type(value).__name__}"
            )

        for char in value:
            if char in FORBIDDEN_ENV_CHARS:
                raise CommandExecutionError(
                    f"env[{repr(key)}] contains forbidden character {repr(char)}: {repr(value)}\n"
                    f"Forbidden chars: {repr(''.join(sorted(FORBIDDEN_ENV_CHARS)))}"
                )


def validate_command_execution(
    cwd: Optional[str] = None,
    env: Optional[Dict[str, str]] = None,
) -> None:
    """
    Validate that cwd and env are safe for remote execution.

    This is the primary defense against shell injection via the bash -lc wrapper.

    Guarantees: If this function returns without raising, cwd and env can be safely
    interpolated into: bash -lc 'cd <cwd> && export <env> && <cmd>'

    Args:
        cwd: Working directory
        env: Environment variables

    Raises:
        CommandExecutionError: If inputs are unsafe

    Note:
        This validation uses a whitelist approach (reject forbidden chars) rather than
        escaping, because escaping shell metacharacters is fragile and error-prone.
        Rejection is more defensible.
    """
    validate_cwd(cwd)
    validate_env(env)
