"""LSP Server management for different languages."""

from __future__ import annotations

import shutil
import subprocess
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    pass


class Language(Enum):
    """Supported programming languages."""

    PYTHON = "python"
    JAVASCRIPT = "javascript"
    TYPESCRIPT = "typescript"
    C = "c"
    CPP = "cpp"


@dataclass
class LSPServerConfig:
    """Configuration for an LSP server."""

    name: str
    language: Language
    command: list[str]
    install_command: str | None = None
    install_instructions: str | None = None
    root_uri: str | None = None
    initialization_options: dict = field(default_factory=dict)


# Default server configurations
DEFAULT_SERVERS: dict[Language, LSPServerConfig] = {
    Language.PYTHON: LSPServerConfig(
        name="pylsp",
        language=Language.PYTHON,
        command=["pylsp"],
        install_command="pip install python-lsp-server",
        install_instructions="Install with: pip install python-lsp-server",
    ),
    Language.JAVASCRIPT: LSPServerConfig(
        name="typescript-language-server",
        language=Language.JAVASCRIPT,
        command=["typescript-language-server", "--stdio"],
        install_command="npm install -g typescript-language-server typescript",
        install_instructions="Install with: npm install -g typescript-language-server typescript",
    ),
    Language.TYPESCRIPT: LSPServerConfig(
        name="typescript-language-server",
        language=Language.TYPESCRIPT,
        command=["typescript-language-server", "--stdio"],
        install_command="npm install -g typescript-language-server typescript",
        install_instructions="Install with: npm install -g typescript-language-server typescript",
    ),
    Language.C: LSPServerConfig(
        name="clangd",
        language=Language.C,
        command=["clangd"],
        install_command=None,  # Platform-specific
        install_instructions="Install clangd from your package manager or LLVM releases",
    ),
    Language.CPP: LSPServerConfig(
        name="clangd",
        language=Language.CPP,
        command=["clangd"],
        install_command=None,
        install_instructions="Install clangd from your package manager or LLVM releases",
    ),
}


# File extension to language mapping
EXTENSION_TO_LANGUAGE: dict[str, Language] = {
    ".py": Language.PYTHON,
    ".pyi": Language.PYTHON,
    ".js": Language.JAVASCRIPT,
    ".jsx": Language.JAVASCRIPT,
    ".ts": Language.TYPESCRIPT,
    ".tsx": Language.TYPESCRIPT,
    ".c": Language.C,
    ".h": Language.C,
    ".cpp": Language.CPP,
    ".hpp": Language.CPP,
    ".cc": Language.CPP,
    ".cxx": Language.CPP,
}


@dataclass
class ServerStatus:
    """Status of an LSP server."""

    language: Language
    installed: bool
    running: bool
    command: list[str]
    error: str | None = None


class LSPServerManager:
    """Manages LSP server lifecycle and availability."""

    def __init__(self) -> None:
        """Initialize the server manager."""
        self._configs: dict[Language, LSPServerConfig] = DEFAULT_SERVERS.copy()
        self._running_servers: dict[Language, subprocess.Popen] = {}

    def detect_language(self, file_path: str | Path) -> Language | None:
        """Detect language from file extension."""
        ext = Path(file_path).suffix.lower()
        return EXTENSION_TO_LANGUAGE.get(ext)

    def get_config(self, language: Language) -> LSPServerConfig:
        """Get server configuration for a language."""
        return self._configs[language]

    def set_config(self, language: Language, config: LSPServerConfig) -> None:
        """Set custom server configuration for a language."""
        self._configs[language] = config

    def check_server_installed(self, language: Language) -> bool:
        """Check if the LSP server for a language is installed."""
        config = self._configs.get(language)
        if not config:
            return False

        # Check if the command exists
        command = config.command[0]
        if shutil.which(command) is not None:
            return True

        # Check additional Windows paths
        import os
        import sys

        if sys.platform == "win32":
            additional_paths = [
                # Python Scripts folder
                Path(os.environ.get("APPDATA", "")) / "Python" / f"Python{sys.version_info.major}{sys.version_info.minor}" / "Scripts",
                # User site-packages scripts
                Path.home() / "AppData" / "Roaming" / "Python" / f"Python{sys.version_info.major}{sys.version_info.minor}" / "Scripts",
                # npm global
                Path(os.environ.get("APPDATA", "")) / "npm",
                # Local npm
                Path.home() / "AppData" / "Roaming" / "npm",
            ]

            for base_path in additional_paths:
                for ext in ["", ".exe", ".cmd", ".bat"]:
                    full_path = base_path / f"{command}{ext}"
                    if full_path.exists():
                        # Update the config to use full path
                        self._configs[language].command[0] = str(full_path)
                        return True

        return False

    def get_status(self, language: Language) -> ServerStatus:
        """Get the status of an LSP server."""
        config = self._configs.get(language)
        if not config:
            return ServerStatus(
                language=language,
                installed=False,
                running=False,
                command=[],
                error=f"No configuration for {language.value}",
            )

        installed = self.check_server_installed(language)
        running = language in self._running_servers and self._running_servers[language].poll() is None

        return ServerStatus(
            language=language,
            installed=installed,
            running=running,
            command=config.command,
            error=None if installed else config.install_instructions,
        )

    def get_all_status(self) -> dict[Language, ServerStatus]:
        """Get status of all configured LSP servers."""
        return {lang: self.get_status(lang) for lang in self._configs}

    def install_server(self, language: Language) -> tuple[bool, str]:
        """Attempt to install an LSP server."""
        config = self._configs.get(language)
        if not config:
            return False, f"No configuration for {language.value}"

        if not config.install_command:
            return False, config.install_instructions or "Manual installation required"

        try:
            result = subprocess.run(
                config.install_command.split(),
                capture_output=True,
                text=True,
                timeout=300,  # 5 minute timeout
            )
            if result.returncode == 0:
                return True, "Installation successful"
            return False, f"Installation failed: {result.stderr}"
        except subprocess.TimeoutExpired:
            return False, "Installation timed out"
        except Exception as e:
            return False, f"Installation error: {e}"

    def start_server(
        self,
        language: Language,
        workspace_path: str | Path | None = None,
    ) -> subprocess.Popen | None:
        """Start an LSP server process."""
        if not self.check_server_installed(language):
            return None

        config = self._configs[language]

        # Stop existing server if running
        self.stop_server(language)

        try:
            # Build environment
            env = None
            cwd = str(workspace_path) if workspace_path else None

            process = subprocess.Popen(
                config.command,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                cwd=cwd,
                env=env,
            )

            self._running_servers[language] = process
            return process

        except Exception:
            return None

    def stop_server(self, language: Language) -> bool:
        """Stop a running LSP server."""
        if language not in self._running_servers:
            return True

        process = self._running_servers[language]
        if process.poll() is None:
            process.terminate()
            try:
                process.wait(timeout=5)
            except subprocess.TimeoutExpired:
                process.kill()

        del self._running_servers[language]
        return True

    def stop_all_servers(self) -> None:
        """Stop all running LSP servers."""
        for language in list(self._running_servers.keys()):
            self.stop_server(language)

    def get_running_server(self, language: Language) -> subprocess.Popen | None:
        """Get a running server process."""
        process = self._running_servers.get(language)
        if process and process.poll() is None:
            return process
        return None

    def __del__(self) -> None:
        """Cleanup on destruction."""
        self.stop_all_servers()
