"""
LSP Server Manager - Manage multiple LSP servers by language

Handles server lifecycle for different programming languages,
including automatic server selection, startup, and restart on crash.

Examples:
    manager = LSPServerManager()

    # Get client for a file (starts server if needed)
    client = manager.get_client("file:///project/app.py")
    result = client.hover("file:///project/app.py", line=10, character=5)

    # Shutdown all servers
    manager.shutdown_all()
"""

import logging
import threading
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional
from urllib.parse import unquote, urlparse

from groknroll.lsp.client import LSPClient, LSPClientError

logger = logging.getLogger(__name__)


@dataclass
class ServerConfig:
    """Configuration for an LSP server"""

    language_id: str
    command: str
    args: list[str] = field(default_factory=list)
    extensions: list[str] = field(default_factory=list)
    root_patterns: list[str] = field(default_factory=list)
    initialization_options: dict[str, Any] = field(default_factory=dict)


# Default server configurations
DEFAULT_SERVERS: dict[str, ServerConfig] = {
    "python": ServerConfig(
        language_id="python",
        command="pyright-langserver",
        args=["--stdio"],
        extensions=[".py", ".pyi", ".pyw"],
        root_patterns=["pyproject.toml", "setup.py", "requirements.txt"],
    ),
    "typescript": ServerConfig(
        language_id="typescript",
        command="typescript-language-server",
        args=["--stdio"],
        extensions=[".ts", ".tsx"],
        root_patterns=["tsconfig.json", "package.json"],
    ),
    "javascript": ServerConfig(
        language_id="javascript",
        command="typescript-language-server",
        args=["--stdio"],
        extensions=[".js", ".jsx", ".mjs", ".cjs"],
        root_patterns=["package.json", "jsconfig.json"],
    ),
    "rust": ServerConfig(
        language_id="rust",
        command="rust-analyzer",
        args=[],
        extensions=[".rs"],
        root_patterns=["Cargo.toml"],
    ),
    "go": ServerConfig(
        language_id="go",
        command="gopls",
        args=[],
        extensions=[".go"],
        root_patterns=["go.mod", "go.sum"],
    ),
    "c": ServerConfig(
        language_id="c",
        command="clangd",
        args=[],
        extensions=[".c", ".h"],
        root_patterns=["compile_commands.json", "CMakeLists.txt", "Makefile"],
    ),
    "cpp": ServerConfig(
        language_id="cpp",
        command="clangd",
        args=[],
        extensions=[".cpp", ".hpp", ".cc", ".hh", ".cxx", ".hxx"],
        root_patterns=["compile_commands.json", "CMakeLists.txt", "Makefile"],
    ),
}


