"""Read-only server registry for MCP that works with gdsfactoryplus registry.

This module provides read-only access to the gdsfactoryplus server registry,
allowing the MCP to discover and connect to running GDSFactory+ server instances.
The registry file is managed by gdsfactoryplus and located at:
~/.gdsfactory/server-registry.json
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

try:
    import psutil

    HAS_PSUTIL = True
except ImportError:
    HAS_PSUTIL = False

__all__ = ["ServerRegistry", "ServerInfo", "get_registry_path"]


def get_registry_path() -> Path:
    """Get the path to the server registry file.

    Returns:
        Path to ~/.gdsfactory/server-registry.json
    """
    gdsfactory_dir = Path.home() / ".gdsfactory"
    gdsfactory_dir.mkdir(parents=True, exist_ok=True)
    return gdsfactory_dir / "server-registry.json"


class ServerInfo:
    """Information about a running GDSFactory+ server.

    This class is compatible with gdsfactoryplus.registry.ServerInfo
    but doesn't require the full gdsfactoryplus package.
    """

    def __init__(
        self,
        port: int,
        pid: int,
        project_path: str,
        project_name: str,
        pdk: str | None = None,
        started_at: str | None = None,
        last_heartbeat: str | None = None,
    ) -> None:
        """Initialize server info.

        Args:
            port: HTTP server port
            pid: Process ID
            project_path: Absolute path to project directory
            project_name: Human-readable project name
            pdk: PDK name (optional)
            started_at: ISO timestamp when server started
            last_heartbeat: ISO timestamp of last heartbeat
        """
        self.port = port
        self.pid = pid
        self.project_path = project_path
        self.project_name = project_name
        self.pdk = pdk
        self.started_at = started_at
        self.last_heartbeat = last_heartbeat

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "port": self.port,
            "pid": self.pid,
            "project_path": self.project_path,
            "project_name": self.project_name,
            "pdk": self.pdk,
            "started_at": self.started_at,
            "last_heartbeat": self.last_heartbeat,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> ServerInfo:
        """Create from dictionary."""
        return cls(
            port=data["port"],
            pid=data["pid"],
            project_path=data["project_path"],
            project_name=data["project_name"],
            pdk=data.get("pdk"),
            started_at=data.get("started_at"),
            last_heartbeat=data.get("last_heartbeat"),
        )

    def is_alive(self) -> bool:
        """Check if the server process is still running.

        Returns:
            True if process exists, False otherwise
            If psutil is not available, always returns True
        """
        if not HAS_PSUTIL:
            # Without psutil, assume the process is alive
            # The registry cleanup is handled by gdsfactoryplus
            return True

        try:
            process = psutil.Process(self.pid)
            return process.is_running()
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            return False


class ServerRegistry:
    """Read-only registry for discovering active GDSFactory+ servers.

    This registry reads from the same file as gdsfactoryplus.registry.ServerRegistry
    (~/.gdsfactory/server-registry.json) but provides only read access.

    The registry file is managed by the GDSFactory+ servers themselves.
    This MCP implementation only reads from it to discover running servers.
    """

    def __init__(self, registry_path: Path | None = None) -> None:
        """Initialize registry with read-only access.

        Args:
            registry_path: Optional custom path to registry file
        """
        self.registry_path = registry_path or get_registry_path()

    def _read_registry(self) -> dict[str, Any]:
        """Read the registry file.

        Returns:
            Registry data as dictionary
        """
        if not self.registry_path.exists():
            return {"servers": {}}

        try:
            with self.registry_path.open() as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            return {"servers": {}}

    def get_server(self, port: int) -> ServerInfo | None:
        """Get server info by port.

        Args:
            port: Port number

        Returns:
            ServerInfo if found and alive, None otherwise
        """
        data = self._read_registry()
        port_key = str(port)

        if port_key not in data["servers"]:
            return None

        server_info = ServerInfo.from_dict(data["servers"][port_key])

        # Check if process is still alive (if psutil is available)
        if HAS_PSUTIL and not server_info.is_alive():
            return None

        return server_info

    def get_server_by_project(self, project: str) -> ServerInfo | None:
        """Get server info by project name or path.

        Args:
            project: Project name or path

        Returns:
            Server info if found and alive, None otherwise
        """
        for server in self.list_servers():
            if project in {
                server.project_name,
                server.project_path,
                Path(server.project_path).name,
            }:
                return server
        return None

    def list_servers(self, *, include_dead: bool = False) -> list[ServerInfo]:
        """List all registered servers.

        Args:
            include_dead: Include servers with dead processes (default: False)
                         Only effective if psutil is available

        Returns:
            List of ServerInfo objects
        """
        data = self._read_registry()
        servers = []

        for server_data in data["servers"].values():
            server_info = ServerInfo.from_dict(server_data)

            # Include all servers if psutil is not available or include_dead is True
            if not HAS_PSUTIL or include_dead or server_info.is_alive():
                servers.append(server_info)

        return servers
