"""
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
    "python": ServerConfig(
        language_id="python",
        command="pyright-langserver",
        args=["--stdio"],
        extensions=[".py", ".pyi", ".pyw"],
        root_patterns=["pyproject.toml", "setup.py", "requirements.txt"],
    ),
    # TypeScript
    "typescript": ServerConfig(
        language_id="typescript",
        command="typescript-language-server",
        args=["--stdio"],
        extensions=[".ts", ".tsx"],
        root_patterns=["tsconfig.json", "package.json"],
    ),
    # JavaScript
    "javascript": ServerConfig(
        language_id="javascript",
        command="typescript-language-server",
        args=["--stdio"],
        extensions=[".js", ".jsx", ".mjs", ".cjs"],
        root_patterns=["package.json", "jsconfig.json"],
    ),
    # Rust
    "rust": ServerConfig(
        language_id="rust",
        command="rust-analyzer",
        args=[],
        extensions=[".rs"],
        root_patterns=["Cargo.toml"],
    ),
    # Go
    "go": ServerConfig(
        language_id="go",
        command="gopls",
        args=[],
        extensions=[".go"],
        root_patterns=["go.mod", "go.sum"],
    ),
    # C
    "c": ServerConfig(
        language_id="c",
        command="clangd",
        args=[],
        extensions=[".c", ".h"],
        root_patterns=["compile_commands.json", "CMakeLists.txt", "Makefile"],
    ),
    # C++
    "cpp": ServerConfig(
        language_id="cpp",
        command="clangd",
        args=[],
        extensions=[".cpp", ".hpp", ".cc", ".hh", ".cxx", ".hxx"],
        root_patterns=["compile_commands.json", "CMakeLists.txt", "Makefile"],
    ),
    # Java
    "java": ServerConfig(
        language_id="java",
        command="jdtls",
        args=[],
        extensions=[".java"],
        root_patterns=["pom.xml", "build.gradle", "build.gradle.kts", ".project"],
    ),
    # C# / .NET
    "csharp": ServerConfig(
        language_id="csharp",
        command="omnisharp",
        args=["-lsp"],
        extensions=[".cs", ".csx"],
        root_patterns=["*.sln", "*.csproj", "project.json"],
    ),
    # Ruby
    "ruby": ServerConfig(
        language_id="ruby",
        command="solargraph",
        args=["stdio"],
        extensions=[".rb", ".rake", ".gemspec"],
        root_patterns=["Gemfile", ".ruby-version"],
    ),
    # PHP
    "php": ServerConfig(
        language_id="php",
        command="phpactor",
        args=["language-server"],
        extensions=[".php", ".phtml"],
        root_patterns=["composer.json", ".php-version"],
    ),
    # Swift
    "swift": ServerConfig(
        language_id="swift",
        command="sourcekit-lsp",
        args=[],
        extensions=[".swift"],
        root_patterns=["Package.swift", "*.xcodeproj", "*.xcworkspace"],
    ),
    # Kotlin
    "kotlin": ServerConfig(
        language_id="kotlin",
        command="kotlin-language-server",
        args=[],
        extensions=[".kt", ".kts"],
        root_patterns=["build.gradle", "build.gradle.kts", "pom.xml"],
    ),
    # Scala
    "scala": ServerConfig(
        language_id="scala",
        command="metals",
        args=[],
        extensions=[".scala", ".sc", ".sbt"],
        root_patterns=["build.sbt", "build.sc", "pom.xml"],
    ),
    # Lua
    "lua": ServerConfig(
        language_id="lua",
        command="lua-language-server",
        args=[],
        extensions=[".lua"],
        root_patterns=[".luarc.json", ".luacheckrc"],
    ),
    # Dart
    "dart": ServerConfig(
        language_id="dart",
        command="dart",
        args=["language-server", "--protocol=lsp"],
        extensions=[".dart"],
        root_patterns=["pubspec.yaml"],
    ),
    # Vue
    "vue": ServerConfig(
        language_id="vue",
        command="vue-language-server",
        args=["--stdio"],
        extensions=[".vue"],
        root_patterns=["vite.config.js", "vue.config.js", "package.json"],
    ),
    # Svelte
    "svelte": ServerConfig(
        language_id="svelte",
        command="svelteserver",
        args=["--stdio"],
        extensions=[".svelte"],
        root_patterns=["svelte.config.js", "package.json"],
    ),
    # Astro
    "astro": ServerConfig(
        language_id="astro",
        command="astro-ls",
        args=["--stdio"],
        extensions=[".astro"],
        root_patterns=["astro.config.mjs", "package.json"],
    ),
    # HTML
    "html": ServerConfig(
        language_id="html",
        command="vscode-html-language-server",
        args=["--stdio"],
        extensions=[".html", ".htm", ".xhtml"],
        root_patterns=["package.json"],
    ),
    # CSS
    "css": ServerConfig(
        language_id="css",
        command="vscode-css-language-server",
        args=["--stdio"],
        extensions=[".css", ".scss", ".sass", ".less"],
        root_patterns=["package.json"],
    ),
    # JSON
    "json": ServerConfig(
        language_id="json",
        command="vscode-json-language-server",
        args=["--stdio"],
        extensions=[".json", ".jsonc"],
        root_patterns=[],
    ),
    # YAML
    "yaml": ServerConfig(
        language_id="yaml",
        command="yaml-language-server",
        args=["--stdio"],
        extensions=[".yaml", ".yml"],
        root_patterns=[],
    ),
    # Markdown
    "markdown": ServerConfig(
        language_id="markdown",
        command="marksman",
        args=["server"],
        extensions=[".md", ".markdown"],
        root_patterns=[],
    ),
    # Bash/Shell
    "bash": ServerConfig(
        language_id="bash",
        command="bash-language-server",
        args=["start"],
        extensions=[".sh", ".bash", ".zsh"],
        root_patterns=[".bashrc", ".zshrc"],
    ),
    # SQL
    "sql": ServerConfig(
        language_id="sql",
        command="sql-language-server",
        args=["up", "--method", "stdio"],
        extensions=[".sql"],
        root_patterns=[],
    ),
    # Docker
    "dockerfile": ServerConfig(
        language_id="dockerfile",
        command="docker-langserver",
        args=["--stdio"],
        extensions=[".dockerfile"],
        root_patterns=["Dockerfile", "docker-compose.yml"],
    ),
    # Terraform
    "terraform": ServerConfig(
        language_id="terraform",
        command="terraform-ls",
        args=["serve"],
        extensions=[".tf", ".tfvars"],
        root_patterns=["main.tf", "terraform.tfvars"],
    ),
    # TOML
    "toml": ServerConfig(
        language_id="toml",
        command="taplo",
        args=["lsp", "stdio"],
        extensions=[".toml"],
        root_patterns=["pyproject.toml", "Cargo.toml"],
    ),
    # Zig
    "zig": ServerConfig(
        language_id="zig",
        command="zls",
        args=[],
        extensions=[".zig"],
        root_patterns=["build.zig"],
    ),
    # Elixir
    "elixir": ServerConfig(
        language_id="elixir",
        command="elixir-ls",
        args=[],
        extensions=[".ex", ".exs"],
        root_patterns=["mix.exs"],
    ),
    # Gleam
    "gleam": ServerConfig(
        language_id="gleam",
        command="gleam",
        args=["lsp"],
        extensions=[".gleam"],
        root_patterns=["gleam.toml"],
    ),
    # Haskell
    "haskell": ServerConfig(
        language_id="haskell",
        command="haskell-language-server-wrapper",
        args=["--lsp"],
        extensions=[".hs", ".lhs"],
        root_patterns=["cabal.project", "stack.yaml", "*.cabal"],
    ),
    # OCaml
    "ocaml": ServerConfig(
        language_id="ocaml",
        command="ocamllsp",
        args=[],
        extensions=[".ml", ".mli"],
        root_patterns=["dune-project", "*.opam"],
    ),
    # Clojure
    "clojure": ServerConfig(
        language_id="clojure",
        command="clojure-lsp",
        args=[],
        extensions=[".clj", ".cljs", ".cljc", ".edn"],
        root_patterns=["deps.edn", "project.clj"],
    ),
    # Erlang
    "erlang": ServerConfig(
        language_id="erlang",
        command="erlang_ls",
        args=[],
        extensions=[".erl", ".hrl"],
        root_patterns=["rebar.config", "erlang.mk"],
    ),
    # F#
    "fsharp": ServerConfig(
        language_id="fsharp",
        command="fsautocomplete",
        args=["--adaptive-lsp-server-enabled"],
        extensions=[".fs", ".fsi", ".fsx"],
        root_patterns=["*.fsproj", "*.sln"],
    ),
    # Julia
    "julia": ServerConfig(
        language_id="julia",
        command="julia",
        args=["--startup-file=no", "--history-file=no", "-e", "using LanguageServer; runserver()"],
        extensions=[".jl"],
        root_patterns=["Project.toml"],
    ),
    # R
    "r": ServerConfig(
        language_id="r",
        command="R",
        args=["--slave", "-e", "languageserver::run()"],
        extensions=[".r", ".R", ".rmd", ".Rmd"],
        root_patterns=["DESCRIPTION", ".Rproj"],
    ),
    # Nim
    "nim": ServerConfig(
        language_id="nim",
        command="nimlsp",
        args=[],
        extensions=[".nim", ".nims"],
        root_patterns=["*.nimble"],
    ),
    # Crystal
    "crystal": ServerConfig(
        language_id="crystal",
        command="crystalline",
        args=[],
        extensions=[".cr"],
        root_patterns=["shard.yml"],
    ),
    # V
    "vlang": ServerConfig(
        language_id="v",
        command="v",
        args=["ls"],
        extensions=[".v"],
        root_patterns=["v.mod"],
    ),
    # Perl
    "perl": ServerConfig(
        language_id="perl",
        command="perlnavigator",
        args=["--stdio"],
        extensions=[".pl", ".pm"],
        root_patterns=["Makefile.PL", "cpanfile"],
    ),
    # GraphQL
    "graphql": ServerConfig(
        language_id="graphql",
        command="graphql-lsp",
        args=["server", "-m", "stream"],
        extensions=[".graphql", ".gql"],
        root_patterns=[".graphqlrc", "graphql.config.js"],
    ),
    # Prisma
    "prisma": ServerConfig(
        language_id="prisma",
        command="prisma-language-server",
        args=["--stdio"],
        extensions=[".prisma"],
        root_patterns=["schema.prisma"],
    ),
    # Protocol Buffers
    "protobuf": ServerConfig(
        language_id="proto",
        command="bufls",
        args=["serve"],
        extensions=[".proto"],
        root_patterns=["buf.yaml"],
    ),
    # CMake
    "cmake": ServerConfig(
        language_id="cmake",
        command="cmake-language-server",
        args=[],
        extensions=[".cmake"],
        root_patterns=["CMakeLists.txt"],
    ),
    # Nix
    "nix": ServerConfig(
        language_id="nix",
        command="nil",
        args=[],
        extensions=[".nix"],
        root_patterns=["flake.nix", "default.nix"],
    ),
    # LaTeX
    "latex": ServerConfig(
        language_id="latex",
        command="texlab",
        args=[],
        extensions=[".tex", ".bib", ".sty", ".cls"],
        root_patterns=["*.tex", "latexmkrc"],
    ),
    # XML
    "xml": ServerConfig(
        language_id="xml",
        command="lemminx",
        args=[],
        extensions=[".xml", ".xsd", ".xsl", ".xslt", ".svg"],
        root_patterns=[],
    ),
}


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()
