"""
Terminal Executor Module

Shell command execution with safety controls:
- Command validation
- Timeout handling
- Output capture
- Working directory management
"""

import os
import subprocess
import threading
from pathlib import Path
from typing import Optional

from rich.console import Console

from ai_coder.tools.base import Tool, ToolResult
from ai_coder.safety.guards import get_safety_guard

console = Console()


class RunCommandTool(Tool):
    """Execute shell commands in the project directory."""

    @property
    def name(self) -> str:
        return "run_command"

    @property
    def description(self) -> str:
        return "Execute a shell command in the project directory"

    def execute(
        self,
        command: str,
        working_dir: Optional[str] = None,
        timeout: Optional[int] = None,
        **kwargs,
    ) -> ToolResult:
        """Execute a shell command."""
        try:
            guard = get_safety_guard()

            # Validate command
            is_safe, error = guard.validate_command(command)
            if not is_safe:
                return ToolResult.error(error)

            # Determine working directory
            if working_dir:
                cwd = self.resolve_path(working_dir)
                valid, error = self.validate_path(cwd)
                if not valid:
                    return ToolResult.error(error)
            else:
                cwd = self.project_root

            if cwd is None:
                return ToolResult.error("Project root not set")

            if not cwd.exists():
                return ToolResult.error(f"Working directory not found: {cwd}")

            # Use configured timeout or default
            cmd_timeout = timeout or guard.config.command_timeout

            # Show command being executed
            console.print(f"\n[bold yellow]$ {command}[/bold yellow]")
            console.print(f"[dim](in {cwd})[/dim]\n")

            # Execute command
            result = self._run_subprocess(command, cwd, cmd_timeout)

            return result

        except Exception as e:
            return ToolResult.error(f"Command execution failed: {e}")

    def _run_subprocess(self, command: str, cwd: Path, timeout: int) -> ToolResult:
        """Run command in subprocess with timeout handling."""
        try:
            # Use shell=True for command parsing
            # Note: This is intentional for user-facing CLI tool
            process = subprocess.Popen(
                command,
                shell=True,
                cwd=str(cwd),
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                env=self._get_safe_env(),
            )

            try:
                stdout, stderr = process.communicate(timeout=timeout)
            except subprocess.TimeoutExpired:
                process.kill()
                process.communicate()
                return ToolResult.error(f"Command timed out after {timeout} seconds")

            # Combine output
            output_parts = []

            if stdout.strip():
                output_parts.append(stdout.strip())

            if stderr.strip():
                output_parts.append(f"[stderr]\n{stderr.strip()}")

            output = "\n".join(output_parts) if output_parts else "(no output)"

            # Check return code
            if process.returncode != 0:
                error_msg = f"Command failed with exit code {process.returncode}"
                if output != "(no output)":
                    error_msg += f"\n\nOutput:\n{output}"
                return ToolResult.error(error_msg)

            # Print output to console
            if output != "(no output)":
                console.print(output[:2000])
                if len(output) > 2000:
                    console.print(f"[dim]... ({len(output) - 2000} more characters)[/dim]")

            return ToolResult.success(output, data={"return_code": process.returncode})

        except FileNotFoundError as e:
            return ToolResult.error(f"Command not found: {e}")
        except PermissionError as e:
            return ToolResult.error(f"Permission denied: {e}")

    def _get_safe_env(self) -> dict:
        """Get a safe environment for subprocess execution."""
        env = os.environ.copy()

        # Remove potentially sensitive variables
        sensitive_vars = [
            "AWS_SECRET_ACCESS_KEY",
            "GITHUB_TOKEN",
            "OPENAI_API_KEY",
            "ANTHROPIC_API_KEY",
        ]

        for var in sensitive_vars:
            env.pop(var, None)

        return env


class AsyncCommandExecutor:
    """
    Async command executor for long-running processes.
    
    Features:
    - Non-blocking execution
    - Output streaming
    - Cancellation support
    """

    def __init__(self, project_root: Path):
        self.project_root = project_root
        self.running_processes: dict[str, subprocess.Popen] = {}
        self._lock = threading.Lock()

    def start(
        self,
        command_id: str,
        command: str,
        cwd: Optional[Path] = None,
    ) -> ToolResult:
        """Start an async command."""
        try:
            working_dir = cwd or self.project_root

            process = subprocess.Popen(
                command,
                shell=True,
                cwd=str(working_dir),
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
            )

            with self._lock:
                self.running_processes[command_id] = process

            return ToolResult.success(f"Started command with ID: {command_id}")

        except Exception as e:
            return ToolResult.error(f"Failed to start command: {e}")

    def get_output(self, command_id: str, max_lines: int = 100) -> ToolResult:
        """Get output from a running command."""
        with self._lock:
            process = self.running_processes.get(command_id)

        if process is None:
            return ToolResult.error(f"No process with ID: {command_id}")

        lines = []
        try:
            while len(lines) < max_lines:
                line = process.stdout.readline()
                if not line:
                    break
                lines.append(line.rstrip())
        except Exception:
            pass

        is_running = process.poll() is None
        status = "running" if is_running else f"finished (exit: {process.returncode})"

        output = "\n".join(lines) if lines else "(no new output)"
        return ToolResult.success(f"[{status}]\n{output}")

    def cancel(self, command_id: str) -> ToolResult:
        """Cancel a running command."""
        with self._lock:
            process = self.running_processes.get(command_id)

        if process is None:
            return ToolResult.error(f"No process with ID: {command_id}")

        try:
            process.terminate()
            process.wait(timeout=5)
        except subprocess.TimeoutExpired:
            process.kill()

        with self._lock:
            self.running_processes.pop(command_id, None)

        return ToolResult.success(f"Cancelled command: {command_id}")

    def cleanup(self) -> None:
        """Clean up all running processes."""
        with self._lock:
            for process in self.running_processes.values():
                try:
                    process.terminate()
                    process.wait(timeout=2)
                except Exception:
                    try:
                        process.kill()
                    except Exception:
                        pass

            self.running_processes.clear()
