"""
Provides Groovy specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Groovy.
"""

import dataclasses
import logging
import os
import pathlib
import shlex

from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_utils import FileUtils, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings

log = logging.getLogger(__name__)

VSCODE_JAVA_ALLOWED_HOSTS = ("github.com", "release-assets.githubusercontent.com", "objects.githubusercontent.com")

# Version pinning convention (see eclipse_jdtls.py for the full spec):
#   INITIAL_* — frozen forever; legacy unversioned install dir is reserved for it.
#   DEFAULT_* — bumped on upgrades; goes into a versioned subdir.
INITIAL_VSCODE_JAVA_VERSION = "1.42.0-561"
INITIAL_VSCODE_JAVA_SHA256_BY_PLATFORM = {
    "win-x64": "ef195b45bd260976ad2e84618f4044b5d7248deed41d647573f0ee22c4233df3",
    "linux-x64": "7660b7b527be6fda46a917966b34d828e7416d5cc84287b29b88e7b99c1737f9",
    "linux-arm64": "e15bc9b2a665d3453203402621b5441062aa41b0ec2d140661f439326fd248c1",
    "osx-x64": "03ae1db1a22c15561a620f1b722d6797d35d4faaa7c4666dbe6ca2715089852f",
    "osx-arm64": "bc00c2699d4b8d478eb9a1621db9d6d3a12ea0dcc247a9cd8040e8ac19c03933",
}
DEFAULT_VSCODE_JAVA_VERSION = "1.42.0-561"
DEFAULT_VSCODE_JAVA_SHA256_BY_PLATFORM = {
    "win-x64": "ef195b45bd260976ad2e84618f4044b5d7248deed41d647573f0ee22c4233df3",
    "linux-x64": "7660b7b527be6fda46a917966b34d828e7416d5cc84287b29b88e7b99c1737f9",
    "linux-arm64": "e15bc9b2a665d3453203402621b5441062aa41b0ec2d140661f439326fd248c1",
    "osx-x64": "03ae1db1a22c15561a620f1b722d6797d35d4faaa7c4666dbe6ca2715089852f",
    "osx-arm64": "bc00c2699d4b8d478eb9a1621db9d6d3a12ea0dcc247a9cd8040e8ac19c03933",
}


def _vscode_java_sha(version: str, platform_key: str) -> str | None:
    if version == INITIAL_VSCODE_JAVA_VERSION:
        return INITIAL_VSCODE_JAVA_SHA256_BY_PLATFORM[platform_key]
    if version == DEFAULT_VSCODE_JAVA_VERSION:
        return DEFAULT_VSCODE_JAVA_SHA256_BY_PLATFORM[platform_key]
    return None


@dataclasses.dataclass
class GroovyRuntimeDependencyPaths:
    """
    Stores the paths to the runtime dependencies of Groovy Language Server
    """

    java_path: str
    java_home_path: str
    ls_jar_path: str
    groovy_home_path: str | None = None


