"""Shell command executor with strict security controls."""

import asyncio
import time
from typing import Any, Optional

from app.config import get_settings
from app.executors.base import BaseExecutor, ExecutionResult
from app.utils.validation import ValidationError, validate_shell_command


class ShellExecutor(BaseExecutor):
    """Execute shell commands with strict security controls.

    Security measures:
    - Absolute path allowlist for commands
    - Per-command argument validation via regex
    - Forbidden shell metacharacter blocking
    - No shell=True (prevents shell injection)
    - Timeout enforcement with watchdog
    - Runs via asyncio.to_thread to avoid blocking

    Payload:
        {
            "cmd": "/usr/bin/ls",       # Must be absolute path
            "args": "-la /opt/data",    # Optional arguments
            "timeout": 60               # Optional timeout override
        }

    Result:
        {
            "exit_code": 0,
            "stdout": "...",
            "stderr": "...",
            "duration_ms": 123.45
        }
    """

    @property
    def task_type(self) -> str:
        return "SHELL"

    async def execute(self, payload: dict[str, Any]) -> ExecutionResult:
        """Execute a shell command.

        Args:
            payload: Must contain "cmd", optionally "args" and "timeout"

        Returns:
            ExecutionResult with command output
        """
        self.validate_payload(payload, ["cmd"])
        settings = get_settings()

        cmd = payload["cmd"]
        args_str = payload.get("args", "")
        timeout_override = payload.get("timeout")

        # Validate command and arguments
        try:
            validated_cmd, args_list, default_timeout = validate_shell_command(cmd, args_str)
        except ValidationError as e:
            self.logger.warning("shell_validation_failed", cmd=cmd, error=str(e))
            return ExecutionResult(
                success=False,
                data={"exit_code": -1, "stdout": "", "stderr": ""},
                error=f"Validation failed: {e}",
            )

        # Determine timeout
        timeout = timeout_override if timeout_override is not None else default_timeout
        timeout = min(timeout, settings.shell_timeout)  # Cap at global max

        self.logger.info(
            "shell_executing",
            cmd=validated_cmd,
            args=args_list,
            timeout=timeout,
        )

        start_time = time.time()

        try:
            # Run in thread pool to avoid blocking event loop
            result = await asyncio.wait_for(
                self._run_subprocess(validated_cmd, args_list),
                timeout=timeout * 2,  # Watchdog at 2x timeout
            )

            duration_ms = (time.time() - start_time) * 1000
            exit_code, stdout, stderr = result

            success = exit_code == 0

            self.logger.info(
                "shell_completed",
                cmd=validated_cmd,
                exit_code=exit_code,
                duration_ms=duration_ms,
            )

            return ExecutionResult(
                success=success,
                data={
                    "exit_code": exit_code,
                    "stdout": stdout,
                    "stderr": stderr,
                },
                error=stderr if not success else None,
                duration_ms=duration_ms,
            )

        except asyncio.TimeoutError:
            duration_ms = (time.time() - start_time) * 1000
            self.logger.error("shell_timeout", cmd=validated_cmd, timeout=timeout)
            return ExecutionResult(
                success=False,
                data={"exit_code": -1, "stdout": "", "stderr": ""},
                error=f"Command timed out after {timeout} seconds",
                duration_ms=duration_ms,
            )

        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            self.logger.error("shell_error", cmd=validated_cmd, error=str(e))
            return ExecutionResult(
                success=False,
                data={"exit_code": -1, "stdout": "", "stderr": ""},
                error=str(e),
                duration_ms=duration_ms,
            )

    async def _run_subprocess(
        self,
        cmd: str,
        args: list[str],
    ) -> tuple[int, str, str]:
        """Run subprocess in thread pool.

        Args:
            cmd: Command to run (absolute path)
            args: Command arguments

        Returns:
            Tuple of (exit_code, stdout, stderr)
        """
        import subprocess

        def _run() -> tuple[int, str, str]:
            # Build full command list
            full_cmd = [cmd] + args

            # Run WITHOUT shell=True for security
            result = subprocess.run(
                full_cmd,
                capture_output=True,
                text=True,
                timeout=get_settings().shell_timeout,
            )

            return result.returncode, result.stdout, result.stderr

        return await asyncio.to_thread(_run)
