"""
Unreal Engine Persistent Server Manager (File-Based IPC)

Manages the lifecycle of the persistent Unreal Engine server using file-based communication.

Architecture:
- Command file: Agent writes commands here
- Response file: UE writes results here
- Ready file: Signals server is initialized

Key difference from Maya/Blender/Houdini:
- Unreal has no standalone Python (no "unrealpy.exe")
- Must launch UnrealEditor-Cmd.exe with -ExecutePythonScript flag
- Requires a .uproject file (even a minimal temp one)
- Startup is slower (~30-120s due to shader compilation on first launch)
"""

import subprocess
import time
import os
import logging
import json
import tempfile
from pathlib import Path
from typing import Optional, Dict, Any

logger = logging.getLogger(__name__)


class UnrealServerManagerFileBased:
    """
    Manages persistent Unreal Engine server using file-based IPC.

    The server is launched once when the agent starts and handles all UE
    operations via file queues, avoiding the heavy restart overhead.
    """

    def __init__(self, base_dir: Optional[Path] = None,
                 unreal_cmd_path: Optional[str] = None,
                 project_path: Optional[str] = None):
        """
        Initialize Unreal server manager.

        Args:
            base_dir: Base directory for IPC files (default: temp directory)
            unreal_cmd_path: Path to UnrealEditor-Cmd.exe (auto-detected if None)
            project_path: Path to .uproject file (creates minimal temp if None)
        """
        self.unreal_process: Optional[subprocess.Popen] = None
        self.is_ready = False
        self.server_startup_time = 0.0

        # Setup IPC file paths
        if base_dir is None:
            base_dir = Path(tempfile.gettempdir()) / "plumber_unreal_persistent"

        self.base_dir = Path(base_dir)
        self.base_dir.mkdir(parents=True, exist_ok=True)

        self.command_file = self.base_dir / "command.json"
        self.response_file = self.base_dir / "response.json"
        self.ready_file = self.base_dir / "ready.json"

        # Find Unreal installation
        self.unreal_cmd_path = unreal_cmd_path or self._find_unreal_cmd()
        self.project_path = project_path or self._get_or_create_temp_project()

    def _find_unreal_cmd(self) -> str:
        """Find UnrealEditor-Cmd.exe path."""
        import glob as glob_mod

        # Check common UE installation paths (Windows)
        search_patterns = [
            r"C:\Program Files\Epic Games\UE_5.*\Engine\Binaries\Win64\UnrealEditor-Cmd.exe",
            r"C:\Program Files\Epic Games\UE_*\Engine\Binaries\Win64\UnrealEditor-Cmd.exe",
            # WSL paths
            "/mnt/c/Program Files/Epic Games/UE_5.*/Engine/Binaries/Win64/UnrealEditor-Cmd.exe",
            "/mnt/c/Program Files/Epic Games/UE_*/Engine/Binaries/Win64/UnrealEditor-Cmd.exe",
        ]

        for pattern in search_patterns:
            matches = sorted(glob_mod.glob(pattern), reverse=True)
            if matches:
                logger.info(f"Found UnrealEditor-Cmd at: {matches[0]}")
                return matches[0]

        # Check Epic Games Launcher manifest
        manifest_path = r"C:\ProgramData\Epic\UnrealEngineLauncher\LauncherInstalled.dat"
        wsl_manifest = "/mnt/c/ProgramData/Epic/UnrealEngineLauncher/LauncherInstalled.dat"

        for mpath in [manifest_path, wsl_manifest]:
            if os.path.exists(mpath):
                try:
                    with open(mpath, 'r') as f:
                        manifest = json.load(f)
                    for item in manifest.get("InstallationList", []):
                        install_loc = item.get("InstallLocation", "")
                        cmd_path = os.path.join(install_loc, "Engine", "Binaries", "Win64", "UnrealEditor-Cmd.exe")
                        if os.path.exists(cmd_path):
                            logger.info(f"Found UnrealEditor-Cmd via launcher manifest: {cmd_path}")
                            return cmd_path
                except Exception as e:
                    logger.debug(f"Error reading launcher manifest: {e}")

        logger.warning("UnrealEditor-Cmd.exe not found in standard locations")
        return ""

    def _get_or_create_temp_project(self) -> str:
        """Get or create a minimal .uproject file for headless execution."""
        project_dir = self.base_dir / "PlumberTemp"
        project_file = project_dir / "PlumberTemp.uproject"

        if project_file.exists():
            return str(project_file)

        project_dir.mkdir(parents=True, exist_ok=True)

        minimal_uproject = {
            "FileVersion": 3,
            "EngineAssociation": "",
            "Category": "",
            "Description": "Plumber Agent temporary project for headless execution",
            "Plugins": [
                {
                    "Name": "PythonScriptPlugin",
                    "Enabled": True
                },
                {
                    "Name": "EditorScriptingUtilities",
                    "Enabled": True
                }
            ]
        }

        with open(project_file, 'w') as f:
            json.dump(minimal_uproject, f, indent=2)

        logger.info(f"Created minimal temp .uproject at: {project_file}")
        return str(project_file)

    def start_server(self, timeout: int = 120) -> bool:
        """
        Start Unreal persistent server.

        Args:
            timeout: Maximum seconds to wait for server startup (default 120s for UE)

        Returns:
            True if server started successfully, False otherwise
        """
        logger.info("Starting Unreal Engine persistent server (file-based IPC)...")

        if not self.unreal_cmd_path or not os.path.exists(self.unreal_cmd_path):
            logger.error(f"UnrealEditor-Cmd.exe not found: {self.unreal_cmd_path}")
            return False

        start_time = time.time()

        # Find server script
        server_script = Path(__file__).parent.parent / "unreal_persistent_server_filebased.py"

        if not server_script.exists():
            logger.error(f"Server script not found: {server_script}")
            return False

        logger.info(f"Server script: {server_script}")
        logger.info(f"UnrealEditor-Cmd: {self.unreal_cmd_path}")
        logger.info(f"Project: {self.project_path}")
        logger.info(f"IPC directory: {self.base_dir}")

        # Clean up old IPC files
        for f in [self.command_file, self.response_file, self.ready_file]:
            if f.exists():
                try:
                    f.unlink()
                except:
                    pass

        try:
            # Build UE command line
            # -ExecutePythonScript runs a Python script inside the Editor
            # Convert paths for Windows if running in WSL
            script_path = str(server_script)
            if script_path.startswith("/mnt/c"):
                script_path = "C:" + script_path[6:].replace("/", "\\")

            project_arg = self.project_path
            if project_arg.startswith("/mnt/c"):
                project_arg = "C:" + project_arg[6:].replace("/", "\\")

            cmd_file_arg = str(self.command_file)
            resp_file_arg = str(self.response_file)
            ready_file_arg = str(self.ready_file)

            if cmd_file_arg.startswith("/mnt/c"):
                cmd_file_arg = "C:" + cmd_file_arg[6:].replace("/", "\\")
                resp_file_arg = "C:" + resp_file_arg[6:].replace("/", "\\")
                ready_file_arg = "C:" + ready_file_arg[6:].replace("/", "\\")

            # Build the Python script argument with IPC paths embedded
            python_script_arg = (
                f'-ExecutePythonScript="{script_path} '
                f'--command-file {cmd_file_arg} '
                f'--response-file {resp_file_arg} '
                f'--ready-file {ready_file_arg}"'
            )

            cmd = [
                self.unreal_cmd_path,
                project_arg,
                python_script_arg,
                "-stdout",
                "-FullStdOutLogOutput",
                "-unattended",
                "-nosplash",
                "-nullrhi",  # No rendering (headless)
            ]

            # Platform-specific process creation
            if os.name == 'nt':
                CREATE_NO_WINDOW = 0x08000000
                creation_flags = CREATE_NO_WINDOW
            else:
                creation_flags = 0

            self.unreal_process = subprocess.Popen(
                cmd,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                creationflags=creation_flags
            )

            logger.info(f"Process started with PID: {self.unreal_process.pid}")

            # Wait for ready file (UE is heavy — longer timeout)
            logger.info("Waiting for Unreal Engine to initialize (this may take 30-120s)...")

            for attempt in range(timeout * 10):  # Check every 0.1s
                if self.unreal_process.poll() is not None:
                    logger.error(f"Unreal server process died (exit code: {self.unreal_process.returncode})")
                    return False

                if self.ready_file.exists():
                    try:
                        with open(self.ready_file, 'r') as f:
                            ready_data = json.load(f)

                        self.server_startup_time = time.time() - start_time
                        self.is_ready = True

                        logger.info("Unreal Engine persistent server ready!")
                        logger.info(f"   Startup time: {self.server_startup_time:.2f}s")
                        logger.info(f"   UE version: {ready_data.get('unreal_version')}")
                        logger.info(f"   PID: {ready_data.get('pid')}")

                        return True

                    except (json.JSONDecodeError, IOError):
                        pass

                time.sleep(0.1)

            elapsed = time.time() - start_time
            logger.error(f"Unreal server failed to start within {timeout}s (waited {elapsed:.1f}s)")
            self._cleanup_process()
            return False

        except Exception as e:
            logger.error(f"Failed to start Unreal server: {e}")
            import traceback
            logger.debug(traceback.format_exc())
            self._cleanup_process()
            return False

    def execute_command(self, command: str, operation_type: str = "generic",
                        timeout: int = 60) -> Dict[str, Any]:
        """
        Execute Unreal command via persistent server.

        Args:
            command: Python code to execute in Unreal Editor context
            operation_type: Type of operation (for logging)
            timeout: Maximum seconds to wait for response

        Returns:
            Dictionary with result

        Raises:
            RuntimeError: If server is not ready or request fails
        """
        if not self.is_ready:
            raise RuntimeError("Unreal server not ready - call start_server() first")

        logger.debug(f"Executing Unreal command: {operation_type}")

        try:
            start_time = time.time()

            command_data = {
                'command': command,
                'operation_type': operation_type
            }

            with open(self.command_file, 'w') as f:
                json.dump(command_data, f)

            # Wait for response
            for attempt in range(timeout * 10):
                if self.unreal_process.poll() is not None:
                    raise RuntimeError(
                        f"Unreal server crashed (exit code: {self.unreal_process.returncode})"
                    )

                if self.response_file.exists():
                    try:
                        with open(self.response_file, 'r') as f:
                            result = json.load(f)

                        try:
                            self.response_file.unlink()
                        except:
                            pass

                        total_time = time.time() - start_time
                        result['total_time'] = total_time

                        if result.get('success'):
                            logger.debug(f"{operation_type} completed in {total_time*1000:.1f}ms")
                        else:
                            logger.warning(f"{operation_type} failed: {result.get('error')}")

                        return result

                    except (json.JSONDecodeError, IOError):
                        pass

                time.sleep(0.1)

            raise RuntimeError(f"Unreal command timed out after {timeout}s")

        except Exception as e:
            logger.error(f"Unreal command failed: {e}")
            raise

    def check_health(self) -> bool:
        """Check if Unreal server is healthy."""
        if not self.is_ready:
            return False
        if self.unreal_process and self.unreal_process.poll() is None:
            return True
        self.is_ready = False
        return False

    def is_alive(self) -> bool:
        """Alias for check_health() — compatible with GUIServerManager interface."""
        return self.check_health()

    def get_server_info(self) -> Optional[Dict[str, Any]]:
        """Get server information."""
        if not self.is_ready:
            return None
        try:
            if self.ready_file.exists():
                with open(self.ready_file, 'r') as f:
                    return json.load(f)
        except:
            return None
        return None

    def _cleanup_process(self):
        """Internal method to cleanup Unreal process."""
        if self.unreal_process:
            try:
                self.unreal_process.terminate()
                self.unreal_process.wait(timeout=10)
            except subprocess.TimeoutExpired:
                self.unreal_process.kill()
            except:
                pass
            finally:
                self.unreal_process = None

    def shutdown(self):
        """Shutdown Unreal persistent server gracefully."""
        logger.info("Shutting down Unreal Engine persistent server...")

        if self.unreal_process:
            try:
                self.unreal_process.terminate()
                try:
                    self.unreal_process.wait(timeout=10)
                    logger.info("Unreal server terminated gracefully")
                except subprocess.TimeoutExpired:
                    logger.warning("Unreal server didn't terminate, forcing kill...")
                    self.unreal_process.kill()
                    self.unreal_process.wait()
            except Exception as e:
                logger.error(f"Error shutting down Unreal server: {e}")
            finally:
                self.unreal_process = None
                self.is_ready = False

        # Clean up IPC files
        for f in [self.command_file, self.response_file, self.ready_file]:
            if f.exists():
                try:
                    f.unlink()
                except:
                    pass

    def __del__(self):
        """Destructor - ensure server is shutdown."""
        if self.unreal_process:
            self.shutdown()