class GroovyLanguageServer(SolidLanguageServer):
    """
    Provides Groovy specific instantiation of the LanguageServer class.
    Contains various configurations and settings specific to Groovy.

    You can pass the following entries in ``ls_specific_settings["groovy"]``:
        - ls_jar_path: Path to the Groovy Language Server JAR.
        - ls_java_home_path: Optional Java home to use instead of Serena's managed JRE.
        - ls_jar_options: Additional JVM/JAR options passed to the Groovy LS.
        - vscode_java_version: Override the pinned vscode-java runtime bundle version
          downloaded by Serena when it manages Java itself (default: the bundled Serena version).
    """

    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
        """
        Creates a Groovy Language Server instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
        """
        runtime_dependency_paths = self._setup_runtime_dependencies(solidlsp_settings)
        self.runtime_dependency_paths = runtime_dependency_paths

        # Get jar options from configuration
        ls_jar_options = []

        if solidlsp_settings.ls_specific_settings:
            groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY)
            jar_options_str = groovy_settings.get("ls_jar_options", "")
            if jar_options_str:
                ls_jar_options = shlex.split(jar_options_str)
                log.info(f"Using Groovy LS JAR options from configuration: {jar_options_str}")

        # Create command to execute the Groovy Language Server
        cmd = [self.runtime_dependency_paths.java_path, "-jar", self.runtime_dependency_paths.ls_jar_path]
        cmd.extend(ls_jar_options)

        # Set environment variables including JAVA_HOME
        proc_env = {"JAVA_HOME": self.runtime_dependency_paths.java_home_path}

        super().__init__(
            config,
            repository_root_path,
            ProcessLaunchInfo(cmd=cmd, env=proc_env, cwd=repository_root_path),
            "groovy",
            solidlsp_settings,
        )

        log.info(f"Starting Groovy Language Server with jar options: {ls_jar_options}")

    @classmethod
    def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> GroovyRuntimeDependencyPaths:
        """
        Setup runtime dependencies for Groovy Language Server and return paths.
        """
        platform_id = PlatformUtils.get_platform_id()
        groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY)
        vscode_java_version = groovy_settings.get("vscode_java_version", DEFAULT_VSCODE_JAVA_VERSION)
        vscode_java_tag = f"v{vscode_java_version.rsplit('-', 1)[0]}"

        # Verify platform support
        assert platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-"), (
            "Only Windows, Linux and macOS platforms are supported for Groovy in multilspy at the moment"
        )

        # Check if user specified custom Java home path
        java_home_path = None
        java_path = None

        if solidlsp_settings and solidlsp_settings.ls_specific_settings:
            groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY)
            custom_java_home = groovy_settings.get("ls_java_home_path")
            if custom_java_home:
                log.info(f"Using custom Java home path from configuration: {custom_java_home}")
                java_home_path = custom_java_home

                # Determine java executable path based on platform
                if platform_id.value.startswith("win-"):
                    java_path = os.path.join(java_home_path, "bin", "java.exe")
                else:
                    java_path = os.path.join(java_home_path, "bin", "java")

        # If no custom Java home path, download and use bundled Java
        if java_home_path is None:
            # Runtime dependency information
            runtime_dependencies = {
                "java": {
                    "win-x64": {
                        "url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-win32-x64-{vscode_java_version}.vsix",
                        "archiveType": "zip",
                        "java_home_path": "extension/jre/21.0.7-win32-x86_64",
                        "java_path": "extension/jre/21.0.7-win32-x86_64/bin/java.exe",
                        "sha256": _vscode_java_sha(vscode_java_version, "win-x64"),
                    },
                    "linux-x64": {
                        "url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-linux-x64-{vscode_java_version}.vsix",
                        "archiveType": "zip",
                        "java_home_path": "extension/jre/21.0.7-linux-x86_64",
                        "java_path": "extension/jre/21.0.7-linux-x86_64/bin/java",
                        "sha256": _vscode_java_sha(vscode_java_version, "linux-x64"),
                    },
                    "linux-arm64": {
                        "url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-linux-arm64-{vscode_java_version}.vsix",
                        "archiveType": "zip",
                        "java_home_path": "extension/jre/21.0.7-linux-aarch64",
                        "java_path": "extension/jre/21.0.7-linux-aarch64/bin/java",
                        "sha256": _vscode_java_sha(vscode_java_version, "linux-arm64"),
                    },
                    "osx-x64": {
                        "url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-darwin-x64-{vscode_java_version}.vsix",
                        "archiveType": "zip",
                        "java_home_path": "extension/jre/21.0.7-macosx-x86_64",
                        "java_path": "extension/jre/21.0.7-macosx-x86_64/bin/java",
                        "sha256": _vscode_java_sha(vscode_java_version, "osx-x64"),
                    },
                    "osx-arm64": {
                        "url": f"https://github.com/redhat-developer/vscode-java/releases/download/{vscode_java_tag}/java-darwin-arm64-{vscode_java_version}.vsix",
                        "archiveType": "zip",
                        "java_home_path": "extension/jre/21.0.7-macosx-aarch64",
                        "java_path": "extension/jre/21.0.7-macosx-aarch64/bin/java",
                        "sha256": _vscode_java_sha(vscode_java_version, "osx-arm64"),
                    },
                },
            }

            java_dependency = runtime_dependencies["java"][platform_id.value]
            java_home_relative_path = java_dependency["java_home_path"]
            java_relative_path = java_dependency["java_path"]
            java_download_url = java_dependency["url"]
            java_archive_type = java_dependency["archiveType"]
            assert java_home_relative_path is not None
            assert java_relative_path is not None
            assert java_download_url is not None
            assert java_archive_type is not None

            static_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "groovy_language_server")
            os.makedirs(static_dir, exist_ok=True)

            # legacy unversioned "java" dir reserved for INITIAL; every other version goes into a versioned subdir
            java_dirname = "java" if vscode_java_version == INITIAL_VSCODE_JAVA_VERSION else f"java-{vscode_java_version}"
            java_dir = os.path.join(static_dir, java_dirname)
            os.makedirs(java_dir, exist_ok=True)

            java_home_path = os.path.join(java_dir, java_home_relative_path)
            java_path = os.path.join(java_dir, java_relative_path)

            if not os.path.exists(java_path):
                log.info(f"Downloading Java for {platform_id.value}...")
                FileUtils.download_and_extract_archive_verified(
                    java_download_url,
                    java_dir,
                    java_archive_type,
                    expected_sha256=java_dependency["sha256"],
                    allowed_hosts=VSCODE_JAVA_ALLOWED_HOSTS,
                )

                if not platform_id.value.startswith("win-"):
                    os.chmod(java_path, 0o755)

        assert java_path and os.path.exists(java_path), f"Java executable not found at {java_path}"

        ls_jar_path = cls._find_groovy_ls_jar(solidlsp_settings)

        return GroovyRuntimeDependencyPaths(java_path=java_path, java_home_path=java_home_path, ls_jar_path=ls_jar_path)

    @classmethod
    def _find_groovy_ls_jar(cls, solidlsp_settings: SolidLSPSettings) -> str:
        """
        Find Groovy Language Server JAR file
        """
        if solidlsp_settings and solidlsp_settings.ls_specific_settings:
            groovy_settings = solidlsp_settings.get_ls_specific_settings(Language.GROOVY)
            config_jar_path = groovy_settings.get("ls_jar_path")
            if config_jar_path and os.path.exists(config_jar_path):
                log.info(f"Using Groovy LS JAR from configuration: {config_jar_path}")
                return config_jar_path

        # if JAR not found
        raise RuntimeError(
            "Groovy Language Server JAR not found. To use Groovy language support:\n"
            "Set 'ls_jar_path' in groovy settings in serena_config.yml:\n"
            "   ls_specific_settings:\n"
            "     groovy:\n"
            "       ls_jar_path: '/path/to/groovy-language-server.jar'\n"
            "   Ensure the JAR file is available at the configured path\n"
        )

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the Groovy Language Server.
        """
        if not os.path.isabs(repository_absolute_path):
            repository_absolute_path = os.path.abspath(repository_absolute_path)

        root_uri = pathlib.Path(repository_absolute_path).as_uri()
        initialize_params = {
            "clientInfo": {"name": "Serena Groovy Client", "version": "1.0.0"},
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "capabilities": {
                "textDocument": {
                    "synchronization": {"dynamicRegistration": True, "didSave": True},
                    "completion": {"dynamicRegistration": True},
                    "hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
                    "definition": {"dynamicRegistration": True},
                    "references": {"dynamicRegistration": True},
                    "documentSymbol": {"dynamicRegistration": True},
                    "workspaceSymbol": {"dynamicRegistration": True},
                    "signatureHelp": {"dynamicRegistration": True},
                    "rename": {"dynamicRegistration": True},
                },
                "workspace": {
                    "workspaceFolders": True,
                },
            },
            "initializationOptions": {
                "settings": {
                    "groovy": {
                        "classpath": [],
                        "diagnostics": {"enabled": True},
                        "completion": {"enabled": True},
                    }
                },
            },
            "processId": os.getpid(),
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
        }
        return initialize_params  # type: ignore

    def _start_server(self) -> None:
        """
        Starts the Groovy Language Server
        """

        def execute_client_command_handler(params: dict) -> list:
            return []

        def do_nothing(params: dict) -> None:
            return

        def window_log_message(msg: dict) -> None:
            log.info(f"LSP: window/logMessage: {msg}")

        self.server.on_request("client/registerCapability", do_nothing)
        self.server.on_notification("language/status", do_nothing)
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
        self.server.on_notification("language/actionableNotification", do_nothing)

        log.info("Starting Groovy server process")
        self.server.start()
        initialize_params = self._get_initialize_params(self.repository_root_path)

        log.info("Sending initialize request from LSP client to LSP server and awaiting response")
        init_response = self.server.send.initialize(initialize_params)

        capabilities = init_response["capabilities"]
        assert "textDocumentSync" in capabilities, "Server must support textDocumentSync"
        assert "hoverProvider" in capabilities, "Server must support hover"
        assert "completionProvider" in capabilities, "Server must support code completion"
        assert "signatureHelpProvider" in capabilities, "Server must support signature help"
        assert "definitionProvider" in capabilities, "Server must support go to definition"
        assert "referencesProvider" in capabilities, "Server must support find references"
        assert "documentSymbolProvider" in capabilities, "Server must support document symbols"
        assert "workspaceSymbolProvider" in capabilities, "Server must support workspace symbols"

        self.server.notify.initialized({})
