"""
LSP Server Auto-Download - Download and install LSP servers automatically

Handles downloading, extracting, and installing LSP server binaries
from official sources.

Examples:
    downloader = LSPDownloader()

    # Check if server is installed
    if not downloader.is_installed("pyright"):
        downloader.download("pyright", progress_callback=print)

    # Get path to server binary
    path = downloader.get_binary_path("pyright")
"""

import hashlib
import logging
import platform
import shutil
import stat
import tarfile
import tempfile
import zipfile
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable, Optional

import httpx

logger = logging.getLogger(__name__)


# Type for progress callback: (downloaded_bytes, total_bytes, message)
ProgressCallback = Callable[[int, int, str], None]


@dataclass
class ServerDownloadInfo:
    """Information for downloading an LSP server"""

    name: str
    version: str
    urls: dict[str, str]  # platform -> url
    checksums: dict[str, str] = field(default_factory=dict)  # platform -> sha256
    binary_name: str = ""  # Name of binary after extraction
    extract_subdir: str = ""  # Subdirectory within archive containing binary
    post_install: Optional[str] = None  # Post-install command


def get_platform_key() -> str:
    """Get platform key for download URLs"""
    system = platform.system().lower()
    machine = platform.machine().lower()

    # Normalize machine architecture
    if machine in ("x86_64", "amd64"):
        arch = "x64"
    elif machine in ("arm64", "aarch64"):
        arch = "arm64"
    elif machine in ("i386", "i686"):
        arch = "x86"
    else:
        arch = machine

    return f"{system}-{arch}"


