"""Shell command execution implementation"""

import os
import subprocess
import asyncio
from typing import Dict, Tuple, Optional, List

from .config import ShellConfig


class ShellCommander:
    """Shell command execution class"""
    
    def __init__(self, config: Optional[ShellConfig] = None):
        """Initialize ShellCommander
        
        Args:
            config: Shell configuration
        """
        self.config = config or ShellConfig()
    
    def _validate_command(self, command: str) -> bool:
        """Validate command against whitelist and blacklist
        
        Args:
            command: Command to validate
        
        Returns:
            bool: True if command is valid, False otherwise
        """
        # Get the first token as the command name
        cmd_name = command.split()[0] if command.strip() else ""
        
        # Check blacklist first
        if self.config.command_blacklist and cmd_name in self.config.command_blacklist:
            return False
        
        # Check whitelist if provided
        if self.config.command_whitelist and cmd_name not in self.config.command_whitelist:
            return False
        
        return True
    
    def _validate_cwd(self, cwd: str) -> bool:
        """Validate working directory against whitelist
        
        Args:
            cwd: Working directory to validate
        
        Returns:
            bool: True if cwd is valid, False otherwise
        """
        if not self.config.cwd_whitelist:
            return True
        
        # Check if cwd is in any of the whitelist paths
        for whitelist_path in self.config.cwd_whitelist:
            if cwd.startswith(whitelist_path):
                return True
        
        return False
    
    def execute(self, command: str, cwd: Optional[str] = None, env: Optional[Dict[str, str]] = None, timeout: Optional[int] = None) -> Tuple[int, str, str]:
        """Execute shell command synchronously
        
        Args:
            command: Command to execute
            cwd: Working directory
            env: Environment variables
            timeout: Timeout in seconds
        
        Returns:
            Tuple[int, str, str]: (return code, stdout, stderr)
        """
        # Validate command
        if not self._validate_command(command):
            raise ValueError(f"Command not allowed: {command}")
        
        # Determine working directory
        target_cwd = cwd or self.config.cwd or os.getcwd()
        
        # Validate working directory
        if not self._validate_cwd(target_cwd):
            raise ValueError(f"Working directory not allowed: {target_cwd}")
        
        # Prepare environment variables
        target_env = os.environ.copy()
        if self.config.env:
            target_env.update(self.config.env)
        if env:
            target_env.update(env)
        
        # Execute command
        process = subprocess.Popen(
            command,
            shell=True,
            cwd=target_cwd,
            env=target_env,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            encoding=self.config.encoding,
            errors='replace'  # Handle encoding errors gracefully
        )
        
        try:
            stdout, stderr = process.communicate(timeout=timeout or self.config.timeout)
        except subprocess.TimeoutExpired:
            process.kill()
            stdout, stderr = process.communicate()
            return process.returncode, stdout, stderr
        
        return process.returncode, stdout, stderr
    
    async def execute_async(self, command: str, cwd: Optional[str] = None, env: Optional[Dict[str, str]] = None, timeout: Optional[int] = None) -> Tuple[int, str, str]:
        """Execute shell command asynchronously
        
        Args:
            command: Command to execute
            cwd: Working directory
            env: Environment variables
            timeout: Timeout in seconds
        
        Returns:
            Tuple[int, str, str]: (return code, stdout, stderr)
        """
        # Validate command
        if not self._validate_command(command):
            raise ValueError(f"Command not allowed: {command}")
        
        # Determine working directory
        target_cwd = cwd or self.config.cwd or os.getcwd()
        
        # Validate working directory
        if not self._validate_cwd(target_cwd):
            raise ValueError(f"Working directory not allowed: {target_cwd}")
        
        # Prepare environment variables
        target_env = os.environ.copy()
        if self.config.env:
            target_env.update(self.config.env)
        if env:
            target_env.update(env)
        
        # Execute command asynchronously
        process = await asyncio.create_subprocess_shell(
            command,
            cwd=target_cwd,
            env=target_env,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )
        
        try:
            if timeout or self.config.timeout:
                stdout_bytes, stderr_bytes = await asyncio.wait_for(
                    process.communicate(),
                    timeout=timeout or self.config.timeout
                )
            else:
                stdout_bytes, stderr_bytes = await process.communicate()
        except asyncio.TimeoutError:
            process.kill()
            stdout_bytes, stderr_bytes = await process.communicate()
        
        # Decode output with error handling
        try:
            stdout = stdout_bytes.decode(self.config.encoding, errors='replace')
        except (AttributeError, UnicodeDecodeError):
            stdout = ""
        
        try:
            stderr = stderr_bytes.decode(self.config.encoding, errors='replace')
        except (AttributeError, UnicodeDecodeError):
            stderr = ""
        
        return process.returncode, stdout, stderr
    
    def set_config(self, config: ShellConfig):
        """Set configuration
        
        Args:
            config: New shell configuration
        """
        self.config = config
    
    def get_config(self) -> ShellConfig:
        """Get current configuration
        
        Returns:
            ShellConfig: Current configuration
        """
        return self.config


class DefaultShellCommander(ShellCommander):
    """Default shell commander with pre-configured settings"""
    
    # Singleton instance
    _instance = None
    
    def __new__(cls):
        """Create singleton instance"""
        if cls._instance is None:
            # Create instance first
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self):
        """Initialize DefaultShellCommander"""
        # Only initialize once
        if not hasattr(self, '_initialized'):
            # Create default configuration with explicit lists
            default_config = ShellConfig()
            default_config.command_whitelist = [
                "echo", "dir", "ls", "type", "more", "find", "findstr",
                "grep", "sort", "date", "time", "cls", "clear",
                "ipconfig", "ping", "netstat", "tasklist", "systeminfo",
                "ver", "whoami", "set", "path"
            ]
            default_config.command_blacklist = [
                "rm", "del", "erase", "rd", "rmdir", "move", "ren",
                "rename", "copy", "xcopy", "robocopy", "mkdir", "md"
            ]
            # Initialize instance with config
            super().__init__(default_config)
            # Mark as initialized
            self._initialized = True
