"""Execution orchestrator - connects all components."""

from typing import Dict, Any, Optional
from ..intent import LLMIntentClassifier, LLMParameterExtractor
from ..action_registry import ActionRegistryImpl
from ..policy_engine import SimplePolicyEngine
from ..rollback import SimpleRollbackManager
from ..execution import SafeOSExecutionAdapter
from ..monitoring import SimpleMonitoringEngine


class ExecutionOrchestrator:
    """Orchestrates complete execution pipeline."""

    def __init__(
        self,
        classifier: LLMIntentClassifier,
        extractor: LLMParameterExtractor,
        registry: ActionRegistryImpl,
        policy: SimplePolicyEngine,
        rollback: SimpleRollbackManager,
        adapter: SafeOSExecutionAdapter,
        monitor: SimpleMonitoringEngine
    ):
        """Initialize orchestrator with all components.
        
        Args:
            classifier: Intent classifier
            extractor: Parameter extractor
            registry: Action registry
            policy: Policy engine
            rollback: Rollback manager
            adapter: Execution adapter
            monitor: Monitoring engine
        """
        self.classifier = classifier
        self.extractor = extractor
        self.registry = registry
        self.policy = policy
        self.rollback = rollback
        self.adapter = adapter
        self.monitor = monitor

    def execute(self, user_input: str, dry_run: bool = True) -> Dict[str, Any]:
        """Execute complete pipeline."""
        import time
        import asyncio
        import os
        
        # Step 1: Classify intent using LLM
        t1 = time.time()
        try:
            intent_result = asyncio.run(self.classifier.classify(user_input))
        except Exception as e:
            print(f"[Error] LLM classification failed: {e}, falling back to sync classifier")
            # Fallback to sync classifier for local models
            try:
                intent_result = self.classifier.classify_sync(user_input)
            except Exception as e2:
                print(f"[Error] Sync classification also failed: {e2}")
                return {
                    "success": False,
                    "stage": "intent_classification",
                    "message": f"Needs clarification: Classification error: {str(e)}",
                    "data": {"confidence": 0.0}
                }
        print(f"[Timing] Intent classification: {time.time() - t1:.2f}s")
        
        if intent_result.needs_clarification:
            return {
                "success": False,
                "stage": "intent_classification",
                "message": f"Needs clarification: {intent_result.reasoning}",
                "data": {"confidence": intent_result.confidence}
            }
        
        action_id = intent_result.action_id
        print(f"[Timing] Detected action: {action_id}")
        
        # Step 2: Get action from registry
        t2 = time.time()
        action = self.registry.get_action(action_id)
        print(f"[Timing] Registry lookup: {time.time() - t2:.2f}s")
        if not action:
            return {
                "success": False,
                "stage": "action_registry",
                "message": f"Unknown action: {action_id}"
            }
        
        # Step 3: Extract parameters - use sync for local models
        t3 = time.time()
        param_result = self.extractor.extract_sync(
            user_input,
            action_id,
            action.required_params
        )
        print(f"[Timing] Parameter extraction: {time.time() - t3:.2f}s")
        
        if param_result.missing_fields:
            return {
                "success": False,
                "stage": "parameter_extraction",
                "message": f"Missing required parameters: {param_result.missing_fields}",
                "data": {"parameters": param_result.parameters}
            }
        
        parameters = param_result.parameters
        
        # Merge default parameters from action schema
        if action.default_params:
            for key, value in action.default_params.items():
                if key not in parameters:
                    parameters[key] = value
        
        print(f"[Timing] Extracted parameters: {parameters}")
        
        # Check for multiple file matches
        if "_multiple_matches" in parameters:
            matches = parameters.pop("_multiple_matches")
            match_list = "\n".join([f"{i+1}. {path}" for i, path in enumerate(matches)])
            return {
                "success": False,
                "stage": "parameter_extraction",
                "message": f"Multiple files found with name '{os.path.basename(matches[0])}'. Please specify which one:\n{match_list}",
                "data": {"matches": matches, "requires_clarification": True}
            }
        
        # Step 4: Policy evaluation
        t4 = time.time()
        policy_decision = self.policy.evaluate(action, parameters)
        print(f"[Timing] Policy evaluation: {time.time() - t4:.2f}s")
        
        if not policy_decision.allowed:
            return {
                "success": False,
                "stage": "policy_evaluation",
                "message": f"Policy denied: {policy_decision.reason}",
                "data": policy_decision.metadata
            }
        
        # Step 5: Create snapshot
        t5 = time.time()
        snapshot = self.rollback.create_snapshot(action_id, parameters)
        print(f"[Timing] Snapshot creation: {time.time() - t5:.2f}s")
        
        # Step 6: Log execution start
        self.monitor.log_execution_start(action_id, {"parameters": parameters})
        
        # Step 7: Execute action
        try:
            t6 = time.time()
            exec_result = self._execute_action(action_id, parameters, dry_run)
            print(f"[Timing] Action execution: {time.time() - t6:.2f}s")
            
            if exec_result.success:
                # Success: commit snapshot and log
                self.rollback.commit(snapshot.snapshot_id)
                self.monitor.log_execution_end(action_id, {
                    "message": exec_result.message,
                    "dry_run": exec_result.dry_run
                })
                
                return {
                    "success": True,
                    "stage": "execution",
                    "message": exec_result.message,
                    "data": exec_result.data,
                    "dry_run": exec_result.dry_run,
                    "snapshot_id": snapshot.snapshot_id
                }
            else:
                # Execution failed: rollback
                rollback_result = self.rollback.rollback(snapshot.snapshot_id)
                self.monitor.log_failure(action_id, exec_result.message, {
                    "rollback": rollback_result
                })
                
                return {
                    "success": False,
                    "stage": "execution",
                    "message": f"Execution failed: {exec_result.message}",
                    "rollback": rollback_result
                }
                
        except Exception as e:
            # Exception: rollback and log
            rollback_result = self.rollback.rollback(snapshot.snapshot_id)
            self.monitor.log_failure(action_id, str(e), {"rollback": rollback_result})
            
            return {
                "success": False,
                "stage": "execution",
                "message": f"Exception: {str(e)}",
                "rollback": rollback_result
            }

    def _execute_action(self, action_id: str, parameters: Dict[str, Any], dry_run: bool):
        """Execute specific action.
        
        Args:
            action_id: Action identifier
            parameters: Action parameters
            dry_run: Dry run mode
            
        Returns:
            ExecutionResult
        """
        # Dynamic action mapping
        action_map = {
            "file.move": lambda: self.adapter.move_file(
                parameters["source"],
                parameters["destination"],
                dry_run=dry_run
            ),
            "file.delete": lambda: self.adapter.delete_file(
                parameters["path"],
                dry_run=dry_run
            ),
            "file.copy": lambda: self.adapter.copy_file(
                parameters["source"],
                parameters["destination"],
                overwrite=parameters.get("overwrite", False),
                dry_run=dry_run
            ),
            "file.read": lambda: self.adapter.read_file(
                parameters["path"],
                encoding=parameters.get("encoding", "utf-8")
            ),
            "file.write": lambda: self.adapter.write_file(
                parameters["path"],
                parameters["content"],
                mode=parameters.get("mode", "w"),
                encoding=parameters.get("encoding", "utf-8"),
                dry_run=dry_run
            ),
            "folder.create": lambda: self.adapter.create_folder(
                parameters["name"],
                parameters["path"],
                parents=parameters.get("parents", True),
                dry_run=dry_run
            ),
            "system.restart": lambda: self.adapter.restart_system(
                delay=parameters.get("delay", 0),
                dry_run=dry_run
            ),
            "system.shutdown": lambda: self.adapter.shutdown_system(
                delay=parameters.get("delay", 0),
                force=parameters.get("force", False),
                dry_run=dry_run
            ),
            "system.sleep": lambda: self.adapter.sleep_system(dry_run=dry_run),
            "system.hibernate": lambda: self.adapter.hibernate_system(dry_run=dry_run),
            "system.lock": lambda: self.adapter.lock_screen(dry_run=dry_run),
            "system.resources": lambda: self.adapter.get_system_resources(),
            "system.info": lambda: self.adapter.get_system_info(),
            "system.settings": lambda: self.adapter.change_system_setting(
                parameters["setting"],
                parameters["value"],
                dry_run=dry_run
            ),
            "system.update": lambda: self.adapter.update_os(
                auto_install=parameters.get("auto_install", False),
                dry_run=dry_run
            ),
            "process.kill": lambda: self.adapter.kill_process(
                parameters["pid"],
                force=parameters.get("force", False),
                dry_run=dry_run
            ),
            "service.start": lambda: self.adapter.start_service(
                parameters["name"],
                dry_run=dry_run
            ),
            "service.stop": lambda: self.adapter.stop_service(
                parameters["name"],
                dry_run=dry_run
            ),
            "service.enable": lambda: self.adapter.enable_service(
                parameters["name"],
                dry_run=dry_run
            ),
            "service.disable": lambda: self.adapter.disable_service(
                parameters["name"],
                dry_run=dry_run
            ),
            "system.volume": lambda: self.adapter.control_volume(
                parameters["action"],
                level=parameters.get("level"),
                dry_run=dry_run
            ),
            "app.install": lambda: self.adapter.install_app(
                parameters["app_name"],
                source=parameters.get("source"),
                version=parameters.get("version"),
                dry_run=dry_run
            ),
            "app.uninstall": lambda: self.adapter.uninstall_app(
                parameters["app_name"],
                force=parameters.get("force", False),
                dry_run=dry_run
            ),
            "app.update": lambda: self.adapter.update_app(
                parameters["app_name"],
                version=parameters.get("version"),
                dry_run=dry_run
            ),
            "app.list": lambda: self.adapter.list_apps(
                filter=parameters.get("filter")
            ),
            "app.open": lambda: self.adapter.open_app(
                parameters["app_name"],
                args=parameters.get("args"),
                dry_run=dry_run
            ),
            "app.force_close": lambda: self.adapter.force_close_app(
                parameters["app_name"],
                dry_run=dry_run
            ),
            "app.set_default": lambda: self.adapter.set_default_app(
                parameters["app_name"],
                parameters["file_type"],
                dry_run=dry_run
            ),
            "app.schedule_autostart": lambda: self.adapter.schedule_app_autostart(
                parameters["app_name"],
                parameters["schedule"],
                args=parameters.get("args"),
                dry_run=dry_run
            ),
            "app.startup_toggle": lambda: self.adapter.toggle_app_startup(
                parameters["app_name"],
                parameters["enabled"],
                dry_run=dry_run
            ),
            "app.version": lambda: self.adapter.get_app_version(
                parameters["app_name"]
            ),
            "browser.open": lambda: self.adapter.open_browser(
                parameters["url"],
                query=parameters.get("query"),
                dry_run=dry_run
            ),
            "monitor.cpu_threshold": lambda: self.adapter.monitor_cpu_threshold(
                parameters["threshold"],
                duration=parameters.get("duration", 60)
            ),
            "monitor.ram_spike": lambda: self.adapter.monitor_ram_spike(
                parameters["spike_threshold"]
            ),
            "monitor.crash_loop": lambda: self.adapter.monitor_crash_loop(
                parameters["service_name"],
                restart_threshold=parameters.get("restart_threshold", 3)
            ),
            "monitor.error_logs": lambda: self.adapter.monitor_error_logs(
                parameters["log_path"]
            ),
            "monitor.root_cause": lambda: self._stub_action("monitor.root_cause", dry_run),
            "monitor.disk_full": lambda: self._stub_action("monitor.disk_full", dry_run),
            "monitor.predict_failure": lambda: self._stub_action("monitor.predict_failure", dry_run),
            "autofix.config_mismatch": lambda: self._stub_action("autofix.config_mismatch", dry_run),
            "autofix.rollback_update": lambda: self._stub_action("autofix.rollback_update", dry_run),
            "wifi.disconnect": lambda: self.adapter.wifi_disconnect(
                interface=parameters.get("interface"),
                dry_run=dry_run
            ),
            "wifi.connect": lambda: self.adapter.wifi_connect(
                parameters["ssid"],
                password=parameters.get("password"),
                dry_run=dry_run
            ),
            "wifi.list": lambda: self.adapter.wifi_list(dry_run=dry_run),
            "env.configure_vars": lambda: self._configure_env_vars(
                parameters["var_name"],
                parameters["var_value"],
                scope=parameters.get("scope"),
                persist=parameters.get("persist"),
                dry_run=dry_run
            ),
            "env.setup_virtualenv": lambda: self._setup_virtualenv(
                parameters["path"],
                python_version=parameters.get("python_version"),
                requirements=parameters.get("requirements"),
                dry_run=dry_run
            ),
            "env.setup_python": lambda: self._setup_python(
                parameters["version"],
                install_path=parameters.get("install_path"),
                dry_run=dry_run
            ),
            "env.setup_node": lambda: self._setup_node(
                parameters["version"],
                install_path=parameters.get("install_path"),
                dry_run=dry_run
            ),
            "env.setup_java": lambda: self._setup_java(
                parameters["version"],
                install_path=parameters.get("install_path"),
                dry_run=dry_run
            ),
            "service.restart": lambda: self._restart_service(
                parameters["name"],
                timeout=parameters.get("timeout"),
                dry_run=dry_run
            ),
            "logs.check": lambda: self._check_logs(
                parameters["log_path"],
                lines=parameters.get("lines"),
                filter_text=parameters.get("filter_text")
            ),
            "logs.clear": lambda: self._clear_logs(
                parameters["log_path"],
                dry_run=dry_run
            ),
            "script.run": lambda: self._run_script(
                parameters["script_path"],
                params=parameters.get("params"),
                working_dir=parameters.get("working_dir"),
                dry_run=dry_run
            ),
            "deploy.local": lambda: self._deploy_local(
                parameters["artifact_path"],
                parameters["target_path"],
                backup=parameters.get("backup"),
                dry_run=dry_run
            ),
            "build.run": lambda: self._run_build(
                parameters["project_path"],
                build_command=parameters.get("build_command"),
                target=parameters.get("target"),
                dry_run=dry_run
            ),
            "git.pull": lambda: self._git_pull(
                parameters["repository_path"],
                branch=parameters.get("branch"),
                remote=parameters.get("remote"),
                dry_run=dry_run
            ),
            "docker.install": lambda: self._install_docker(
                version=parameters.get("version"),
                dry_run=dry_run
            ),
            "cache.clear": lambda: self._clear_cache(
                parameters["cache_type"],
                path=parameters.get("path"),
                dry_run=dry_run
            ),
            "file.rename": lambda: self.adapter.rename_file(
                parameters["path"],
                parameters["new_name"],
                dry_run=dry_run
            ),
            "file.search": lambda: self._search_files(
                parameters["pattern"],
                path=parameters.get("path"),
                recursive=parameters.get("recursive", True)
            ),
            "file.create": lambda: self.adapter.create_file(
                parameters["path"],
                content=parameters.get("content", ""),
                dry_run=dry_run
            ),
            "file.chmod": lambda: self._stub_action("file.chmod", dry_run),
            "file.compress": lambda: self._stub_action("file.compress", dry_run),
            "file.extract": lambda: self._stub_action("file.extract", dry_run),
            "disk.usage": lambda: self._stub_action("disk.usage", dry_run),
            "network.ping": lambda: self.adapter.network_ping(
                parameters["host"],
                count=parameters.get("count", 4),
                dry_run=dry_run
            ),
            "network.check_ip": lambda: self.adapter.network_check_ip(
                interface=parameters.get("interface")
            ),
            "network.flush_dns": lambda: self.adapter.network_flush_dns(dry_run=dry_run),
            "firewall.open_port": lambda: self._open_firewall_port(
                parameters["port"],
                protocol=parameters.get("protocol", "TCP"),
                rule_name=parameters.get("rule_name"),
                dry_run=dry_run
            ),
            "user.change_password": lambda: self._change_user_password(
                parameters["username"],
                new_password=parameters.get("new_password"),
                dry_run=dry_run
            ),
            "user.create": lambda: self._stub_action("user.create", dry_run),
            "user.delete": lambda: self._stub_action("user.delete", dry_run)
        }
        
        # Get action handler
        handler = action_map.get(action_id)
        
        if handler:
            return handler()
        else:
            from ..models.execution_result import ExecutionResult
            return ExecutionResult(
                success=False,
                message=f"Action not implemented: {action_id}",
                dry_run=dry_run
            )

    def _setup_virtualenv(self, path: str, python_version: Optional[str], requirements: Optional[str], dry_run: bool):
        from ..models.execution_result import ExecutionResult
        import subprocess
        import os
        import shutil
        
        full_path = os.path.abspath(path)
        venv_path = os.path.join(full_path, "venv")
        
        # Check if valid venv exists (has Scripts/activate or bin/activate)
        if os.path.exists(full_path):
            activate_win = os.path.join(full_path, "Scripts", "activate")
            activate_unix = os.path.join(full_path, "bin", "activate")
            if os.path.exists(activate_win) or os.path.exists(activate_unix):
                return ExecutionResult(success=False, message=f"Virtual environment already exists at {full_path}", dry_run=False)
            # Empty/incomplete folder - remove it
            shutil.rmtree(full_path, ignore_errors=True)
        
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would create venv at {venv_path}", dry_run=True)
        
        try:
            subprocess.run(["python", "-m", "venv", venv_path], check=True, timeout=180)
            return ExecutionResult(success=True, message=f"Virtual environment created at {venv_path}", dry_run=False)
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {e}", dry_run=False)

    def _setup_python(self, version: str, install_path: Optional[str], dry_run: bool):
        from ..models.execution_result import ExecutionResult
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would install Python {version}", dry_run=True)
        return ExecutionResult(success=False, message="Python installation requires manual setup", dry_run=False)

    def _setup_node(self, version: str, install_path: Optional[str], dry_run: bool):
        from ..models.execution_result import ExecutionResult
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would install Node.js {version}", dry_run=True)
        return ExecutionResult(success=False, message="Node.js installation requires manual setup", dry_run=False)

    def _setup_java(self, version: str, install_path: Optional[str], dry_run: bool):
        """Setup Java environment."""
        return self.adapter.setup_java(version, install_path, dry_run)

    def _configure_env_vars(self, var_name: str, var_value: str, scope: Optional[str], persist: Optional[bool], dry_run: bool):
        from ..models.execution_result import ExecutionResult
        import os
        import subprocess
        
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would set {var_name}={var_value}", dry_run=True)
        
        try:
            # Set in current session
            os.environ[var_name] = var_value
            
            # Set permanently in Windows system environment
            if persist or persist is None:  # Default to persistent
                scope_flag = "Machine" if scope == "system" else "User"
                cmd = ["setx", var_name, var_value]
                if scope == "system":
                    cmd.insert(0, "setx")
                    cmd.append("/M")  # Machine-level (requires admin)
                
                result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
                if result.returncode == 0:
                    return ExecutionResult(
                        success=True, 
                        message=f"Set environment variable {var_name}={var_value} (permanent for {scope_flag})", 
                        dry_run=False
                    )
                else:
                    return ExecutionResult(
                        success=True, 
                        message=f"Set {var_name}={var_value} in current session (permanent save failed: {result.stderr})", 
                        dry_run=False
                    )
            else:
                return ExecutionResult(
                    success=True, 
                    message=f"Set environment variable {var_name}={var_value} (current session only)", 
                    dry_run=False
                )
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {e}", dry_run=False)

    def _restart_service(self, service_name: str, timeout: Optional[int], dry_run: bool):
        from ..models.execution_result import ExecutionResult
        import subprocess
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would restart service {service_name}", dry_run=True)
        try:
            subprocess.run(["net", "stop", service_name], timeout=timeout or 120)
            subprocess.run(["net", "start", service_name], timeout=timeout or 120)
            return ExecutionResult(success=True, message=f"Service {service_name} restarted", dry_run=False)
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {e}", dry_run=False)

    def _check_logs(self, log_path: str, lines: Optional[int], filter_text: Optional[str]):
        from ..models.execution_result import ExecutionResult
        import os
        
        if not os.path.exists(log_path):
            return ExecutionResult(success=False, message=f"Path not found or does not exist: {log_path}", dry_run=False)
        
        try:
            if os.path.isfile(log_path):
                with open(log_path, 'r') as f:
                    content = f.readlines()
                    if lines:
                        content = content[-lines:]
                    if filter_text:
                        content = [l for l in content if filter_text in l]
                return ExecutionResult(success=True, message=f"Read {len(content)} log lines", data={"logs": "".join(content)}, dry_run=False)
            elif os.path.isdir(log_path):
                all_content = []
                for filename in sorted(os.listdir(log_path)):
                    if filename.endswith('.log'):
                        filepath = os.path.join(log_path, filename)
                        with open(filepath, 'r') as f:
                            all_content.extend(f.readlines())
                if lines:
                    all_content = all_content[-lines:]
                if filter_text:
                    all_content = [l for l in all_content if filter_text in l]
                return ExecutionResult(success=True, message=f"Read {len(all_content)} log lines from directory", data={"logs": "".join(all_content)}, dry_run=False)
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {e}", dry_run=False)

    def _clear_logs(self, log_path: str, dry_run: bool):
        from ..models.execution_result import ExecutionResult
        import os
        
        if not os.path.exists(log_path):
            return ExecutionResult(success=False, message=f"Path not found or does not exist: {log_path}", dry_run=False)
        
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would clear logs at {log_path}", dry_run=True)
        
        try:
            if os.path.isfile(log_path):
                open(log_path, 'w').close()
                return ExecutionResult(success=True, message=f"Cleared log file: {log_path}", dry_run=False)
            elif os.path.isdir(log_path):
                cleared_count = 0
                for filename in os.listdir(log_path):
                    if filename.endswith('.log'):
                        filepath = os.path.join(log_path, filename)
                        open(filepath, 'w').close()
                        cleared_count += 1
                return ExecutionResult(success=True, message=f"Cleared {cleared_count} log files in {log_path}", dry_run=False)
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {e}", dry_run=False)

    def _run_script(self, script_path: str, params: Optional[Dict], working_dir: Optional[str], dry_run: bool):
        from ..models.execution_result import ExecutionResult
        import subprocess
        import os
        
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would run script {script_path}", dry_run=True)
        
        if not os.path.exists(script_path):
            return ExecutionResult(success=False, message=f"Script not found: {script_path}", dry_run=False)
        
        if os.path.isdir(script_path):
            # Run all scripts in directory
            results = []
            for filename in sorted(os.listdir(script_path)):
                if filename.endswith(('.py', '.ps1', '.bat', '.cmd', '.sh')):
                    filepath = os.path.join(script_path, filename)
                    result = self._run_single_script(filepath, params, working_dir)
                    results.append(f"{filename}: {result.message}")
            
            if results:
                return ExecutionResult(success=True, message=f"Executed {len(results)} scripts\n" + "\n".join(results), dry_run=False)
            else:
                return ExecutionResult(success=False, message=f"No scripts found in directory: {script_path}", dry_run=False)
        
        return self._run_single_script(script_path, params, working_dir)
    
    def _run_single_script(self, script_path: str, params: Optional[Dict], working_dir: Optional[str]):
        from ..models.execution_result import ExecutionResult
        import subprocess
        import os
        
        try:
            ext = os.path.splitext(script_path)[1].lower()
            
            if ext == '.py':
                cmd = ['python', script_path]
            elif ext == '.ps1':
                cmd = ['powershell', '-ExecutionPolicy', 'Bypass', '-File', script_path]
            elif ext in ['.bat', '.cmd']:
                cmd = [script_path]
            elif ext == '.sh':
                cmd = ['bash', script_path]
            else:
                cmd = [script_path]
            
            if params:
                cmd.extend(list(params.values()))
            
            result = subprocess.run(cmd, cwd=working_dir, capture_output=True, text=True, timeout=300)
            
            if result.returncode == 0:
                return ExecutionResult(success=True, message=f"Success", data={"output": result.stdout}, dry_run=False)
            else:
                return ExecutionResult(success=False, message=f"Failed (exit {result.returncode})", data={"error": result.stderr}, dry_run=False)
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {e}", dry_run=False)

    def _deploy_local(self, artifact_path: str, target_path: str, backup: Optional[bool], dry_run: bool):
        from ..models.execution_result import ExecutionResult
        import shutil
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would deploy {artifact_path} to {target_path}", dry_run=True)
        try:
            if backup:
                shutil.copy2(target_path, f"{target_path}.backup")
            shutil.copy2(artifact_path, target_path)
            return ExecutionResult(success=True, message=f"Deployed {artifact_path} to {target_path}", dry_run=False)
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {e}", dry_run=False)

    def _run_build(self, project_path: str, build_command: Optional[str], target: Optional[str], dry_run: bool):
        from ..models.execution_result import ExecutionResult
        import subprocess
        import os
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would run build in {project_path}", dry_run=True)
        try:
            # Detect build system
            if not build_command:
                if os.path.exists(os.path.join(project_path, "setup.py")):
                    cmd = ["python", "setup.py", "build"]
                elif os.path.exists(os.path.join(project_path, "pyproject.toml")):
                    cmd = ["python", "-m", "build"]
                elif os.path.exists(os.path.join(project_path, "package.json")):
                    cmd = ["npm", "run", "build"]
                elif os.path.exists(os.path.join(project_path, "pom.xml")):
                    cmd = ["mvn", "package"]
                elif os.path.exists(os.path.join(project_path, "build.gradle")):
                    cmd = ["gradle", "build"]
                else:
                    return ExecutionResult(success=False, message=f"No build system detected in {project_path}", dry_run=False)
            else:
                cmd = build_command.split()
            
            result = subprocess.run(cmd, cwd=project_path, capture_output=True, text=True, timeout=600)
            success = result.returncode == 0
            output = result.stdout if result.stdout else result.stderr
            return ExecutionResult(
                success=success, 
                message=f"Build {'completed' if success else 'failed'}", 
                data={"output": output, "returncode": result.returncode}, 
                dry_run=False
            )
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {e}", dry_run=False)

    def _git_pull(self, repository_path: str, branch: Optional[str], remote: Optional[str], dry_run: bool):
        from ..models.execution_result import ExecutionResult
        import subprocess
        import os
        
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would pull from {remote or 'origin'}/{branch or 'current'}", dry_run=True)
        
        # Check if directory exists and is a git repository
        if not os.path.exists(repository_path):
            return ExecutionResult(
                success=False, 
                message=f"Repository path does not exist: {repository_path}. Please clone the repository first.",
                dry_run=False
            )
        
        git_dir = os.path.join(repository_path, ".git")
        if not os.path.exists(git_dir):
            return ExecutionResult(
                success=False,
                message=f"Not a git repository: {repository_path}. Please clone the repository first.",
                dry_run=False
            )
        
        try:
            # Build git pull command: git pull <remote> <branch>
            cmd = ["git", "pull"]
            if remote:
                cmd.append(remote)
            if branch:
                cmd.append(branch)
            
            result = subprocess.run(cmd, cwd=repository_path, capture_output=True, text=True, timeout=120)
            
            if result.returncode == 0:
                return ExecutionResult(
                    success=True, 
                    message=f"Git pull completed successfully", 
                    data={"output": result.stdout or result.stderr}, 
                    dry_run=False
                )
            else:
                return ExecutionResult(
                    success=False, 
                    message=f"Git pull failed: {result.stderr or result.stdout}", 
                    data={"output": result.stderr or result.stdout}, 
                    dry_run=False
                )
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {e}", dry_run=False)

    def _install_docker(self, version: Optional[str], dry_run: bool):
        """Install Docker Desktop using winget."""
        from ..models.execution_result import ExecutionResult
        import subprocess
        
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would install Docker {version or 'latest'}", dry_run=True)
        
        try:
            # Use winget to install Docker Desktop
            print("[Install] Installing Docker Desktop...")
            cmd = 'winget install Docker.DockerDesktop --source winget --accept-source-agreements --accept-package-agreements'
            if version:
                cmd += f' --version {version}'
            
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=600)
            
            if result.returncode == 0:
                return ExecutionResult(
                    success=True, 
                    message="Docker Desktop installed successfully. Please restart your system to complete installation.",
                    data={"output": result.stdout},
                    dry_run=False
                )
            else:
                return ExecutionResult(
                    success=False, 
                    message=f"Docker installation failed: {result.stderr}",
                    dry_run=False
                )
        except subprocess.TimeoutExpired:
            return ExecutionResult(success=False, message="Installation timed out after 10 minutes", dry_run=False)
        except Exception as e:
            return ExecutionResult(success=False, message=f"Installation failed: {str(e)}", dry_run=False)

    def _clear_cache(self, cache_type: str, path: Optional[str], dry_run: bool):
        from ..models.execution_result import ExecutionResult
        import shutil
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would clear {cache_type} cache", dry_run=True)
        try:
            if path:
                shutil.rmtree(path, ignore_errors=True)
            return ExecutionResult(success=True, message=f"Cleared {cache_type} cache", dry_run=False)
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {e}", dry_run=False)

    def _search_files(self, pattern: str, path: Optional[str] = None, recursive: bool = True):
        """Search for files matching pattern."""
        from ..models.execution_result import ExecutionResult
        import os
        import fnmatch
        
        search_root = path if path else os.path.expanduser("~")
        matches = []
        
        try:
            if recursive:
                # Recursive search (limit depth to avoid long searches)
                for root, dirs, files in os.walk(search_root):
                    # Skip system directories
                    dirs[:] = [d for d in dirs if d not in ['.git', 'node_modules', '__pycache__', 'venv', '.venv', 'AppData']]
                    
                    for filename in files:
                        if fnmatch.fnmatch(filename.lower(), pattern.lower()):
                            matches.append(os.path.join(root, filename))
                    
                    # Limit results to prevent overwhelming output
                    if len(matches) >= 50:
                        break
            else:
                # Non-recursive search
                for filename in os.listdir(search_root):
                    filepath = os.path.join(search_root, filename)
                    if os.path.isfile(filepath) and fnmatch.fnmatch(filename.lower(), pattern.lower()):
                        matches.append(filepath)
            
            if matches:
                match_list = "\n".join(matches[:50])
                return ExecutionResult(
                    success=True,
                    message=f"Found {len(matches)} file(s) matching '{pattern}':\n{match_list}",
                    data={"matches": matches, "count": len(matches)},
                    dry_run=False
                )
            else:
                return ExecutionResult(
                    success=True,
                    message=f"No files found matching '{pattern}'",
                    data={"matches": [], "count": 0},
                    dry_run=False
                )
        except Exception as e:
            return ExecutionResult(success=False, message=f"Search failed: {e}", dry_run=False)

    def _stub_action(self, action_id: str, dry_run: bool):
        from ..models.execution_result import ExecutionResult
        if dry_run:
            return ExecutionResult(success=True, message=f"[DRY RUN] Would execute {action_id}", dry_run=True)
        return ExecutionResult(success=False, message=f"{action_id} requires elevated privileges or manual configuration", dry_run=False, metadata={"requires_admin": True})

    def _open_firewall_port(self, port: int, protocol: str, rule_name: Optional[str], dry_run: bool):
        """Open firewall port using netsh."""
        from ..models.execution_result import ExecutionResult
        import subprocess
        import ctypes
        
        if not rule_name:
            rule_name = f"Allow_{protocol}_{port}"
        
        if dry_run:
            return ExecutionResult(
                success=True, 
                message=f"[DRY RUN] Would open port {port}/{protocol} with rule '{rule_name}'",
                dry_run=True
            )
        
        # Check if running as administrator
        try:
            is_admin = ctypes.windll.shell32.IsUserAnAdmin()
        except:
            is_admin = False
        
        if not is_admin:
            return ExecutionResult(
                success=False,
                message=f"Cannot open port {port}: Administrator privileges required. Please restart UniShell as Administrator (right-click → Run as administrator).",
                dry_run=False,
                metadata={"requires_admin": True, "action": "firewall.open_port"}
            )
        
        try:
            # Add firewall rule to allow inbound traffic on the specified port
            cmd = f'netsh advfirewall firewall add rule name="{rule_name}" dir=in action=allow protocol={protocol} localport={port}'
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
            
            if result.returncode == 0:
                return ExecutionResult(
                    success=True,
                    message=f"Firewall rule created: Port {port}/{protocol} opened",
                    data={"rule_name": rule_name, "port": port, "protocol": protocol},
                    dry_run=False
                )
            else:
                error_msg = result.stderr.strip() if result.stderr else result.stdout.strip()
                if not error_msg:
                    error_msg = "Access denied. This operation requires administrator privileges. Please run as administrator."
                return ExecutionResult(
                    success=False,
                    message=f"Failed to create firewall rule: {error_msg}",
                    dry_run=False,
                    metadata={"requires_admin": True}
                )
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {str(e)}", dry_run=False)

    def _change_user_password(self, username: str, new_password: Optional[str], dry_run: bool):
        """Change user password using net user command."""
        from ..models.execution_result import ExecutionResult
        import subprocess
        
        if not new_password:
            return ExecutionResult(
                success=False,
                message="new_password is required",
                dry_run=False
            )
        
        if dry_run:
            return ExecutionResult(
                success=True,
                message=f"[DRY RUN] Would change password for user '{username}'",
                dry_run=True
            )
        
        try:
            # Change password using net user command
            cmd = f'net user "{username}" "{new_password}"'
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
            
            if result.returncode == 0:
                return ExecutionResult(
                    success=True,
                    message=f"Password changed successfully for user '{username}'",
                    data={"username": username},
                    dry_run=False
                )
            else:
                error_msg = result.stderr.strip() if result.stderr else result.stdout.strip()
                if not error_msg:
                    error_msg = "Access denied. This operation requires administrator privileges."
                return ExecutionResult(
                    success=False,
                    message=f"Failed to change password: {error_msg}",
                    dry_run=False,
                    metadata={"requires_admin": True}
                )
        except Exception as e:
            return ExecutionResult(success=False, message=f"Failed: {str(e)}", dry_run=False)