# Registry of known LSP servers
# Note: URLs and checksums should be updated for actual releases
SERVER_REGISTRY: dict[str, ServerDownloadInfo] = {
    # Python - Pyright
    "pyright": ServerDownloadInfo(
        name="pyright",
        version="1.1.350",
        urls={
            "linux-x64": "https://github.com/microsoft/pyright/releases/download/1.1.350/pyright-1.1.350.tar.gz",
            "linux-arm64": "https://github.com/microsoft/pyright/releases/download/1.1.350/pyright-1.1.350.tar.gz",
            "darwin-x64": "https://github.com/microsoft/pyright/releases/download/1.1.350/pyright-1.1.350.tar.gz",
            "darwin-arm64": "https://github.com/microsoft/pyright/releases/download/1.1.350/pyright-1.1.350.tar.gz",
        },
        binary_name="pyright-langserver",
        extract_subdir="",
    ),
    # TypeScript/JavaScript
    "typescript-language-server": ServerDownloadInfo(
        name="typescript-language-server",
        version="4.3.3",
        urls={
            "linux-x64": "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-4.3.3.tgz",
            "linux-arm64": "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-4.3.3.tgz",
            "darwin-x64": "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-4.3.3.tgz",
            "darwin-arm64": "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-4.3.3.tgz",
        },
        binary_name="typescript-language-server",
    ),
    # Rust
    "rust-analyzer": ServerDownloadInfo(
        name="rust-analyzer",
        version="2024-01-15",
        urls={
            "linux-x64": "https://github.com/rust-lang/rust-analyzer/releases/download/2024-01-15/rust-analyzer-x86_64-unknown-linux-gnu.gz",
            "linux-arm64": "https://github.com/rust-lang/rust-analyzer/releases/download/2024-01-15/rust-analyzer-aarch64-unknown-linux-gnu.gz",
            "darwin-x64": "https://github.com/rust-lang/rust-analyzer/releases/download/2024-01-15/rust-analyzer-x86_64-apple-darwin.gz",
            "darwin-arm64": "https://github.com/rust-lang/rust-analyzer/releases/download/2024-01-15/rust-analyzer-aarch64-apple-darwin.gz",
        },
        binary_name="rust-analyzer",
    ),
    # Lua
    "lua-language-server": ServerDownloadInfo(
        name="lua-language-server",
        version="3.7.4",
        urls={
            "linux-x64": "https://github.com/LuaLS/lua-language-server/releases/download/3.7.4/lua-language-server-3.7.4-linux-x64.tar.gz",
            "linux-arm64": "https://github.com/LuaLS/lua-language-server/releases/download/3.7.4/lua-language-server-3.7.4-linux-arm64.tar.gz",
            "darwin-x64": "https://github.com/LuaLS/lua-language-server/releases/download/3.7.4/lua-language-server-3.7.4-darwin-x64.tar.gz",
            "darwin-arm64": "https://github.com/LuaLS/lua-language-server/releases/download/3.7.4/lua-language-server-3.7.4-darwin-arm64.tar.gz",
        },
        binary_name="lua-language-server",
        extract_subdir="bin",
    ),
    # Go
    "gopls": ServerDownloadInfo(
        name="gopls",
        version="0.15.0",
        urls={
            "linux-x64": "https://github.com/golang/tools/releases/download/gopls/v0.15.0/gopls-linux-amd64.tar.gz",
            "linux-arm64": "https://github.com/golang/tools/releases/download/gopls/v0.15.0/gopls-linux-arm64.tar.gz",
            "darwin-x64": "https://github.com/golang/tools/releases/download/gopls/v0.15.0/gopls-darwin-amd64.tar.gz",
            "darwin-arm64": "https://github.com/golang/tools/releases/download/gopls/v0.15.0/gopls-darwin-arm64.tar.gz",
        },
        binary_name="gopls",
    ),
    # Zig
    "zls": ServerDownloadInfo(
        name="zls",
        version="0.11.0",
        urls={
            "linux-x64": "https://github.com/zigtools/zls/releases/download/0.11.0/zls-x86_64-linux.tar.xz",
            "linux-arm64": "https://github.com/zigtools/zls/releases/download/0.11.0/zls-aarch64-linux.tar.xz",
            "darwin-x64": "https://github.com/zigtools/zls/releases/download/0.11.0/zls-x86_64-macos.tar.xz",
            "darwin-arm64": "https://github.com/zigtools/zls/releases/download/0.11.0/zls-aarch64-macos.tar.xz",
        },
        binary_name="zls",
    ),
    # YAML
    "yaml-language-server": ServerDownloadInfo(
        name="yaml-language-server",
        version="1.14.0",
        urls={
            "linux-x64": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.14.0.tgz",
            "linux-arm64": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.14.0.tgz",
            "darwin-x64": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.14.0.tgz",
            "darwin-arm64": "https://registry.npmjs.org/yaml-language-server/-/yaml-language-server-1.14.0.tgz",
        },
        binary_name="yaml-language-server",
    ),
    # Bash
    "bash-language-server": ServerDownloadInfo(
        name="bash-language-server",
        version="5.1.2",
        urls={
            "linux-x64": "https://registry.npmjs.org/bash-language-server/-/bash-language-server-5.1.2.tgz",
            "linux-arm64": "https://registry.npmjs.org/bash-language-server/-/bash-language-server-5.1.2.tgz",
            "darwin-x64": "https://registry.npmjs.org/bash-language-server/-/bash-language-server-5.1.2.tgz",
            "darwin-arm64": "https://registry.npmjs.org/bash-language-server/-/bash-language-server-5.1.2.tgz",
        },
        binary_name="bash-language-server",
    ),
    # Terraform
    "terraform-ls": ServerDownloadInfo(
        name="terraform-ls",
        version="0.32.7",
        urls={
            "linux-x64": "https://releases.hashicorp.com/terraform-ls/0.32.7/terraform-ls_0.32.7_linux_amd64.zip",
            "linux-arm64": "https://releases.hashicorp.com/terraform-ls/0.32.7/terraform-ls_0.32.7_linux_arm64.zip",
            "darwin-x64": "https://releases.hashicorp.com/terraform-ls/0.32.7/terraform-ls_0.32.7_darwin_amd64.zip",
            "darwin-arm64": "https://releases.hashicorp.com/terraform-ls/0.32.7/terraform-ls_0.32.7_darwin_arm64.zip",
        },
        binary_name="terraform-ls",
    ),
    # Markdown
    "marksman": ServerDownloadInfo(
        name="marksman",
        version="2023-12-09",
        urls={
            "linux-x64": "https://github.com/artempyanykh/marksman/releases/download/2023-12-09/marksman-linux-x64",
            "linux-arm64": "https://github.com/artempyanykh/marksman/releases/download/2023-12-09/marksman-linux-arm64",
            "darwin-x64": "https://github.com/artempyanykh/marksman/releases/download/2023-12-09/marksman-macos",
            "darwin-arm64": "https://github.com/artempyanykh/marksman/releases/download/2023-12-09/marksman-macos",
        },
        binary_name="marksman",
    ),
    # TOML
    "taplo": ServerDownloadInfo(
        name="taplo",
        version="0.8.1",
        urls={
            "linux-x64": "https://github.com/tamasfe/taplo/releases/download/0.8.1/taplo-full-linux-x86_64.gz",
            "linux-arm64": "https://github.com/tamasfe/taplo/releases/download/0.8.1/taplo-full-linux-aarch64.gz",
            "darwin-x64": "https://github.com/tamasfe/taplo/releases/download/0.8.1/taplo-full-darwin-x86_64.gz",
            "darwin-arm64": "https://github.com/tamasfe/taplo/releases/download/0.8.1/taplo-full-darwin-aarch64.gz",
        },
        binary_name="taplo",
    ),
    # Clojure
    "clojure-lsp": ServerDownloadInfo(
        name="clojure-lsp",
        version="2024.01.10-14.04.33",
        urls={
            "linux-x64": "https://github.com/clojure-lsp/clojure-lsp/releases/download/2024.01.10-14.04.33/clojure-lsp-native-static-linux-amd64.zip",
            "linux-arm64": "https://github.com/clojure-lsp/clojure-lsp/releases/download/2024.01.10-14.04.33/clojure-lsp-native-linux-aarch64.zip",
            "darwin-x64": "https://github.com/clojure-lsp/clojure-lsp/releases/download/2024.01.10-14.04.33/clojure-lsp-native-macos-amd64.zip",
            "darwin-arm64": "https://github.com/clojure-lsp/clojure-lsp/releases/download/2024.01.10-14.04.33/clojure-lsp-native-macos-aarch64.zip",
        },
        binary_name="clojure-lsp",
    ),
    # LaTeX
    "texlab": ServerDownloadInfo(
        name="texlab",
        version="5.12.3",
        urls={
            "linux-x64": "https://github.com/latex-lsp/texlab/releases/download/v5.12.3/texlab-x86_64-linux.tar.gz",
            "linux-arm64": "https://github.com/latex-lsp/texlab/releases/download/v5.12.3/texlab-aarch64-linux.tar.gz",
            "darwin-x64": "https://github.com/latex-lsp/texlab/releases/download/v5.12.3/texlab-x86_64-macos.tar.gz",
            "darwin-arm64": "https://github.com/latex-lsp/texlab/releases/download/v5.12.3/texlab-aarch64-macos.tar.gz",
        },
        binary_name="texlab",
    ),
    # Nix
    "nil": ServerDownloadInfo(
        name="nil",
        version="2023-11-21",
        urls={
            "linux-x64": "https://github.com/oxalica/nil/releases/download/2023-11-21/nil-2023-11-21-x86_64-linux.tar.xz",
            "linux-arm64": "https://github.com/oxalica/nil/releases/download/2023-11-21/nil-2023-11-21-aarch64-linux.tar.xz",
            "darwin-x64": "https://github.com/oxalica/nil/releases/download/2023-11-21/nil-2023-11-21-x86_64-darwin.tar.xz",
            "darwin-arm64": "https://github.com/oxalica/nil/releases/download/2023-11-21/nil-2023-11-21-aarch64-darwin.tar.xz",
        },
        binary_name="nil",
    ),
    # Elixir
    "elixir-ls": ServerDownloadInfo(
        name="elixir-ls",
        version="0.19.0",
        urls={
            "linux-x64": "https://github.com/elixir-lsp/elixir-ls/releases/download/v0.19.0/elixir-ls-v0.19.0.zip",
            "linux-arm64": "https://github.com/elixir-lsp/elixir-ls/releases/download/v0.19.0/elixir-ls-v0.19.0.zip",
            "darwin-x64": "https://github.com/elixir-lsp/elixir-ls/releases/download/v0.19.0/elixir-ls-v0.19.0.zip",
            "darwin-arm64": "https://github.com/elixir-lsp/elixir-ls/releases/download/v0.19.0/elixir-ls-v0.19.0.zip",
        },
        binary_name="language_server.sh",
    ),
    # Haskell
    "haskell-language-server": ServerDownloadInfo(
        name="haskell-language-server",
        version="2.6.0.0",
        urls={
            "linux-x64": "https://github.com/haskell/haskell-language-server/releases/download/2.6.0.0/haskell-language-server-2.6.0.0-x86_64-linux-deb10.tar.xz",
            "linux-arm64": "https://github.com/haskell/haskell-language-server/releases/download/2.6.0.0/haskell-language-server-2.6.0.0-aarch64-linux-deb10.tar.xz",
            "darwin-x64": "https://github.com/haskell/haskell-language-server/releases/download/2.6.0.0/haskell-language-server-2.6.0.0-x86_64-darwin.tar.xz",
            "darwin-arm64": "https://github.com/haskell/haskell-language-server/releases/download/2.6.0.0/haskell-language-server-2.6.0.0-aarch64-darwin.tar.xz",
        },
        binary_name="haskell-language-server-wrapper",
    ),
    # HTML/CSS/JSON (vscode-langservers-extracted)
    "vscode-langservers": ServerDownloadInfo(
        name="vscode-langservers",
        version="4.8.0",
        urls={
            "linux-x64": "https://registry.npmjs.org/vscode-langservers-extracted/-/vscode-langservers-extracted-4.8.0.tgz",
            "linux-arm64": "https://registry.npmjs.org/vscode-langservers-extracted/-/vscode-langservers-extracted-4.8.0.tgz",
            "darwin-x64": "https://registry.npmjs.org/vscode-langservers-extracted/-/vscode-langservers-extracted-4.8.0.tgz",
            "darwin-arm64": "https://registry.npmjs.org/vscode-langservers-extracted/-/vscode-langservers-extracted-4.8.0.tgz",
        },
        binary_name="vscode-html-language-server",
    ),
    # Vue
    "vue-language-server": ServerDownloadInfo(
        name="vue-language-server",
        version="1.8.27",
        urls={
            "linux-x64": "https://registry.npmjs.org/@vue/language-server/-/language-server-1.8.27.tgz",
            "linux-arm64": "https://registry.npmjs.org/@vue/language-server/-/language-server-1.8.27.tgz",
            "darwin-x64": "https://registry.npmjs.org/@vue/language-server/-/language-server-1.8.27.tgz",
            "darwin-arm64": "https://registry.npmjs.org/@vue/language-server/-/language-server-1.8.27.tgz",
        },
        binary_name="vue-language-server",
    ),
    # Svelte
    "svelte-language-server": ServerDownloadInfo(
        name="svelte-language-server",
        version="0.16.5",
        urls={
            "linux-x64": "https://registry.npmjs.org/svelte-language-server/-/svelte-language-server-0.16.5.tgz",
            "linux-arm64": "https://registry.npmjs.org/svelte-language-server/-/svelte-language-server-0.16.5.tgz",
            "darwin-x64": "https://registry.npmjs.org/svelte-language-server/-/svelte-language-server-0.16.5.tgz",
            "darwin-arm64": "https://registry.npmjs.org/svelte-language-server/-/svelte-language-server-0.16.5.tgz",
        },
        binary_name="svelteserver",
    ),
    # GraphQL
    "graphql-lsp": ServerDownloadInfo(
        name="graphql-lsp",
        version="3.3.26",
        urls={
            "linux-x64": "https://registry.npmjs.org/graphql-language-service-cli/-/graphql-language-service-cli-3.3.26.tgz",
            "linux-arm64": "https://registry.npmjs.org/graphql-language-service-cli/-/graphql-language-service-cli-3.3.26.tgz",
            "darwin-x64": "https://registry.npmjs.org/graphql-language-service-cli/-/graphql-language-service-cli-3.3.26.tgz",
            "darwin-arm64": "https://registry.npmjs.org/graphql-language-service-cli/-/graphql-language-service-cli-3.3.26.tgz",
        },
        binary_name="graphql-lsp",
    ),
    # Prisma
    "prisma-language-server": ServerDownloadInfo(
        name="prisma-language-server",
        version="5.9.1",
        urls={
            "linux-x64": "https://registry.npmjs.org/@prisma/language-server/-/language-server-5.9.1.tgz",
            "linux-arm64": "https://registry.npmjs.org/@prisma/language-server/-/language-server-5.9.1.tgz",
            "darwin-x64": "https://registry.npmjs.org/@prisma/language-server/-/language-server-5.9.1.tgz",
            "darwin-arm64": "https://registry.npmjs.org/@prisma/language-server/-/language-server-5.9.1.tgz",
        },
        binary_name="prisma-language-server",
    ),
    # Docker
    "docker-langserver": ServerDownloadInfo(
        name="docker-langserver",
        version="0.11.0",
        urls={
            "linux-x64": "https://registry.npmjs.org/dockerfile-language-server-nodejs/-/dockerfile-language-server-nodejs-0.11.0.tgz",
            "linux-arm64": "https://registry.npmjs.org/dockerfile-language-server-nodejs/-/dockerfile-language-server-nodejs-0.11.0.tgz",
            "darwin-x64": "https://registry.npmjs.org/dockerfile-language-server-nodejs/-/dockerfile-language-server-nodejs-0.11.0.tgz",
            "darwin-arm64": "https://registry.npmjs.org/dockerfile-language-server-nodejs/-/dockerfile-language-server-nodejs-0.11.0.tgz",
        },
        binary_name="docker-langserver",
    ),
    # CMake
    "cmake-language-server": ServerDownloadInfo(
        name="cmake-language-server",
        version="0.1.9",
        urls={
            "linux-x64": "https://files.pythonhosted.org/packages/py3/c/cmake_language_server/cmake_language_server-0.1.9-py3-none-any.whl",
            "linux-arm64": "https://files.pythonhosted.org/packages/py3/c/cmake_language_server/cmake_language_server-0.1.9-py3-none-any.whl",
            "darwin-x64": "https://files.pythonhosted.org/packages/py3/c/cmake_language_server/cmake_language_server-0.1.9-py3-none-any.whl",
            "darwin-arm64": "https://files.pythonhosted.org/packages/py3/c/cmake_language_server/cmake_language_server-0.1.9-py3-none-any.whl",
        },
        binary_name="cmake-language-server",
        post_install="pip install cmake-language-server",
    ),
    # Protocol Buffers
    "bufls": ServerDownloadInfo(
        name="bufls",
        version="0.0.2",
        urls={
            "linux-x64": "https://github.com/bufbuild/buf-language-server/releases/download/v0.0.2/bufls-Linux-x86_64",
            "linux-arm64": "https://github.com/bufbuild/buf-language-server/releases/download/v0.0.2/bufls-Linux-aarch64",
            "darwin-x64": "https://github.com/bufbuild/buf-language-server/releases/download/v0.0.2/bufls-Darwin-x86_64",
            "darwin-arm64": "https://github.com/bufbuild/buf-language-server/releases/download/v0.0.2/bufls-Darwin-arm64",
        },
        binary_name="bufls",
    ),
    # SQL
    "sql-language-server": ServerDownloadInfo(
        name="sql-language-server",
        version="1.6.0",
        urls={
            "linux-x64": "https://registry.npmjs.org/sql-language-server/-/sql-language-server-1.6.0.tgz",
            "linux-arm64": "https://registry.npmjs.org/sql-language-server/-/sql-language-server-1.6.0.tgz",
            "darwin-x64": "https://registry.npmjs.org/sql-language-server/-/sql-language-server-1.6.0.tgz",
            "darwin-arm64": "https://registry.npmjs.org/sql-language-server/-/sql-language-server-1.6.0.tgz",
        },
        binary_name="sql-language-server",
    ),
}


