"""
LSP 工具

提供给 AI Agent 使用的 LSP 操作工具。
支持 goToDefinition、findReferences、hover 等操作。
"""

import os
from typing import Any, Literal
from pydantic import BaseModel, Field

from .base import BaseTool
from lsp.manager import get_lsp_manager
from lsp.server import get_server_for_file, get_available_servers
from lsp.protocol import SYMBOL_KIND_NAMES


# 语言服务器安装指引
LSP_INSTALL_HINTS = {
    "pyright": "npm install -g pyright",
    "typescript": "npm install -g typescript-language-server typescript",
    "gopls": "go install golang.org/x/tools/gopls@latest",
    "rust-analyzer": "rustup component add rust-analyzer",
    "clangd": "安装 LLVM/Clang 或通过包管理器安装 clangd",
    "jdtls": "安装 Eclipse JDT Language Server",
    "ruby-lsp": "gem install ruby-lsp",
    "intelephense": "npm install -g intelephense",
    "sourcekit-lsp": "Xcode 自带，或通过 Swift 工具链安装",
    "kotlin-language-server": "从 GitHub releases 下载安装",
    "lua-language-server": "从 GitHub releases 下载安装",
    "bash-language-server": "npm install -g bash-language-server",
    "yaml-language-server": "npm install -g yaml-language-server",
    "vscode-json-languageserver": "npm install -g vscode-json-languageserver",
    "elixir-ls": "从 GitHub releases 下载安装",
    "haskell-language-server": "ghcup install hls",
    "zls": "从 GitHub releases 下载安装",
    "nixd": "nix-env -iA nixpkgs.nixd",
    "ocamllsp": "opam install ocaml-lsp-server",
    "gleam": "已包含在 gleam 命令中",
    "dart": "已包含在 dart 命令中",
    "vue-language-server": "npm install -g @vue/language-server",
    "svelte-language-server": "npm install -g svelte-language-server",
    "astro-ls": "npm install -g @astrojs/language-server",
}


# 支持的操作类型
LSP_OPERATIONS = Literal[
    "goToDefinition",
    "findReferences",
    "hover",
    "documentSymbol",
    "workspaceSymbol",
    "goToImplementation",
    "incomingCalls",
    "outgoingCalls",
]


class LspToolInput(BaseModel):
    """LSP 工具输入参数"""

    operation: str = Field(
        description="要执行的 LSP 操作: goToDefinition, findReferences, hover, documentSymbol, workspaceSymbol, goToImplementation, incomingCalls, outgoingCalls"
    )
    file_path: str = Field(
        description="文件的绝对路径或相对路径"
    )
    line: int = Field(
        default=1,
        ge=1,
        description="行号 (1-based)，documentSymbol 操作可忽略"
    )
    character: int = Field(
        default=1,
        ge=1,
        description="字符偏移 (1-based)，documentSymbol 操作可忽略"
    )
    query: str = Field(
        default="",
        description="workspaceSymbol 操作的搜索查询"
    )


LSP_TOOL_DESCRIPTION_TEMPLATE = """使用 Language Server Protocol (LSP) 进行代码导航和分析。

支持的操作:
- goToDefinition: 跳转到符号定义
- findReferences: 查找所有引用
- hover: 获取符号的类型信息和文档
- documentSymbol: 获取文件中所有符号（函数、类、变量等）
- workspaceSymbol: 在整个工作区搜索符号
- goToImplementation: 跳转到接口/抽象方法的实现
- incomingCalls: 查看谁调用了这个函数
- outgoingCalls: 查看这个函数调用了哪些其他函数

当前可用的语言服务器（仅支持以下文件类型）:
{available_servers}

示例:
1. 查找函数定义: operation="goToDefinition", file_path="src/main.py", line=10, character=5
2. 查找所有引用: operation="findReferences", file_path="src/utils.py", line=20, character=10
3. 获取文件符号: operation="documentSymbol", file_path="src/models.py"
4. 搜索工作区符号: operation="workspaceSymbol", file_path="src/main.py", query="User"
"""


def _build_lsp_description() -> str:
    """构建包含可用服务器信息的工具描述"""
    available = get_available_servers()
    if not available:
        return "LSP 工具当前不可用（没有安装任何语言服务器）"

    server_lines = []
    for server in available:
        exts = ", ".join(server.extensions)
        server_lines.append(f"- {server.id}: {exts}")

    return LSP_TOOL_DESCRIPTION_TEMPLATE.format(
        available_servers="\n".join(server_lines)
    )


