import logging
import os
import pathlib
import subprocess
import time
from collections.abc import Hashable

from overrides import override

from solidlsp import ls_types
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_types import SymbolKind
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__)


class SourceKitLSP(SolidLanguageServer):
    """
    Provides Swift specific instantiation of the LanguageServer class using sourcekit-lsp.
    """

    @override
    def is_ignored_dirname(self, dirname: str) -> bool:
        # For Swift projects, we should ignore:
        # - .build: Swift Package Manager build artifacts
        # - .swiftpm: Swift Package Manager metadata
        # - node_modules: if the project has JavaScript components
        # - dist/build: common output directories
        return super().is_ignored_dirname(dirname) or dirname in [".build", ".swiftpm", "node_modules", "dist", "build"]

    @staticmethod
    def _get_sourcekit_lsp_version() -> str:
        """Get the installed sourcekit-lsp version or raise error if sourcekit was not found."""
        try:
            result = subprocess.run(["sourcekit-lsp", "-h"], capture_output=True, text=True, check=False)
            if result.returncode == 0:
                return result.stdout.strip()
            else:
                raise Exception(f"`sourcekit-lsp -h` resulted in: {result}")
        except Exception as e:
            raise RuntimeError(
                "Could not find sourcekit-lsp, please install it as described in https://github.com/apple/sourcekit-lsp#installation"
                "And make sure it is available on your PATH."
            ) from e

    def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
        sourcekit_version = self._get_sourcekit_lsp_version()
        log.info(f"Starting sourcekit lsp with version: {sourcekit_version}")

        super().__init__(
            config, repository_root_path, ProcessLaunchInfo(cmd="sourcekit-lsp", cwd=repository_root_path), "swift", solidlsp_settings
        )
        self.request_id = 0
        self._did_sleep_before_requesting_references = False
        self._initialization_timestamp: float | None = None

    @override
    def _document_symbols_cache_fingerprint(self) -> Hashable:
        normalize_symbol_name_version = 1
        return normalize_symbol_name_version

    @override
    def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
        original_name = symbol["name"]

        if symbol.get("kind") not in (SymbolKind.Function, SymbolKind.Method, SymbolKind.Constructor):
            return original_name

        if "(" not in original_name:
            return original_name

        return original_name.split("(", 1)[0].strip()

    @staticmethod
    def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
        """
        Returns the initialize params for the Swift Language Server.
        """
        root_uri = pathlib.Path(repository_absolute_path).as_uri()

        initialize_params = {
            "capabilities": {
                "general": {
                    "markdown": {"parser": "marked", "version": "1.1.0"},
                    "positionEncodings": ["utf-16"],
                    "regularExpressions": {"engine": "ECMAScript", "version": "ES2020"},
                    "staleRequestSupport": {
                        "cancel": True,
                        "retryOnContentModified": [
                            "textDocument/semanticTokens/full",
                            "textDocument/semanticTokens/range",
                            "textDocument/semanticTokens/full/delta",
                        ],
                    },
                },
                "notebookDocument": {"synchronization": {"dynamicRegistration": True, "executionSummarySupport": True}},
                "textDocument": {
                    "callHierarchy": {"dynamicRegistration": True},
                    "codeAction": {
                        "codeActionLiteralSupport": {
                            "codeActionKind": {
                                "valueSet": [
                                    "",
                                    "quickfix",
                                    "refactor",
                                    "refactor.extract",
                                    "refactor.inline",
                                    "refactor.rewrite",
                                    "source",
                                    "source.organizeImports",
                                ]
                            }
                        },
                        "dataSupport": True,
                        "disabledSupport": True,
                        "dynamicRegistration": True,
                        "honorsChangeAnnotations": True,
                        "isPreferredSupport": True,
                        "resolveSupport": {"properties": ["edit"]},
                    },
                    "codeLens": {"dynamicRegistration": True},
                    "colorProvider": {"dynamicRegistration": True},
                    "completion": {
                        "completionItem": {
                            "commitCharactersSupport": True,
                            "deprecatedSupport": True,
                            "documentationFormat": ["markdown", "plaintext"],
                            "insertReplaceSupport": True,
                            "insertTextModeSupport": {"valueSet": [1, 2]},
                            "labelDetailsSupport": True,
                            "preselectSupport": True,
                            "resolveSupport": {"properties": ["documentation", "detail", "additionalTextEdits"]},
                            "snippetSupport": True,
                            "tagSupport": {"valueSet": [1]},
                        },
                        "completionItemKind": {
                            "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
                        },
                        "completionList": {"itemDefaults": ["commitCharacters", "editRange", "insertTextFormat", "insertTextMode", "data"]},
                        "contextSupport": True,
                        "dynamicRegistration": True,
                        "insertTextMode": 2,
                    },
                    "declaration": {"dynamicRegistration": True, "linkSupport": True},
                    "definition": {"dynamicRegistration": True, "linkSupport": True},
                    "diagnostic": {"dynamicRegistration": True, "relatedDocumentSupport": False},
                    "documentHighlight": {"dynamicRegistration": True},
                    "documentLink": {"dynamicRegistration": True, "tooltipSupport": True},
                    "documentSymbol": {
                        "dynamicRegistration": True,
                        "hierarchicalDocumentSymbolSupport": True,
                        "labelSupport": True,
                        "symbolKind": {
                            "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
                        },
                        "tagSupport": {"valueSet": [1]},
                    },
                    "foldingRange": {
                        "dynamicRegistration": True,
                        "foldingRange": {"collapsedText": False},
                        "foldingRangeKind": {"valueSet": ["comment", "imports", "region"]},
                        "lineFoldingOnly": True,
                        "rangeLimit": 5000,
                    },
                    "formatting": {"dynamicRegistration": True},
                    "hover": {"contentFormat": ["markdown", "plaintext"], "dynamicRegistration": True},
                    "implementation": {"dynamicRegistration": True, "linkSupport": True},
                    "inlayHint": {
                        "dynamicRegistration": True,
                        "resolveSupport": {"properties": ["tooltip", "textEdits", "label.tooltip", "label.location", "label.command"]},
                    },
                    "inlineValue": {"dynamicRegistration": True},
                    "linkedEditingRange": {"dynamicRegistration": True},
                    "onTypeFormatting": {"dynamicRegistration": True},
                    "publishDiagnostics": {
                        "codeDescriptionSupport": True,
                        "dataSupport": True,
                        "relatedInformation": True,
                        "tagSupport": {"valueSet": [1, 2]},
                        "versionSupport": False,
                    },
                    "rangeFormatting": {"dynamicRegistration": True, "rangesSupport": True},
                    "references": {"dynamicRegistration": True},
                    "rename": {
                        "dynamicRegistration": True,
                        "honorsChangeAnnotations": True,
                        "prepareSupport": True,
                        "prepareSupportDefaultBehavior": 1,
                    },
                    "selectionRange": {"dynamicRegistration": True},
                    "semanticTokens": {
                        "augmentsSyntaxTokens": True,
                        "dynamicRegistration": True,
                        "formats": ["relative"],
                        "multilineTokenSupport": False,
                        "overlappingTokenSupport": False,
                        "requests": {"full": {"delta": True}, "range": True},
                        "serverCancelSupport": True,
                        "tokenModifiers": [
                            "declaration",
                            "definition",
                            "readonly",
                            "static",
                            "deprecated",
                            "abstract",
                            "async",
                            "modification",
                            "documentation",
                            "defaultLibrary",
                        ],
                        "tokenTypes": [
                            "namespace",
                            "type",
                            "class",
                            "enum",
                            "interface",
                            "struct",
                            "typeParameter",
                            "parameter",
                            "variable",
                            "property",
                            "enumMember",
                            "event",
                            "function",
                            "method",
                            "macro",
                            "keyword",
                            "modifier",
                            "comment",
                            "string",
                            "number",
                            "regexp",
                            "operator",
                            "decorator",
                        ],
                    },
                    "signatureHelp": {
                        "contextSupport": True,
                        "dynamicRegistration": True,
                        "signatureInformation": {
                            "activeParameterSupport": True,
                            "documentationFormat": ["markdown", "plaintext"],
                            "parameterInformation": {"labelOffsetSupport": True},
                        },
                    },
                    "synchronization": {"didSave": True, "dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True},
                    "typeDefinition": {"dynamicRegistration": True, "linkSupport": True},
                    "typeHierarchy": {"dynamicRegistration": True},
                },
                "window": {
                    "showDocument": {"support": True},
                    "showMessage": {"messageActionItem": {"additionalPropertiesSupport": True}},
                    "workDoneProgress": True,
                },
                "workspace": {
                    "applyEdit": True,
                    "codeLens": {"refreshSupport": True},
                    "configuration": True,
                    "diagnostics": {"refreshSupport": True},
                    "didChangeConfiguration": {"dynamicRegistration": True},
                    "didChangeWatchedFiles": {"dynamicRegistration": True, "relativePatternSupport": True},
                    "executeCommand": {"dynamicRegistration": True},
                    "fileOperations": {
                        "didCreate": True,
                        "didDelete": True,
                        "didRename": True,
                        "dynamicRegistration": True,
                        "willCreate": True,
                        "willDelete": True,
                        "willRename": True,
                    },
                    "foldingRange": {"refreshSupport": True},
                    "inlayHint": {"refreshSupport": True},
                    "inlineValue": {"refreshSupport": True},
                    "semanticTokens": {"refreshSupport": False},
                    "symbol": {
                        "dynamicRegistration": True,
                        "resolveSupport": {"properties": ["location.range"]},
                        "symbolKind": {
                            "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
                        },
                        "tagSupport": {"valueSet": [1]},
                    },
                    "workspaceEdit": {
                        "changeAnnotationSupport": {"groupsOnLabel": True},
                        "documentChanges": True,
                        "failureHandling": "textOnlyTransactional",
                        "normalizesLineEndings": True,
                        "resourceOperations": ["create", "rename", "delete"],
                    },
                    "workspaceFolders": True,
                },
            },
            "clientInfo": {"name": "Visual Studio Code", "version": "1.102.2"},
            "initializationOptions": {
                "backgroundIndexing": True,
                "backgroundPreparationMode": "enabled",
                "textDocument/codeLens": {"supportedCommands": {"swift.debug": "swift.debug", "swift.run": "swift.run"}},
                "window/didChangeActiveDocument": True,
                "workspace/getReferenceDocument": True,
                "workspace/peekDocuments": True,
            },
            "locale": "en",
            "processId": os.getpid(),
            "rootPath": repository_absolute_path,
            "rootUri": root_uri,
            "workspaceFolders": [
                {
                    "uri": root_uri,
                    "name": os.path.basename(repository_absolute_path),
                }
            ],
        }

        return initialize_params  # type: ignore[return-value]

    def _start_server(self) -> None:
        """Start sourcekit-lsp server process"""

        def register_capability_handler(_params: dict) -> None:
            return

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

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

        self.server.on_request("client/registerCapability", register_capability_handler)
        self.server.on_notification("window/logMessage", window_log_message)
        self.server.on_notification("$/progress", do_nothing)
        self.server.on_notification("textDocument/publishDiagnostics", do_nothing)

        log.info("Starting sourcekit-lsp 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"]
        log.info(f"SourceKit LSP capabilities: {list(capabilities.keys())}")

        assert "textDocumentSync" in capabilities, "textDocumentSync capability missing"
        assert "definitionProvider" in capabilities, "definitionProvider capability missing"

        self.server.notify.initialized({})

        # Mark initialization timestamp for smarter delay calculation
        self._initialization_timestamp = time.time()

    @override
    def request_references(self, relative_file_path: str, line: int, column: int) -> list[ls_types.Location]:
        # SourceKit LSP needs initialization + indexing time after startup
        # before it can provide accurate reference information. This sleep
        # prevents race conditions where references might not be available yet.
        # CI environments need extra time for project indexing and cross-file analysis
        if not self._did_sleep_before_requesting_references:
            # Calculate minimum delay based on how much time has passed since initialization
            if self._initialization_timestamp:
                elapsed = time.time() - self._initialization_timestamp
                # Increased CI delay for project indexing: 15s CI, 5s local
                base_delay = 15 if os.getenv("CI") else 5
                remaining_delay = max(2, base_delay - elapsed)
            else:
                # Fallback if initialization timestamp is missing
                remaining_delay = 15 if os.getenv("CI") else 5

            log.info(f"Sleeping {remaining_delay:.1f}s before requesting references for the first time (CI needs extra indexing time)")
            time.sleep(remaining_delay)
            self._did_sleep_before_requesting_references = True

        # Get references with retry logic for CI stability
        references = super().request_references(relative_file_path, line, column)

        # In CI, if no references found, retry once after additional delay
        if os.getenv("CI") and not references:
            log.info("No references found in CI - retrying after additional 5s delay")
            time.sleep(5)
            references = super().request_references(relative_file_path, line, column)

        return references
