"""
Provides F# specific instantiation of the LanguageServer class.
"""

import logging
import os
import pathlib
import shutil
import threading
from pathlib import Path

from overrides import override

from serena.util.dotnet import DotNETUtil
from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_exceptions import SolidLSPException
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__)

FSAUTOCOMPLETE_VERSION = "0.83.0"


class FSharpLanguageServer(SolidLanguageServer):
    """
    Provides F# specific instantiation of the LanguageServer class using Ionide LSP (FsAutoComplete).
    Contains various configurations and settings specific to F# development.

    You can pass the following entries in ``ls_specific_settings["fsharp"]``:
        - fsautocomplete_version: Override the pinned FsAutoComplete version
          installed by Serena (default: the bundled Serena version).
    """

    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
        """
        Creates an FSharpLanguageServer instance. This class is not meant to be instantiated directly.
        Use LanguageServer.create() instead.
        """
        fsharp_lsp_executable_path = self._setup_runtime_dependencies(config, solidlsp_settings)
        super().__init__(
            config,
            repository_root_path,
            ProcessLaunchInfo(cmd=fsharp_lsp_executable_path, cwd=repository_root_path),
            "fsharp",
            solidlsp_settings,
        )
        self.server_ready = threading.Event()
        self.initialize_searcher_command_available = threading.Event()

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        return super().is_ignored_dirname(dirname) or dirname in [
            "bin",
            "obj",
            "packages",
            ".paket",
            "paket-files",
            ".fake",
            ".ionide",
        ]

    @classmethod
    def _setup_runtime_dependencies(cls, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings) -> str:
        """
        Setup runtime dependencies for F# Language Server and return the command to start the server.
        """
        fsharp_settings = solidlsp_settings.get_ls_specific_settings(Language.FSHARP)
        fsautocomplete_version = fsharp_settings.get("fsautocomplete_version", FSAUTOCOMPLETE_VERSION)
        dotnet_exe = DotNETUtil("8.0", allow_higher_version=True).get_dotnet_path_or_raise()

        RuntimeDependencyCollection(
            [
                RuntimeDependency(
                    id="fsautocomplete",
                    description="FsAutoComplete (Ionide F# Language Server)",
                    command=f"dotnet tool install --tool-path ./ fsautocomplete --version {fsautocomplete_version}",
                    platform_id="any",
                ),
            ]
        )

        # Install FsAutoComplete if not already installed
        fsharp_ls_dir = os.path.join(cls.ls_resources_dir(solidlsp_settings), "fsharp-lsp")
        fsautocomplete_path = os.path.join(fsharp_ls_dir, "fsautocomplete")

        # Handle Windows executable extension
        if os.name == "nt":
            fsautocomplete_path += ".exe"

        if not os.path.exists(fsautocomplete_path):
            log.info(f"FsAutoComplete executable not found at {fsautocomplete_path}. Installing...")

            # Ensure the directory exists
            os.makedirs(fsharp_ls_dir, exist_ok=True)

            # Install FsAutoComplete using dotnet tool install
            try:
                import subprocess

                result = subprocess.run(
                    [dotnet_exe, "tool", "install", "--tool-path", fsharp_ls_dir, "fsautocomplete", "--version", fsautocomplete_version],
                    cwd=fsharp_ls_dir,
                    capture_output=True,
                    text=True,
                    check=True,
                )
                log.info("FsAutoComplete installed successfully")
                log.debug(f"Installation output: {result.stdout}")
            except subprocess.CalledProcessError as e:
                log.error(f"Failed to install FsAutoComplete: {e.stderr}")
                raise RuntimeError(f"Failed to install FsAutoComplete: {e.stderr}")

        if not os.path.exists(fsautocomplete_path):
            raise FileNotFoundError(
                f"FsAutoComplete executable not found at {fsautocomplete_path}, something went wrong with the installation."
            )

        # FsAutoComplete uses --lsp flag for LSP mode
        return f"{fsautocomplete_path} --adaptive-lsp-server-enabled --project-graph-enabled --use-fcs-transparent-compiler"

    def _get_initialize_params(self) -> InitializeParams:
        """
        Returns the initialize params for the F# Language Server.
        """
        root_uri = pathlib.Path(self.repository_root_path).as_uri()

        initialize_params = {
            "processId": os.getpid(),
            "rootPath": self.repository_root_path,
            "rootUri": root_uri,
            "workspaceFolders": [{"name": "workspace", "uri": root_uri}],
            "capabilities": {
                "workspace": {
                    "applyEdit": True,
                    "workspaceEdit": {"documentChanges": True},
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "didChangeWatchedFiles": {"dynamicRegistration": True},
                    "symbol": {"dynamicRegistration": True},
                    "executeCommand": {"dynamicRegistration": True},
                    "configuration": True,
                    "workspaceFolders": True,
                },
                "textDocument": {
                    "synchronization": {
                        "dynamicRegistration": True,
                        "willSave": True,
                        "willSaveWaitUntil": True,
                        "didSave": True,
                    },
                    "completion": {
                        "dynamicRegistration": True,
                        "contextSupport": True,
                        "completionItem": {
                            "snippetSupport": True,
                            "commitCharactersSupport": True,
                            "documentationFormat": ["markdown", "plaintext"],
                            "deprecatedSupport": True,
                        },
                    },
                    "hover": {
                        "dynamicRegistration": True,
                        "contentFormat": ["markdown", "plaintext"],
                    },
                    "signatureHelp": {
                        "dynamicRegistration": True,
                        "signatureInformation": {"documentationFormat": ["markdown", "plaintext"]},
                    },
                    "definition": {"dynamicRegistration": True},
                    "references": {"dynamicRegistration": True},
                    "documentHighlight": {"dynamicRegistration": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "symbolKind": {"valueSet": list(range(1, 27))},  # All SymbolKind values (1-26)
                        "hierarchicalDocumentSymbolSupport": True,
                    },
                    "codeAction": {
                        "dynamicRegistration": True,
                        "codeActionLiteralSupport": {
                            "codeActionKind": {
                                "valueSet": [
                                    "",
                                    "quickfix",
                                    "refactor",
                                    "refactor.extract",
                                    "refactor.inline",
                                    "refactor.rewrite",
                                    "source",
                                    "source.organizeImports",
                                ]
                            }
                        },
                    },
                    "codeLens": {"dynamicRegistration": True},
                    "formatting": {"dynamicRegistration": True},
                    "rangeFormatting": {"dynamicRegistration": True},
                    "onTypeFormatting": {"dynamicRegistration": True},
                    "rename": {"dynamicRegistration": True},
                    "documentLink": {"dynamicRegistration": True},
                    "publishDiagnostics": {
                        "relatedInformation": True,
                        "versionSupport": False,
                        "tagSupport": {"valueSet": [1, 2]},
                    },
                    "implementation": {"dynamicRegistration": True},
                    "typeDefinition": {"dynamicRegistration": True},
                    "colorProvider": {"dynamicRegistration": True},
                    "foldingRange": {
                        "dynamicRegistration": True,
                        "rangeLimit": 5000,
                        "lineFoldingOnly": True,
                    },
                    "declaration": {"dynamicRegistration": True},
                    "selectionRange": {"dynamicRegistration": True},
                },
                "window": {
                    "workDoneProgress": True,
                },
            },
            "initializationOptions": {
                # F# specific initialization options
                "automaticWorkspaceInit": True,
                "abstractClassStubGeneration": True,
                "abstractClassStubGenerationObjectIdentifier": "this",
                "abstractClassStubGenerationMethodBody": 'failwith "Not Implemented"',
                "addFsiWatcher": False,
                "codeLenses": {"signature": {"enabled": True}, "references": {"enabled": True}},
                "disableInMemoryProjectReferences": False,
                "dotNetRoot": self._get_dotnet_root(),
                "enableMSBuildProjectGraph": False,
                "excludeProjectDirectories": ["paket-files"],
                "externalAutocomplete": False,
                "fsac": {"attachDebugger": False, "silencedLogs": [], "conserveMemory": False, "netCoreDllPath": ""},
                "fsiExtraParameters": [],
                "generateBinlog": False,
                "interfaceStubGeneration": True,
                "interfaceStubGenerationObjectIdentifier": "this",
                "interfaceStubGenerationMethodBody": 'failwith "Not Implemented"',
                "keywordsAutocomplete": True,
                "linter": True,
                "pipelineHints": {"enabled": True},
                "recordStubGeneration": True,
                "recordStubGenerationBody": 'failwith "Not Implemented"',
                "resolveNamespaces": True,
                "saveOnlyOpenFiles": False,
                "showProjectExplorerIn": ["ionide", "solution"],
                "simplifyNameAnalyzer": True,
                "smartIndent": False,
                "suggestGitignore": True,
                "suggestSdkScripts": True,
                "unionCaseStubGeneration": True,
                "unionCaseStubGenerationBody": 'failwith "Not Implemented"',
                "unusedDeclarationsAnalyzer": True,
                "unusedOpensAnalyzer": True,
                "verboseLogging": False,
                "workspaceModePeekDeepLevel": 2,
                "workspacePath": self.repository_root_path,
            },
            "trace": "off",
        }

        return initialize_params  # type: ignore

    def _get_dotnet_root(self) -> str:
        """
        Get the .NET root directory.
        """
        dotnet_exe = shutil.which("dotnet")
        if dotnet_exe:
            # Try to get the installation path
            try:
                import subprocess

                result = subprocess.run([dotnet_exe, "--info"], capture_output=True, text=True, check=True)
                lines = result.stdout.split("\n")
                for line in lines:
                    if "Base Path:" in line or "Base path:" in line:
                        base_path = line.split(":", 1)[1].strip()
                        # Get the parent directory (remove 'sdk/version' part)
                        return str(Path(base_path).parent.parent)
            except (subprocess.CalledProcessError, Exception):
                pass

        # Fallback: use the directory containing dotnet executable
        if dotnet_exe:
            return str(Path(dotnet_exe).parent)

        return ""

    def _start_server(self) -> None:
        """
        Start the F# Language Server with custom handlers.
        """

        def handle_window_log_message(params: dict) -> None:
            """Handle window/logMessage from the LSP server."""
            message = params.get("message", "")
            message_type = params.get("type", 1)

            # Map LSP log levels to Python logging levels
            level_map = {1: logging.ERROR, 2: logging.WARNING, 3: logging.INFO, 4: logging.DEBUG}
            level = level_map.get(message_type, logging.INFO)

            log.log(level, f"FsAutoComplete: {message}")

        def handle_window_show_message(params: dict) -> None:
            """Handle window/showMessage from the LSP server."""
            message = params.get("message", "")
            message_type = params.get("type", 1)

            # Map LSP message types to Python logging levels
            level_map = {1: logging.ERROR, 2: logging.WARNING, 3: logging.INFO, 4: logging.DEBUG}
            level = level_map.get(message_type, logging.INFO)

            log.log(level, f"FsAutoComplete Message: {message}")

        def handle_workspace_configuration(params: dict) -> list:
            """Handle workspace/configuration requests from the LSP server."""
            # Return empty configuration for now
            items = params.get("items", [])
            return [None] * len(items)

        def handle_client_register_capability(params: dict) -> None:
            """Handle client/registerCapability requests from the LSP server."""
            # For now, just acknowledge the registration
            return

        def handle_client_unregister_capability(params: dict) -> None:
            """Handle client/unregisterCapability requests from the LSP server."""
            # For now, just acknowledge the unregistration
            return

        def handle_work_done_progress_create(params: dict) -> None:
            """Handle window/workDoneProgress/create requests from the LSP server."""
            # Just acknowledge the request - we don't need to track progress for now
            return

        # Register custom handlers
        self.server.on_notification("window/logMessage", handle_window_log_message)
        self.server.on_notification("window/showMessage", handle_window_show_message)
        self.server.on_request("workspace/configuration", handle_workspace_configuration)
        self.server.on_request("client/registerCapability", handle_client_register_capability)
        self.server.on_request("client/unregisterCapability", handle_client_unregister_capability)
        self.server.on_request("window/workDoneProgress/create", handle_work_done_progress_create)

        log.info("Starting FsAutoComplete F# language server process")

        try:
            self.server.start()
        except Exception as e:
            log.error(f"Failed to start F# language server process: {e}")
            raise SolidLSPException(f"Failed to start F# language server: {e}")

        # Send initialization
        initialize_params = self._get_initialize_params()

        log.info("Sending initialize request to F# language server")
        try:
            self.server.send.initialize(initialize_params)
            log.debug("Received initialize response from F# language server")
        except Exception as e:
            raise SolidLSPException(f"Failed to initialize F# language server for {self.repository_root_path}: {e}") from e

        # Complete initialization
        self.server.notify.initialized({})

        log.info("F# language server initialized successfully")

    @override
    def _get_wait_time_for_cross_file_referencing(self) -> float:
        """
        F# projects can be large and may need more time for cross-file analysis.
        """
        return 15.0  # 15 seconds should be sufficient for most F# projects
