"""Shell connector for AiCippy.

Provides sandboxed shell command execution.
"""

from __future__ import annotations

import shutil
import tempfile
from pathlib import Path
from typing import Any

from aicippy.connectors.base import (
    BaseConnector,
    ConnectorConfig,
    ConnectorStatus,
    ToolResult,
)
from aicippy.utils.logging import get_logger

logger = get_logger(__name__)

# Commands considered safe for non-privileged execution (read-only / informational)
SAFE_COMMANDS: frozenset[str] = frozenset({
    "ls", "cat", "grep", "find", "echo", "pwd", "whoami", "date",
    "head", "tail", "wc", "sort", "uniq", "diff", "file", "which",
    "env", "printenv", "uname", "hostname", "id", "df", "du",
    "ps", "top", "free", "uptime", "stat", "readlink", "basename",
    "dirname", "realpath", "tr", "cut", "awk", "sed", "tee",
    "less", "more", "strings", "hexdump", "md5sum", "sha256sum",
    "curl", "wget", "ping", "dig", "nslookup", "host",
    "git", "python", "python3", "pip", "npm", "node", "go",
    "docker", "kubectl", "terraform", "make", "cmake",
    "cargo", "rustc", "javac", "java", "mvn", "gradle",
})

# Shell command patterns that are blocked (destructive system operations)
DANGEROUS_PATTERNS: frozenset[str] = frozenset({
    "rm -rf /",
    "rm -fr /",
    "mkfs",
    "dd if=",
    "format c:",
    "fdisk",
    "shutdown",
    "reboot",
    "halt",
    "kill -9 -1",
    "chmod 777 /",
    "wipefs",
    "> /dev/sd",
    ":(){ :|:& };:",
})


class ShellConnector(BaseConnector):
    """Shell command connector with sandboxing.

    Provides restricted shell command execution with
    dangerous command blocking.
    """

    name = "bash"
    description = "Shell commands with security sandboxing"
    version = "1.0.0"

    def __init__(self, config: ConnectorConfig | None = None) -> None:
        """Initialize shell connector."""
        if config is None:
            config = ConnectorConfig(sandbox_mode=False)
        super().__init__(config)
        self._shell_path: str | None = None

    async def check_availability(self) -> bool:
        """Check if shell is available.

        Returns:
            True (shell is always available).

        """
        self._shell_path = shutil.which("bash") or shutil.which("sh")
        if self._shell_path:
            self._status = ConnectorStatus.AVAILABLE
            return True
        self._status = ConnectorStatus.UNAVAILABLE
        self._last_error = "No shell found"
        return False

    async def validate_command(self, command: str) -> tuple[bool, str]:
        """Validate a shell command for safety.

        When sandbox_mode is enabled, only SAFE_COMMANDS are allowed.
        Dangerous patterns are always blocked regardless of mode.

        Args:
            command: Command to validate.

        Returns:
            Tuple of (is_valid, error_message).

        """
        if not command or not command.strip():
            return False, "Empty command"

        # Check for shell injection at the base level
        if self._has_shell_injection(command):
            return False, "Shell injection characters detected in command"

        # Check base-level dangerous command patterns
        is_dangerous, reason = self._is_dangerous_command(command)
        if is_dangerous:
            return False, reason

        cmd_lower = command.strip().lower()

        # Check against dangerous patterns (always blocked)
        for pattern in DANGEROUS_PATTERNS:
            if pattern in cmd_lower:
                logger.warning(
                    "shell_dangerous_pattern",
                    pattern=pattern,
                    command=command[:100],
                )
                return False, f"Dangerous shell command blocked: {pattern}"

        # In sandbox mode, only allow safe commands
        if self.config.sandbox_mode:
            # Extract the base command (first word)
            base_cmd = cmd_lower.split()[0] if cmd_lower.split() else ""
            # Strip path prefix (e.g., /usr/bin/ls -> ls)
            base_cmd = base_cmd.rsplit("/", maxsplit=1)[-1]
            if base_cmd not in SAFE_COMMANDS:
                return False, f"Command '{base_cmd}' not in safe commands list (sandbox mode)"

        # Check allowlist/blocklist from configuration
        is_allowed, error = self._is_command_allowed(command)
        if not is_allowed:
            return False, error

        return True, ""

    async def execute(self, command: str, **kwargs: Any) -> ToolResult:  # noqa: ANN401
        """Execute a shell command.

        Args:
            command: Shell command to execute.
            **kwargs: Additional arguments (cwd, env, timeout).

        Returns:
            ToolResult with command output.

        """
        # Validate
        is_valid, error = await self.validate_command(command)
        if not is_valid:
            return ToolResult.error_result(error)

        # Check availability
        if not self._shell_path:
            await self.check_availability()

        if not self.is_available:
            return ToolResult.error_result(self._last_error or "Shell not available")

        # Get optional parameters
        cwd = kwargs.get("cwd")
        env = kwargs.get("env")
        timeout = kwargs.get("timeout", self.config.timeout_seconds)

        # Execute via shell
        logger.info("shell_command_executing", command=command[:100])

        args = [self._shell_path or "/bin/sh", "-c", command]

        result = await self._run_command(
            args,
            timeout=timeout,
            env=env,
            cwd=cwd,
        )

        if result.success:
            logger.info("shell_command_success", execution_time_ms=result.execution_time_ms)
        else:
            logger.warning(
                "shell_command_failed",
                error=result.error,
                execution_time_ms=result.execution_time_ms,
            )

        return result

    async def execute_script(
        self,
        script: str,
        cwd: str | None = None,
        env: dict[str, str] | None = None,
    ) -> ToolResult:
        """Execute a multi-line shell script.

        Args:
            script: Multi-line script content.
            cwd: Working directory.
            env: Environment variables.

        Returns:
            ToolResult with script output.

        """
        # Write script to temp file
        with tempfile.NamedTemporaryFile(
            mode="w",
            suffix=".sh",
            delete=False,
        ) as f:
            f.write("#!/bin/bash\nset -e\n")
            f.write(script)
            script_path = f.name

        try:
            # Make executable
            Path(script_path).chmod(0o700)

            # Execute
            return await self._run_command(
                [self._shell_path or "/bin/bash", script_path],
                cwd=cwd,
                env=env,
            )
        finally:
            # Clean up
            try:
                Path(script_path).unlink()
            except OSError:
                logger.debug("script_cleanup_failed", path=str(script_path), exc_info=True)