class LspTool(BaseTool):
    """LSP 工具"""

    name = "lsp"
    description = ""  # 动态生成
    args_schema = LspToolInput

    def __init__(self):
        """初始化 LSP 工具，动态生成包含可用服务器的描述"""
        self.description = _build_lsp_description()

    async def run(
        self,
        operation: str,
        file_path: str,
        line: int = 1,
        character: int = 1,
        query: str = "",
        **kwargs
    ) -> Any:
        """
        执行 LSP 操作

        Args:
            operation: 操作类型
            file_path: 文件路径
            line: 行号 (1-based)
            character: 字符偏移 (1-based)
            query: workspaceSymbol 的搜索查询

        Returns:
            操作结果的格式化字符串
        """
        # 解析文件路径
        if not os.path.isabs(file_path):
            file_path = os.path.abspath(file_path)

        if not os.path.exists(file_path):
            return f"错误: 文件不存在: {file_path}"

        # 检查该文件类型是否有对应的语言服务器
        server = get_server_for_file(file_path)
        if not server:
            ext = os.path.splitext(file_path)[1]
            return f"错误: 不支持的文件类型 {ext}，没有对应的语言服务器配置"

        if not server.is_available():
            install_hint = LSP_INSTALL_HINTS.get(server.id, f"请安装 {server.id}")
            return f"错误: 语言服务器 {server.id} 未安装\n安装方法: {install_hint}"

        manager = get_lsp_manager()

        try:
            if operation == "goToDefinition":
                return await self._go_to_definition(manager, file_path, line, character)
            elif operation == "findReferences":
                return await self._find_references(manager, file_path, line, character)
            elif operation == "hover":
                return await self._hover(manager, file_path, line, character)
            elif operation == "documentSymbol":
                return await self._document_symbol(manager, file_path)
            elif operation == "workspaceSymbol":
                return await self._workspace_symbol(manager, file_path, query)
            elif operation == "goToImplementation":
                return await self._go_to_implementation(manager, file_path, line, character)
            elif operation == "incomingCalls":
                return await self._incoming_calls(manager, file_path, line, character)
            elif operation == "outgoingCalls":
                return await self._outgoing_calls(manager, file_path, line, character)
            else:
                return f"错误: 不支持的操作: {operation}"

        except Exception as e:
            return f"LSP 操作失败: {str(e)}"

    async def _go_to_definition(self, manager, file_path: str, line: int, character: int) -> str:
        """跳转到定义"""
        locations = await manager.go_to_definition(file_path, line, character)

        if not locations:
            return f"未找到定义 ({file_path}:{line}:{character})"

        result = ["定义位置:"]
        for loc in locations:
            path = loc.to_file_path()
            start = loc.range.start
            result.append(f"  {path}:{start.line + 1}:{start.character + 1}")

        return "\n".join(result)

    async def _find_references(self, manager, file_path: str, line: int, character: int) -> str:
        """查找引用"""
        locations = await manager.find_references(file_path, line, character)

        if not locations:
            return f"未找到引用 ({file_path}:{line}:{character})"

        result = [f"找到 {len(locations)} 个引用:"]
        for loc in locations:
            path = loc.to_file_path()
            start = loc.range.start
            result.append(f"  {path}:{start.line + 1}:{start.character + 1}")

        return "\n".join(result)

    async def _hover(self, manager, file_path: str, line: int, character: int) -> str:
        """获取悬停信息"""
        hover = await manager.hover(file_path, line, character)

        if not hover:
            return f"无悬停信息 ({file_path}:{line}:{character})"

        return hover.get_text()

    async def _document_symbol(self, manager, file_path: str) -> str:
        """获取文档符号"""
        symbols = await manager.document_symbol(file_path)

        if not symbols:
            return f"未找到符号 ({file_path})"

        result = [f"文件符号 ({len(symbols)} 个):"]

        def format_symbol(symbol, indent=0):
            prefix = "  " * indent
            kind = symbol.get_kind_name()
            line = symbol.range.start.line + 1
            lines = [f"{prefix}- {symbol.name} ({kind}) [行 {line}]"]
            for child in symbol.children:
                lines.extend(format_symbol(child, indent + 1))
            return lines

        for symbol in symbols:
            result.extend(format_symbol(symbol))

        return "\n".join(result)

    async def _workspace_symbol(self, manager, file_path: str, query: str) -> str:
        """搜索工作区符号"""
        if not query:
            return "错误: workspaceSymbol 操作需要提供 query 参数"

        symbols = await manager.workspace_symbol(file_path, query)

        if not symbols:
            return f"未找到匹配 '{query}' 的符号"

        result = [f"找到 {len(symbols)} 个符号匹配 '{query}':"]
        for sym in symbols[:50]:  # 限制显示数量
            kind = sym.get_kind_name()
            path = sym.location.to_file_path()
            line = sym.location.range.start.line + 1
            container = f" in {sym.containerName}" if sym.containerName else ""
            result.append(f"  {sym.name} ({kind}){container} - {path}:{line}")

        if len(symbols) > 50:
            result.append(f"  ... 还有 {len(symbols) - 50} 个结果")

        return "\n".join(result)

    async def _go_to_implementation(self, manager, file_path: str, line: int, character: int) -> str:
        """跳转到实现"""
        locations = await manager.go_to_implementation(file_path, line, character)

        if not locations:
            return f"未找到实现 ({file_path}:{line}:{character})"

        result = ["实现位置:"]
        for loc in locations:
            path = loc.to_file_path()
            start = loc.range.start
            result.append(f"  {path}:{start.line + 1}:{start.character + 1}")

        return "\n".join(result)

    async def _incoming_calls(self, manager, file_path: str, line: int, character: int) -> str:
        """获取传入调用"""
        calls = await manager.incoming_calls(file_path, line, character)

        if not calls:
            return f"未找到调用者 ({file_path}:{line}:{character})"

        result = [f"被以下 {len(calls)} 个位置调用:"]
        for call in calls:
            item = call.from_
            path = item.uri
            if path.startswith("file://"):
                path = path[7:]
            line_num = item.range.start.line + 1
            result.append(f"  {item.name} - {path}:{line_num}")

        return "\n".join(result)

    async def _outgoing_calls(self, manager, file_path: str, line: int, character: int) -> str:
        """获取传出调用"""
        calls = await manager.outgoing_calls(file_path, line, character)

        if not calls:
            return f"未找到被调用的函数 ({file_path}:{line}:{character})"

        result = [f"调用了以下 {len(calls)} 个函数:"]
        for call in calls:
            item = call.to
            path = item.uri
            if path.startswith("file://"):
                path = path[7:]
            line_num = item.range.start.line + 1
            result.append(f"  {item.name} - {path}:{line_num}")

        return "\n".join(result)
