"""
Advanced Terminal Manager for autonomous terminal interactions
Handles command execution, output parsing, and intelligent terminal operations
"""
from typing import Dict, List, Optional, Union, Any
import asyncio
import re
from dataclasses import dataclass
from enum import Enum
import shlex
import os
from pathlib import Path
import json
import time

class CommandStatus(Enum):
    SUCCESS = "success"
    ERROR = "error"
    TIMEOUT = "timeout"
    INTERRUPTED = "interrupted"

@dataclass
class CommandResult:
    status: CommandStatus
    output: str
    error: str
    exit_code: int
    duration: float
    command: str
    cwd: str
    env: Dict[str, str]

class OutputPattern:
    """Patterns to watch for in command output"""
    def __init__(self, 
                pattern: str, 
                is_regex: bool = False,
                response: Optional[str] = None,
                should_terminate: bool = False):
        self.pattern = re.compile(pattern) if is_regex else pattern
        self.is_regex = is_regex
        self.response = response
        self.should_terminate = should_terminate

class TerminalManager:
    def __init__(self, workspace_path: str):
        self.workspace_path = Path(workspace_path)
        self.current_dir = self.workspace_path
        self.env = os.environ.copy()
        self.command_history: List[CommandResult] = []
        self.max_history = 100
        self.default_timeout = 60  # seconds
        
    async def execute_command(self, 
                            command: str,
                            expected_patterns: Optional[List[OutputPattern]] = None,
                            timeout: Optional[int] = None,
                            capture_output: bool = True) -> CommandResult:
        """Execute a command and handle its output intelligently"""
        start_time = time.time()
        timeout = timeout or self.default_timeout
        
        try:
            # Split command properly
            if isinstance(command, str):
                cmd_args = shlex.split(command)
            else:
                cmd_args = command
                
            # Create subprocess
            process = await asyncio.create_subprocess_exec(
                *cmd_args,
                stdout=asyncio.subprocess.PIPE if capture_output else None,
                stderr=asyncio.subprocess.PIPE if capture_output else None,
                cwd=str(self.current_dir),
                env=self.env
            )
            
            if capture_output:
                # Handle interactive output if patterns are provided
                if expected_patterns:
                    result = await self._handle_interactive_output(process, expected_patterns, timeout)
                else:
                    # Simple output capture
                    try:
                        stdout, stderr = await asyncio.wait_for(process.communicate(), timeout)
                        result = CommandResult(
                            status=CommandStatus.SUCCESS if process.returncode == 0 else CommandStatus.ERROR,
                            output=stdout.decode() if stdout else "",
                            error=stderr.decode() if stderr else "",
                            exit_code=process.returncode,
                            duration=time.time() - start_time,
                            command=command,
                            cwd=str(self.current_dir),
                            env=self.env.copy()
                        )
                    except asyncio.TimeoutError:
                        result = CommandResult(
                            status=CommandStatus.TIMEOUT,
                            output="",
                            error=f"Command timed out after {timeout} seconds",
                            exit_code=-1,
                            duration=timeout,
                            command=command,
                            cwd=str(self.current_dir),
                            env=self.env.copy()
                        )
            else:
                # Just wait for completion without capturing output
                try:
                    await asyncio.wait_for(process.wait(), timeout)
                    result = CommandResult(
                        status=CommandStatus.SUCCESS if process.returncode == 0 else CommandStatus.ERROR,
                        output="",
                        error="",
                        exit_code=process.returncode,
                        duration=time.time() - start_time,
                        command=command,
                        cwd=str(self.current_dir),
                        env=self.env.copy()
                    )
                except asyncio.TimeoutError:
                    result = CommandResult(
                        status=CommandStatus.TIMEOUT,
                        output="",
                        error=f"Command timed out after {timeout} seconds",
                        exit_code=-1,
                        duration=timeout,
                        command=command,
                        cwd=str(self.current_dir),
                        env=self.env.copy()
                    )
                    
            # Store in history
            self._add_to_history(result)
            return result
            
        except Exception as e:
            result = CommandResult(
                status=CommandStatus.ERROR,
                output="",
                error=str(e),
                exit_code=-1,
                duration=time.time() - start_time,
                command=command,
                cwd=str(self.current_dir),
                env=self.env.copy()
            )
            self._add_to_history(result)
            return result
            
    async def _handle_interactive_output(self,
                                     process: asyncio.subprocess.Process,
                                     patterns: List[OutputPattern],
                                     timeout: int) -> CommandResult:
        """Handle interactive command output with pattern matching"""
        start_time = time.time()
        output_buffer = []
        error_buffer = []
        
        async def read_stream(stream: asyncio.StreamReader, buffer: list):
            while True:
                line = await stream.readline()
                if not line:
                    break
                decoded_line = line.decode()
                buffer.append(decoded_line)
                
                # Check patterns
                for pattern in patterns:
                    if pattern.is_regex:
                        match = pattern.pattern.search(decoded_line)
                        if match and pattern.response:
                            process.stdin.write(f"{pattern.response}\n".encode())
                            await process.stdin.drain()
                        if match and pattern.should_terminate:
                            process.terminate()
                            return
                    else:
                        if pattern.pattern in decoded_line:
                            if pattern.response:
                                process.stdin.write(f"{pattern.response}\n".encode())
                                await process.stdin.drain()
                            if pattern.should_terminate:
                                process.terminate()
                                return
        
        try:
            # Start reading both streams
            stdout_task = asyncio.create_task(read_stream(process.stdout, output_buffer))
            stderr_task = asyncio.create_task(read_stream(process.stderr, error_buffer))
            
            # Wait for process to complete or timeout
            await asyncio.wait_for(process.wait(), timeout)
            
            # Cancel stream reading tasks
            stdout_task.cancel()
            stderr_task.cancel()
            
            return CommandResult(
                status=CommandStatus.SUCCESS if process.returncode == 0 else CommandStatus.ERROR,
                output="".join(output_buffer),
                error="".join(error_buffer),
                exit_code=process.returncode,
                duration=time.time() - start_time,
                command=process._transport._proc.args,
                cwd=str(self.current_dir),
                env=self.env.copy()
            )
            
        except asyncio.TimeoutError:
            process.terminate()
            return CommandResult(
                status=CommandStatus.TIMEOUT,
                output="".join(output_buffer),
                error="".join(error_buffer) + f"\nCommand timed out after {timeout} seconds",
                exit_code=-1,
                duration=timeout,
                command=process._transport._proc.args,
                cwd=str(self.current_dir),
                env=self.env.copy()
            )
            
    def _add_to_history(self, result: CommandResult):
        """Add command result to history"""
        self.command_history.append(result)
        if len(self.command_history) > self.max_history:
            self.command_history.pop(0)
            
    def cd(self, path: Union[str, Path]):
        """Change current directory"""
        new_path = Path(path)
        if not new_path.is_absolute():
            new_path = self.current_dir / new_path
        if new_path.exists() and new_path.is_dir():
            self.current_dir = new_path
            return True
        return False
        
    def set_env(self, key: str, value: str):
        """Set environment variable"""
        self.env[key] = value
        
    def get_env(self, key: str) -> Optional[str]:
        """Get environment variable"""
        return self.env.get(key)
        
    def clear_history(self):
        """Clear command history"""
        self.command_history.clear()
        
    async def execute_script(self, script: List[str]) -> List[CommandResult]:
        """Execute a series of commands"""
        results = []
        for command in script:
            result = await self.execute_command(command)
            results.append(result)
            if result.status != CommandStatus.SUCCESS:
                break
        return results
        
    async def run_with_retry(self, 
                          command: str,
                          max_retries: int = 3,
                          retry_delay: int = 5) -> CommandResult:
        """Execute command with automatic retries"""
        last_result = None
        for attempt in range(max_retries):
            result = await self.execute_command(command)
            if result.status == CommandStatus.SUCCESS:
                return result
            last_result = result
            if attempt < max_retries - 1:
                await asyncio.sleep(retry_delay)
        return last_result
        
    def analyze_output(self, result: CommandResult) -> Dict[str, Any]:
        """Analyze command output for common patterns and issues"""
        analysis = {
            "success": result.status == CommandStatus.SUCCESS,
            "duration": result.duration,
            "exit_code": result.exit_code,
            "errors": [],
            "warnings": [],
            "info": []
        }
        
        # Common error patterns
        error_patterns = {
            r"error:": "error",
            r"exception:": "error",
            r"failed:": "error",
            r"warning:": "warning",
            r"deprecated:": "warning"
        }
        
        # Check both output and error streams
        for line in (result.output + result.error).split("\n"):
            for pattern, level in error_patterns.items():
                if re.search(pattern, line, re.IGNORECASE):
                    analysis[f"{level}s"].append(line.strip())
                    
        return analysis
        
    async def run_build_tool(self, tool: str, args: List[str] = None) -> CommandResult:
        """Run common build tools with appropriate configuration"""
        build_tools = {
            "npm": {
                "install": ["npm", "install"],
                "build": ["npm", "run", "build"],
                "test": ["npm", "test"],
            },
            "pip": {
                "install": ["pip", "install", "-r", "requirements.txt"],
                "test": ["pytest"],
            },
            "poetry": {
                "install": ["poetry", "install"],
                "build": ["poetry", "build"],
                "test": ["poetry", "run", "pytest"],
            }
        }
        
        if tool not in build_tools:
            raise ValueError(f"Unsupported build tool: {tool}")
            
        command = build_tools[tool].get(args[0] if args else "install", [tool])
        if args and args[0] not in build_tools[tool]:
            command.extend(args)
            
        return await self.execute_command(" ".join(command))
        
    def parse_command_output(self, result: CommandResult, 
                           patterns: Dict[str, str]) -> Dict[str, List[str]]:
        """Parse command output using custom patterns"""
        parsed = {key: [] for key in patterns}
        
        for line in result.output.split("\n"):
            for key, pattern in patterns.items():
                if re.search(pattern, line):
                    parsed[key].append(line.strip())
                    
        return parsed