class LSPServerManager:
    """
    Manage multiple LSP servers by language

    Automatically starts and manages LSP servers for different
    programming languages based on file extensions.

    Example:
        manager = LSPServerManager(root_uri="file:///project")

        # Get client for Python file (starts pyright if needed)
        client = manager.get_client("file:///project/app.py")

        # Use the client
        result = client.hover("file:///project/app.py", 10, 5)

        # Shutdown all servers
        manager.shutdown_all()
    """

    def __init__(
        self,
        root_uri: Optional[str] = None,
        servers: Optional[dict[str, ServerConfig]] = None,
        auto_restart: bool = True,
        timeout: float = 30.0,
    ):
        """
        Initialize server manager

        Args:
            root_uri: Root URI for workspace (auto-detected if None)
            servers: Server configurations (uses defaults if None)
            auto_restart: Automatically restart crashed servers
            timeout: Default timeout for requests
        """
        self.root_uri = root_uri
        self.servers = servers or DEFAULT_SERVERS.copy()
        self.auto_restart = auto_restart
        self.timeout = timeout

        self._clients: dict[str, LSPClient] = {}
        self._lock = threading.Lock()

        # Build extension to language mapping
        self._ext_to_lang: dict[str, str] = {}
        for lang, config in self.servers.items():
            for ext in config.extensions:
                self._ext_to_lang[ext.lower()] = lang

    def get_client(self, uri: str) -> Optional[LSPClient]:
        """
        Get LSP client for a file URI

        Starts the appropriate server if not already running.

        Args:
            uri: File URI (file:///path/to/file.ext)

        Returns:
            LSPClient for the file's language, or None if unsupported
        """
        language = self.detect_language(uri)
        if not language:
            logger.debug(f"No language detected for: {uri}")
            return None

        return self.get_client_for_language(language)

    def get_client_for_language(self, language: str) -> Optional[LSPClient]:
        """
        Get LSP client for a specific language

        Args:
            language: Language identifier (python, typescript, etc.)

        Returns:
            LSPClient or None if language not configured
        """
        if language not in self.servers:
            logger.warning(f"No server configured for language: {language}")
            return None

        with self._lock:
            # Check if client exists and is running
            if language in self._clients:
                client = self._clients[language]
                if client.is_running:
                    return client
                elif self.auto_restart:
                    # Server crashed, try to restart
                    logger.warning(f"Server crashed for {language}, restarting...")
                    self._start_server(language)
                    return self._clients.get(language)
                else:
                    # Remove dead client
                    del self._clients[language]
                    return None

            # Start new server
            self._start_server(language)
            return self._clients.get(language)

    def _start_server(self, language: str) -> None:
        """
        Start an LSP server for a language

        Args:
            language: Language identifier
        """
        config = self.servers.get(language)
        if not config:
            return

        try:
            client = LSPClient(
                command=config.command,
                args=config.args,
                timeout=self.timeout,
            )

            client.start()

            # Determine root URI
            root_uri = self.root_uri or f"file://{Path.cwd()}"

            client.initialize(root_uri=root_uri)

            self._clients[language] = client
            logger.info(f"Started LSP server for {language}: {config.command}")

        except LSPClientError as e:
            logger.error(f"Failed to start server for {language}: {e}")
        except FileNotFoundError:
            logger.warning(f"Server not found for {language}: {config.command}")

    def detect_language(self, uri: str) -> Optional[str]:
        """
        Detect language from file URI

        Args:
            uri: File URI

        Returns:
            Language identifier or None
        """
        # Parse URI to get path
        parsed = urlparse(uri)
        path = unquote(parsed.path)

        # Get file extension
        ext = Path(path).suffix.lower()

        return self._ext_to_lang.get(ext)

    def get_language_id(self, uri: str) -> Optional[str]:
        """
        Get LSP language ID for a file

        Args:
            uri: File URI

        Returns:
            Language ID for textDocument/didOpen
        """
        language = self.detect_language(uri)
        if not language:
            return None

        config = self.servers.get(language)
        return config.language_id if config else None

    def is_server_running(self, language: str) -> bool:
        """
        Check if server is running for a language

        Args:
            language: Language identifier

        Returns:
            True if server is running
        """
        with self._lock:
            if language in self._clients:
                return self._clients[language].is_running
            return False

    def get_running_servers(self) -> list[str]:
        """
        Get list of running servers

        Returns:
            List of language identifiers with running servers
        """
        with self._lock:
            return [
                lang for lang, client in self._clients.items() if client.is_running
            ]

    def shutdown(self, language: str) -> None:
        """
        Shutdown server for a specific language

        Args:
            language: Language identifier
        """
        with self._lock:
            if language in self._clients:
                try:
                    self._clients[language].shutdown()
                except Exception as e:
                    logger.error(f"Error shutting down {language} server: {e}")
                finally:
                    del self._clients[language]
                    logger.info(f"Shutdown LSP server for {language}")

    def shutdown_all(self) -> None:
        """Shutdown all running servers"""
        with self._lock:
            languages = list(self._clients.keys())

        for language in languages:
            self.shutdown(language)

        logger.info("All LSP servers shutdown")

    def restart(self, language: str) -> Optional[LSPClient]:
        """
        Restart server for a language

        Args:
            language: Language identifier

        Returns:
            New client or None
        """
        self.shutdown(language)
        return self.get_client_for_language(language)

    def add_server(self, config: ServerConfig) -> None:
        """
        Add or update a server configuration

        Args:
            config: Server configuration
        """
        # Determine language key from language_id
        language = config.language_id

        self.servers[language] = config

        # Update extension mapping
        for ext in config.extensions:
            self._ext_to_lang[ext.lower()] = language

        logger.info(f"Added server config for {language}: {config.command}")

    def remove_server(self, language: str) -> None:
        """
        Remove a server configuration

        Args:
            language: Language identifier
        """
        # Shutdown if running
        self.shutdown(language)

        # Remove configuration
        if language in self.servers:
            config = self.servers.pop(language)

            # Remove extension mappings
            for ext in config.extensions:
                if ext.lower() in self._ext_to_lang:
                    del self._ext_to_lang[ext.lower()]

            logger.info(f"Removed server config for {language}")

    def get_supported_extensions(self) -> list[str]:
        """
        Get all supported file extensions

        Returns:
            List of file extensions
        """
        return list(self._ext_to_lang.keys())

    def get_supported_languages(self) -> list[str]:
        """
        Get all configured languages

        Returns:
            List of language identifiers
        """
        return list(self.servers.keys())

    def __enter__(self) -> "LSPServerManager":
        """Context manager entry"""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        """Context manager exit - shutdown all servers"""
        self.shutdown_all()