class DownloadError(Exception):
    """Error during download"""

    pass


class ChecksumError(DownloadError):
    """Checksum verification failed"""

    pass


class LSPDownloader:
    """
    Download and manage LSP server installations

    Handles downloading, extracting, and verifying LSP servers
    from official sources.

    Example:
        downloader = LSPDownloader()

        # Download with progress
        def on_progress(downloaded, total, msg):
            print(f"{msg}: {downloaded}/{total}")

        downloader.download("pyright", progress_callback=on_progress)

        # Get binary path
        binary = downloader.get_binary_path("pyright")
    """

    DEFAULT_INSTALL_DIR = Path.home() / ".groknroll" / "lsp"

    def __init__(
        self,
        install_dir: Optional[Path] = None,
        registry: Optional[dict[str, ServerDownloadInfo]] = None,
        timeout: float = 300.0,
    ):
        """
        Initialize LSP downloader

        Args:
            install_dir: Directory to install servers
            registry: Server download registry
            timeout: Download timeout in seconds
        """
        self.install_dir = install_dir or self.DEFAULT_INSTALL_DIR
        self.registry = registry or SERVER_REGISTRY.copy()
        self.timeout = timeout

    def is_installed(self, server_name: str) -> bool:
        """
        Check if a server is installed

        Args:
            server_name: Name of server

        Returns:
            True if server binary exists
        """
        binary_path = self.get_binary_path(server_name)
        return binary_path is not None and binary_path.exists()

    def get_binary_path(self, server_name: str) -> Optional[Path]:
        """
        Get path to installed server binary

        Args:
            server_name: Name of server

        Returns:
            Path to binary or None if not in registry
        """
        info = self.registry.get(server_name)
        if not info:
            return None

        server_dir = self.install_dir / server_name
        binary_name = info.binary_name or server_name

        # Check common locations
        possible_paths = [
            server_dir / binary_name,
            server_dir / "bin" / binary_name,
            server_dir / info.extract_subdir / binary_name if info.extract_subdir else None,
        ]

        for path in possible_paths:
            if path and path.exists():
                return path

        # Default path (may not exist)
        return server_dir / binary_name

    def get_server_dir(self, server_name: str) -> Path:
        """
        Get installation directory for a server

        Args:
            server_name: Name of server

        Returns:
            Path to server directory
        """
        return self.install_dir / server_name

    def download(
        self,
        server_name: str,
        force: bool = False,
        progress_callback: Optional[ProgressCallback] = None,
    ) -> Path:
        """
        Download and install an LSP server

        Args:
            server_name: Name of server to download
            force: Force re-download even if installed
            progress_callback: Callback for progress updates

        Returns:
            Path to installed binary

        Raises:
            DownloadError: If download fails
            ChecksumError: If checksum verification fails
        """
        if not force and self.is_installed(server_name):
            binary_path = self.get_binary_path(server_name)
            if binary_path:
                logger.info(f"{server_name} already installed at {binary_path}")
                return binary_path

        info = self.registry.get(server_name)
        if not info:
            raise DownloadError(f"Unknown server: {server_name}")

        # Get URL for current platform
        platform_key = get_platform_key()
        url = info.urls.get(platform_key)
        if not url:
            raise DownloadError(f"No download available for {server_name} on {platform_key}")

        expected_checksum = info.checksums.get(platform_key)

        # Create install directory
        server_dir = self.get_server_dir(server_name)
        server_dir.mkdir(parents=True, exist_ok=True)

        # Download to temp file
        with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
            tmp_path = Path(tmp_file.name)

        try:
            if progress_callback:
                progress_callback(0, 0, f"Downloading {server_name}...")

            self._download_file(url, tmp_path, progress_callback)

            # Verify checksum
            if expected_checksum:
                if progress_callback:
                    progress_callback(0, 0, "Verifying checksum...")
                self._verify_checksum(tmp_path, expected_checksum)

            # Extract archive
            if progress_callback:
                progress_callback(0, 0, "Extracting...")
            self._extract_archive(tmp_path, server_dir, info)

            # Make binary executable
            binary_path = self.get_binary_path(server_name)
            if binary_path and binary_path.exists():
                self._make_executable(binary_path)

            if progress_callback:
                progress_callback(0, 0, f"Installed {server_name}")

            logger.info(f"Installed {server_name} to {server_dir}")
            return binary_path or server_dir

        finally:
            # Clean up temp file
            if tmp_path.exists():
                tmp_path.unlink()

    def _download_file(
        self,
        url: str,
        dest: Path,
        progress_callback: Optional[ProgressCallback] = None,
    ) -> None:
        """Download file from URL with progress"""
        try:
            with httpx.stream("GET", url, timeout=self.timeout, follow_redirects=True) as response:
                response.raise_for_status()

                total_size = int(response.headers.get("content-length", 0))
                downloaded = 0

                with open(dest, "wb") as f:
                    for chunk in response.iter_bytes(chunk_size=8192):
                        f.write(chunk)
                        downloaded += len(chunk)

                        if progress_callback and total_size:
                            progress_callback(
                                downloaded,
                                total_size,
                                f"Downloading: {downloaded}/{total_size} bytes",
                            )

        except httpx.HTTPStatusError as e:
            raise DownloadError(f"HTTP error: {e.response.status_code}")
        except httpx.RequestError as e:
            raise DownloadError(f"Download failed: {e}")

    def _verify_checksum(self, file_path: Path, expected: str) -> None:
        """Verify file checksum"""
        sha256 = hashlib.sha256()

        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(8192), b""):
                sha256.update(chunk)

        actual = sha256.hexdigest()

        if actual != expected:
            raise ChecksumError(f"Checksum mismatch: expected {expected}, got {actual}")

    def _extract_archive(
        self,
        archive_path: Path,
        dest_dir: Path,
        info: ServerDownloadInfo,
    ) -> None:
        """Extract archive to destination"""
        suffix = archive_path.suffix.lower()
        name = archive_path.name.lower()

        # Determine archive type
        if name.endswith(".tar.gz") or name.endswith(".tgz"):
            self._extract_tar(archive_path, dest_dir)
        elif suffix == ".gz":
            # Single gzipped file (like rust-analyzer)
            self._extract_gzip(archive_path, dest_dir, info.binary_name)
        elif suffix == ".zip":
            self._extract_zip(archive_path, dest_dir)
        elif suffix == ".tar":
            self._extract_tar(archive_path, dest_dir)
        else:
            # Assume it's a binary, just copy
            binary_name = info.binary_name or info.name
            shutil.copy2(archive_path, dest_dir / binary_name)

    def _extract_tar(self, archive_path: Path, dest_dir: Path) -> None:
        """Extract tar archive"""
        with tarfile.open(archive_path, "r:*") as tar:
            tar.extractall(dest_dir, filter="data")

    def _extract_zip(self, archive_path: Path, dest_dir: Path) -> None:
        """Extract zip archive"""
        with zipfile.ZipFile(archive_path, "r") as zf:
            zf.extractall(dest_dir)

    def _extract_gzip(self, archive_path: Path, dest_dir: Path, binary_name: str) -> None:
        """Extract single gzipped file"""
        import gzip

        output_path = dest_dir / binary_name

        with gzip.open(archive_path, "rb") as f_in:
            with open(output_path, "wb") as f_out:
                shutil.copyfileobj(f_in, f_out)

    def _make_executable(self, path: Path) -> None:
        """Make file executable"""
        current_mode = path.stat().st_mode
        path.chmod(current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)

    def uninstall(self, server_name: str) -> bool:
        """
        Uninstall a server

        Args:
            server_name: Name of server

        Returns:
            True if server was uninstalled
        """
        server_dir = self.get_server_dir(server_name)

        if server_dir.exists():
            shutil.rmtree(server_dir)
            logger.info(f"Uninstalled {server_name}")
            return True

        return False

    def get_installed_servers(self) -> list[str]:
        """
        Get list of installed servers

        Returns:
            List of server names
        """
        installed = []

        if not self.install_dir.exists():
            return installed

        for server_name in self.registry:
            if self.is_installed(server_name):
                installed.append(server_name)

        return installed

    def get_available_servers(self) -> list[str]:
        """
        Get list of servers available for download

        Returns:
            List of server names available for current platform
        """
        platform_key = get_platform_key()
        available = []

        for name, info in self.registry.items():
            if platform_key in info.urls:
                available.append(name)

        return available

    def add_server(self, info: ServerDownloadInfo) -> None:
        """
        Add server to registry

        Args:
            info: Server download information
        """
        self.registry[info.name] = info

    def get_server_info(self, server_name: str) -> Optional[ServerDownloadInfo]:
        """
        Get server download info

        Args:
            server_name: Name of server

        Returns:
            ServerDownloadInfo or None
        """
        return self.registry.get(server_name)

    def get_server_version(self, server_name: str) -> Optional[str]:
        """
        Get installed/available server version

        Args:
            server_name: Name of server

        Returns:
            Version string or None
        """
        info = self.registry.get(server_name)
        return info.version if info else None


# Convenience functions


def download_server(
    server_name: str,
    install_dir: Optional[Path] = None,
    progress_callback: Optional[ProgressCallback] = None,
) -> Path:
    """
    Download an LSP server (convenience function)

    Args:
        server_name: Name of server
        install_dir: Installation directory
        progress_callback: Progress callback

    Returns:
        Path to installed binary
    """
    downloader = LSPDownloader(install_dir=install_dir)
    return downloader.download(server_name, progress_callback=progress_callback)


def is_server_installed(
    server_name: str,
    install_dir: Optional[Path] = None,
) -> bool:
    """
    Check if server is installed (convenience function)

    Args:
        server_name: Name of server
        install_dir: Installation directory

    Returns:
        True if installed
    """
    downloader = LSPDownloader(install_dir=install_dir)
    return downloader.is_installed(server_name)


def get_server_binary(
    server_name: str,
    install_dir: Optional[Path] = None,
) -> Optional[Path]:
    """
    Get path to server binary (convenience function)

    Args:
        server_name: Name of server
        install_dir: Installation directory

    Returns:
        Path to binary or None
    """
    downloader = LSPDownloader(install_dir=install_dir)
    return downloader.get_binary_path(server_name)
