"""
GUI DCC Server Manager

Connects to DCC applications running in GUI mode with Plumber addon installed.
Unlike headless server managers, this does NOT launch any process.
It detects running GUI DCCs by polling for ready.json in known IPC directories.

Supports: Houdini, Maya, Blender, Unreal

Interface is compatible with headless server managers (execute_command, is_alive,
get_status, shutdown) so it can be used as a drop-in replacement in dcc_executor.py.
"""

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

logger = logging.getLogger(__name__)


class GUIServerManager:
    """
    Manages connection to a DCC running in GUI mode with Plumber addon.

    This manager does NOT launch any process. It connects to an already-running
    DCC application that has the Plumber GUI server addon active.
    """

    def __init__(self, dcc_name: str, base_dir: Optional[Path] = None):
        """
        Args:
            dcc_name: "houdini", "maya", or "blender"
            base_dir: Override IPC directory (default: temp/plumber_{dcc}_gui)
        """
        self.dcc_name = dcc_name
        self.is_ready = False
        self.gui_dcc_version: Optional[str] = None
        self.gui_dcc_pid: Optional[int] = None

        if base_dir is None:
            base_dir = Path(tempfile.gettempdir()) / f"plumber_{dcc_name}_gui"

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

    def check_gui_available(self) -> bool:
        """
        Check if a GUI DCC instance is running and has Plumber addon active.
        Reads ready.json to determine availability and validates the PID is alive.
        """
        if not self.ready_file.exists():
            if self.is_ready:
                logger.info(f"[GUI] {self.dcc_name} GUI server no longer available (ready.json removed)")
                self.is_ready = False
                self.gui_dcc_version = None
                self.gui_dcc_pid = None
            return False

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

            if ready_data.get('status') != 'ready' or ready_data.get('mode') != 'gui':
                return False

            pid = ready_data.get('pid')
            if pid and not self._is_pid_alive(pid):
                logger.warning(f"[GUI] {self.dcc_name} GUI DCC PID {pid} no longer alive, cleaning up")
                self._cleanup_stale()
                return False

            version_key = f"{self.dcc_name}_version"
            self.gui_dcc_version = ready_data.get(version_key, 'unknown')
            self.gui_dcc_pid = pid
            self.is_ready = True
            return True

        except (json.JSONDecodeError, IOError, OSError) as e:
            logger.debug(f"[GUI] Error reading {self.dcc_name} ready.json: {e}")
            return False

    def _is_pid_alive(self, pid: int) -> bool:
        """Check if a process with the given PID is still running."""
        try:
            if os.name == 'nt':
                import ctypes
                kernel32 = ctypes.windll.kernel32
                PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
                handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
                if handle:
                    kernel32.CloseHandle(handle)
                    return True
                return False
            else:
                os.kill(pid, 0)
                return True
        except (OSError, ProcessLookupError):
            return False

    def _cleanup_stale(self):
        """Remove stale IPC files from a dead GUI DCC."""
        self.is_ready = False
        self.gui_dcc_version = None
        self.gui_dcc_pid = None
        for f in [self.ready_file, self.command_file, self.response_file]:
            if f.exists():
                try:
                    f.unlink()
                except OSError:
                    pass

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

        Same interface as headless server managers' execute_command().

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

        Returns:
            Dict with result (success, result, error, execution_time, total_time, mode)

        Raises:
            RuntimeError: If GUI server is not available
            TimeoutError: If command times out
        """
        if not self.is_ready:
            raise RuntimeError(f"{self.dcc_name} GUI server not available")

        if not self.check_gui_available():
            raise RuntimeError(f"{self.dcc_name} GUI DCC is no longer running")

        logger.debug(f"[GUI] Executing {self.dcc_name} command: {operation_type}")
        request_start = time.time()

        # Clean old response
        if self.response_file.exists():
            try:
                self.response_file.unlink()
            except OSError:
                pass

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

        try:
            with open(self.command_file, 'w') as f:
                json.dump(command_data, f, indent=2)
        except Exception as e:
            raise RuntimeError(f"Failed to write command file: {e}")

        # Wait for response
        poll_interval = 0.05  # 50ms
        max_attempts = int(timeout / poll_interval)

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

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

                    # Clean up response file
                    try:
                        self.response_file.unlink()
                    except OSError:
                        pass

                    logger.debug(f"[GUI] {self.dcc_name} {operation_type} completed in {total_time*1000:.0f}ms")
                    return result

                except (json.JSONDecodeError, IOError):
                    pass  # File not fully written yet

            time.sleep(poll_interval)

        elapsed = time.time() - request_start
        raise TimeoutError(f"{self.dcc_name} GUI command '{operation_type}' timed out after {elapsed:.1f}s")

    def is_alive(self) -> bool:
        """Check if GUI DCC is still available. Compatible with headless manager interface."""
        return self.check_gui_available()

    def get_status(self) -> Dict[str, Any]:
        """Get GUI server status."""
        return {
            'is_ready': self.is_ready,
            'is_alive': self.is_alive(),
            'mode': 'gui',
            'dcc_name': self.dcc_name,
            'dcc_version': self.gui_dcc_version,
            'pid': self.gui_dcc_pid,
            'ipc_directory': str(self.base_dir)
        }

    def shutdown(self):
        """No-op for GUI manager -- we don't own the DCC process."""
        self.is_ready = False
