"""
MCP Tools for RAG System

Exposes RAG functionality as MCP tools for AI agents.
"""

from pathlib import Path
from typing import Annotated, Optional

from pydantic import Field

# Import error tracking for better diagnostics
_RAG_IMPORT_ERROR = None

# Robust dependency check with improved environment bridging
try:
    import chromadb  # noqa: F401
    from sentence_transformers import SentenceTransformer  # noqa: F401

    # Only import internal RAG module if dependencies exist
    from boring.rag import RAGRetriever, create_rag_retriever

    _RAG_IMPORT_ERROR = None
except ImportError:
    # Attempt to bridge isolated environments (e.g. MCP running in embedded Python)
    import site
    import sys

    # Strategy 1: User site packages
    try:
        user_site = site.getusersitepackages()
        if isinstance(user_site, str) and user_site not in sys.path:
            sys.path.append(user_site)
    except Exception:
        pass

    # Strategy 2: Global python site-packages (if accessible)
    # This matches where 'pip install' likely went if running basic python
    # We can't easily guess it, but we can try common paths or just rely on user_site.

    try:
        import chromadb  # noqa: F401
        from sentence_transformers import SentenceTransformer  # noqa: F401

        from boring.rag import RAGRetriever, create_rag_retriever

        _RAG_IMPORT_ERROR = None
    except ImportError as final_error:
        # Capture the specific missing dependency and environment info for debugging
        pip_cmd = f"{sys.executable} -m pip install chromadb sentence-transformers"
        debug_info = f"Python: {sys.executable}\nPaths: {sys.path}"
        _RAG_IMPORT_ERROR = (
            f"{str(final_error)}\n\n"
            f"[Recommended Fix]\n"
            f"Run this command:\n    {pip_cmd}\n\n"
            f"[Debug Info]\n{debug_info}"
        )
        RAGRetriever = None
        create_rag_retriever = None


# Singleton retriever instance (per project)
_retrievers: dict = {}


def reload_rag_dependencies() -> dict:
    """
    Attempt to reload RAG dependencies at runtime.

    This allows the MCP server to pick up newly installed packages
    without a full process restart.

    Returns:
        dict with status and message
    """
    global _RAG_IMPORT_ERROR, RAGRetriever, create_rag_retriever

    import site
    import sys

    # Step 1: Refresh user site packages
    try:
        user_site = site.getusersitepackages()
        if isinstance(user_site, str) and user_site not in sys.path:
            sys.path.insert(0, user_site)
    except Exception:
        pass

    # Step 2: Clear any cached import errors
    for mod_name in ["chromadb", "sentence_transformers", "boring.rag"]:
        if mod_name in sys.modules:
            del sys.modules[mod_name]

    # Step 3: Try importing again
    try:
        import chromadb  # noqa: F401
        from sentence_transformers import SentenceTransformer  # noqa: F401

        from boring.rag import RAGRetriever as _RAGRetriever
        from boring.rag import create_rag_retriever as _create_rag_retriever

        RAGRetriever = _RAGRetriever
        create_rag_retriever = _create_rag_retriever
        _RAG_IMPORT_ERROR = None
        _retrievers.clear()  # Clear cached retrievers

        return {
            "status": "SUCCESS",
            "message": "✅ RAG dependencies reloaded successfully! You can now use boring_rag_index and boring_rag_search.",
        }
    except ImportError as e:
        pip_cmd = f"{sys.executable} -m pip install chromadb sentence-transformers"
        _RAG_IMPORT_ERROR = str(e)
        return {
            "status": "ERROR",
            "message": f"❌ Failed to reload RAG dependencies: {e}",
            "fix_command": pip_cmd,
            "hint": f"Run this command first, then try boring_rag_reload again:\n    {pip_cmd}",
        }


def get_retriever(project_root: Path) -> Optional[RAGRetriever]:
    """Get or create RAG retriever for a project. Returns None if RAG is not available."""
    if create_rag_retriever is None:
        return None
    key = str(project_root)
    if key not in _retrievers:
        _retrievers[key] = create_rag_retriever(project_root)
    return _retrievers[key]


def register_rag_tools(mcp, helpers: dict):
    """
    Register RAG tools with the MCP server.

    Args:
        mcp: FastMCP instance
        helpers: Dict with helper functions (get_project_root_or_error, etc.)
    """
    get_project_root_or_error = helpers.get("get_project_root_or_error")

    @mcp.tool(
        description="重新載入 RAG 功能 (Reload RAG). 適合: '刷新一下', 'Refresh RAG', '安裝套件後重新載入'.",
        annotations={"readOnlyHint": False, "idempotentHint": True},
    )
    def boring_rag_reload() -> dict:
        """
        Attempt to reload RAG dependencies at runtime.

        Use this after installing chromadb/sentence-transformers to enable
        RAG features without restarting the MCP server.

        Returns:
            Status and message indicating success or required actions
        """
        return reload_rag_dependencies()

    @mcp.tool(
        description="讓我記住這個專案的所有程式碼 (Index code for search). 適合: '記住這個專案', 'Learn my codebase', '建立索引', 'Index my project'.",
        annotations={"readOnlyHint": False, "idempotentHint": True, "openWorldHint": False},
    )
    def boring_rag_index(
        force: Annotated[
            bool,
            Field(
                description="If True, rebuild index even if it exists. Use when codebase structure changed significantly or index appears corrupted. Default: False (skips if index exists)."
            ),
        ] = False,
        project_path: Annotated[
            str,
            Field(
                description="Optional explicit path to project root. If not provided, automatically detects project root by searching for common markers (pyproject.toml, package.json, etc.) starting from current directory. Example: '.' or '/path/to/project'."
            ),
        ] = None,
    ) -> str:
        """
        Index the codebase for RAG retrieval.

        Creates vector embeddings and dependency graph for semantic code search.
        Run this once per project, or with force=True to rebuild.

        Args:
            force: If True, rebuild index even if it exists
            project_path: Optional explicit path to project root

        Returns:
            Status message with indexing statistics
        """
        project_root, error = get_project_root_or_error(project_path)
        if error:
            return error.get("message")
        retriever = get_retriever(project_root)
        if retriever is None or not retriever.is_available:
            pip_cmd = f"{sys.executable} -m pip install chromadb sentence-transformers"
            return (
                "❌ RAG module not available.\n\n"
                f"[Recommended Fix]\nRun this command: {pip_cmd}\n\n"
                "After installation, run `boring_rag_reload` to apply changes.\n\n"
                "💡 [Instructions for AI]: If you see this error, please ask the user if you should run the install command for them."
            )

        count = retriever.build_index(force=force)
        stats = retriever.get_stats()

        if stats.index_stats:
            idx = stats.index_stats
            return (
                f"✅ RAG Index {'rebuilt' if force else 'ready'}\n\n"
                f"📊 Statistics:\n"
                f"- Files indexed: {idx.total_files}\n"
                f"- Total chunks: {idx.total_chunks}\n"
                f"- Functions: {idx.functions}\n"
                f"- Classes: {idx.classes}\n"
                f"- Methods: {idx.methods}\n"
                f"- Script chunks: {getattr(idx, 'script_chunks', 0)}\n"
                f"- Skipped: {idx.skipped_files}\n\n"
                f"💡 Project root: {project_root}"
            )

        return f"✅ RAG Index ready with {count} chunks for `{project_root}`"

    @mcp.tool(
        description="找程式碼、搜尋功能、查看某個功能怎麼寫的 (Search code). 適合: 'Find code', '找登入那邊的程式碼', 'Show me authentication', '搜尋 XXX 功能'.",
        annotations={"readOnlyHint": True, "openWorldHint": False, "idempotentHint": True},
    )
    def boring_rag_search(
        query: Annotated[
            str,
            Field(
                description="Natural language query describing what code you're looking for. Uses semantic similarity search. Examples: 'authentication error handling', 'database connection pool', 'user login validation'. Be specific for better results."
            ),
        ],
        max_results: Annotated[
            int,
            Field(
                description="Maximum number of search results to return. Range: 1-50. Default: 10. Higher values provide more context but may include less relevant results."
            ),
        ] = 10,
        expand_graph: Annotated[
            bool,
            Field(
                description="If True, includes related code via dependency graph (callers/callees). Provides more comprehensive context. Default: True. Set to False for faster searches with only direct matches."
            ),
        ] = True,
        file_filter: Annotated[
            str,
            Field(
                description="Optional substring to filter results by file path. Only files containing this substring will be included. Examples: 'auth' (matches auth.py, authentication.py), 'src/api' (matches files in src/api/). Case-sensitive."
            ),
        ] = None,
        threshold: Annotated[
            float,
            Field(
                description="Minimum relevance score (0.0 to 1.0) for results. Higher values return only highly relevant matches. Default: 0.0 (all results). Recommended: 0.3-0.5 for quality filtering."
            ),
        ] = 0.0,
        project_path: Annotated[
            str,
            Field(
                description="Optional explicit path to project root. If not provided, automatically detects project root by searching for common markers (pyproject.toml, package.json, etc.) starting from current directory. Example: '.' or '/path/to/project'."
            ),
        ] = None,
    ) -> str:
        """
        Search the codebase using semantic RAG retrieval.

        Combines vector similarity search with dependency graph expansion
        to find the most relevant code for your query.

        Args:
            query: What you're looking for (e.g., "authentication error handling")
            max_results: Maximum number of results (default 10)
            expand_graph: Include related code via dependency graph (default True)
            file_filter: Filter by file path substring (e.g., "auth" or "src/api")
            threshold: Minimum relevance score (0.0 to 1.0)
            project_path: Optional explicit path to project root

        Returns:
            Formatted search results with code snippets
        """
        project_root, error = get_project_root_or_error(project_path)
        if error:
            return error.get("message")
        retriever = get_retriever(project_root)
        if retriever is None or not retriever.is_available:
            pip_cmd = f"{sys.executable} -m pip install chromadb sentence-transformers"
            return (
                "❌ RAG module not available.\n\n"
                f"[Recommended Fix]\nRun this command: {pip_cmd}\n\n"
                "After installation, run `boring_rag_reload` to apply changes.\n\n"
                "💡 [Instructions for AI]: If you see this error, please ask the user if you should run the install command for them."
            )

        # Enhanced index health check with diagnostics
        if retriever.collection:
            chunk_count = retriever.collection.count()
            if chunk_count == 0:
                return (
                    "❌ RAG index is empty.\n\n"
                    "**Solution:** Run `boring_rag_index` first to index your codebase:\n"
                    "```\n"
                    "boring_rag_index(force=True)\n"
                    "```\n\n"
                    "**Tip:** Use `boring_rag_status` to check index health."
                )
        else:
            return (
                "❌ RAG collection not initialized.\n\n"
                "**Solution:** Run `boring_rag_index` to create the index."
            )

        # Normalize file_filter for cross-platform compatibility
        if file_filter:
            file_filter = file_filter.replace("\\", "/")

        results = retriever.retrieve(
            query=query,
            n_results=max_results,
            expand_graph=expand_graph,
            file_filter=file_filter,
            threshold=threshold,
        )

        if not results:
            return (
                f"🔍 No results found for: **{query}**\n\n"
                f"**Suggestions:**\n"
                f"- Try a different query\n"
                f"- Check if code exists in indexed files\n"
                f"- Run `boring_rag_status` to verify index health"
            )

        parts = [f"🔍 Found {len(results)} results for: **{query}**\n"]

        for i, result in enumerate(results, 1):
            chunk = result.chunk
            method = result.retrieval_method.upper()
            score = f"{result.score:.2f}"

            parts.append(
                f"### {i}. [{method}] `{chunk.file_path}` → `{chunk.name}` (score: {score})\n"
                f"Lines {chunk.start_line}-{chunk.end_line} | Type: {chunk.chunk_type}\n"
                f"```python\n{chunk.content[:500]}{'...' if len(chunk.content) > 500 else ''}\n```\n"
            )

        return "\n".join(parts)

    @mcp.tool(
        description="查看索引狀態、檢查程式碼記憶 (Check index status). 適合: 'RAG 狀態', 'Check if indexed', '有沒有記住我的程式碼'.",
        annotations={"readOnlyHint": True, "openWorldHint": False, "idempotentHint": True},
    )
    def boring_rag_status(
        project_path: Annotated[
            str,
            Field(
                description="Optional explicit path to project root. If not provided, automatically detects project root by searching for common markers (pyproject.toml, package.json, etc.) starting from current directory. Example: '.' or '/path/to/project'."
            ),
        ] = None,
    ) -> str:
        """
        Check RAG index health and provide diagnostic information.

        Use this to verify the index is properly built and contains expected data.

        Args:
            project_path: Optional explicit path to project root

        Returns:
            Comprehensive index health report
        """
        project_root, error = get_project_root_or_error(project_path)
        if error:
            return error.get("message")

        report = []  # Initialize report list
        retriever = get_retriever(project_root)
        if retriever is None or not retriever.is_available:
            pip_cmd = f"{sys.executable} -m pip install chromadb sentence-transformers"
            report.append("## ❌ RAG Not Available\n")
            report.append(f"Install dependencies:\n```bash\n{pip_cmd}\n```\n")
            report.append("\n💡 [Instructions for AI]: If you see this, please ask the user if you should run the install command for them.")
            return "\n".join(report)

        report.append("## ✅ ChromaDB Available\n")

        # Check collection status
        if retriever.collection:
            chunk_count = retriever.collection.count()
            report.append(f"**Indexed Chunks:** {chunk_count}\n")

            if chunk_count == 0:
                report.append("\n## ⚠️ Index Empty\n")
                report.append("Run `boring_rag_index(force=True)` to build the index.\n")
            else:
                report.append("\n## ✅ Index Healthy\n")

                # Get detailed stats
                stats = retriever.get_stats()
                if stats.index_stats:
                    idx = stats.index_stats
                    report.append(f"- **Files indexed:** {idx.total_files}\n")
                    report.append(f"- **Functions:** {idx.functions}\n")
                    report.append(f"- **Classes:** {idx.classes}\n")
                    report.append(f"- **Methods:** {idx.methods}\n")
                    report.append(f"- **Skipped:** {idx.skipped_files}\n")

                if stats.graph_stats:
                    graph = stats.graph_stats
                    report.append("\n**Dependency Graph:**\n")
                    report.append(f"- Nodes: {graph.total_nodes}\n")
                    report.append(f"- Edges: {graph.total_edges}\n")
        else:
            report.append("## ❌ Collection Not Initialized\n")
            report.append("Run `boring_rag_index` to create the index.\n")

        # Check persist directory
        if retriever.persist_dir.exists():
            report.append(f"\n**Persist Directory:** `{retriever.persist_dir}`\n")

        return "\n".join(report)

    @mcp.tool(
        description="查看某個函數叫了誰、被誰叫 (Code context). 適合: '這個函數被誰用', 'Who uses this?', '查看依賴關係'.",
        annotations={"readOnlyHint": True, "openWorldHint": False, "idempotentHint": True},
    )
    def boring_rag_context(
        file_path: Annotated[
            str,
            Field(
                description="Path to the file relative to project root. Example: 'src/auth/login.py' or 'app/models/user.py'. Must be a valid file path in the indexed codebase."
            ),
        ],
        function_name: Annotated[
            str,
            Field(
                description="Optional name of the function to get context for. If provided, returns callers and callees for this specific function. Example: 'authenticate_user' or 'process_payment'. Leave empty to get file-level context."
            ),
        ] = None,
        class_name: Annotated[
            str,
            Field(
                description="Optional name of the class if getting class-level context. Use when function_name is a method. Example: 'UserService' or 'PaymentProcessor'. Required when function_name is a class method."
            ),
        ] = None,
        project_path: Annotated[
            str,
            Field(
                description="Optional explicit path to project root. If not provided, automatically detects project root by searching for common markers (pyproject.toml, package.json, etc.) starting from current directory. Example: '.' or '/path/to/project'."
            ),
        ] = None,
    ) -> str:
        """
        Get comprehensive context for modifying a specific code location.

        Returns the target code plus:
        - Callers: Code that calls this (might break if you change it)
        - Callees: Code this depends on (need to understand the interface)
        - Siblings: Other methods in the same class

        Args:
            file_path: Path to the file (relative to project root)
            function_name: Name of the function to get context for
            class_name: Name of the class (if getting class context)
            project_path: Optional explicit path to project root

        Returns:
            Categorized code context for safe modification
        """
        project_root, error = get_project_root_or_error(project_path)
        if error:
            return error.get("message")
        retriever = get_retriever(project_root)
        if retriever is None or not retriever.is_available:
            pip_cmd = f"{sys.executable} -m pip install chromadb sentence-transformers"
            return (
                "❌ RAG module not available.\n\n"
                f"[Recommended Fix]\nRun this command: {pip_cmd}\n\n"
                "After installation, run `boring_rag_reload` to apply changes.\n\n"
                "💡 [Instructions for AI]: If you see this error, please ask the user if you should run the install command for them."
            )

        context = retriever.get_modification_context(
            file_path=file_path, function_name=function_name, class_name=class_name
        )

        parts = [f"📍 Context for `{function_name or class_name}` in `{file_path}`\n"]

        # Target
        if context["target"]:
            chunk = context["target"][0].chunk
            parts.append(f"## 🎯 Target\n```python\n{chunk.content}\n```\n")
        else:
            return f"❌ Could not find `{function_name or class_name}` in `{file_path}`"

        # Callers (might break)
        if context["callers"]:
            parts.append(
                f"## ⚠️ Callers ({len(context['callers'])} - might break if you change the interface)\n"
            )
            for r in context["callers"][:5]:
                c = r.chunk
                parts.append(f"- `{c.file_path}` → `{c.name}` (L{c.start_line})\n")

        # Callees (dependencies)
        if context["callees"]:
            parts.append(
                f"## 📦 Dependencies ({len(context['callees'])} - understand these interfaces)\n"
            )
            for r in context["callees"][:5]:
                c = r.chunk
                sig = c.signature or c.content[:100]
                parts.append(f"- `{c.name}`: `{sig[:80]}...`\n")

        # Siblings
        if context["siblings"]:
            parts.append(f"## 👥 Sibling Methods ({len(context['siblings'])})\n")
            for r in context["siblings"][:5]:
                c = r.chunk
                parts.append(f"- `{c.name}` (L{c.start_line}-{c.end_line})\n")

        return "\n".join(parts)

    @mcp.tool(
        description="展開更深層的依賴關係 (Expand dependencies). 適合: '繼續展開', 'Show more context', '看更深的依賴'.",
        annotations={"readOnlyHint": True, "openWorldHint": False, "idempotentHint": True},
    )
    def boring_rag_expand(
        chunk_id: Annotated[
            str,
            Field(
                description="The chunk ID to expand from, obtained from boring_rag_search results. Format: string identifier. Example: 'chunk_abc123' or 'func:auth:login'. Use this to get deeper dependency context for a specific code location."
            ),
        ],
        depth: Annotated[
            int,
            Field(
                description="Number of dependency layers to expand recursively. Range: 1-5. Default: 2. Higher values provide more comprehensive context but may include less relevant code. Use 1 for immediate dependencies only, 3-5 for deep dependency analysis."
            ),
        ] = 2,
        project_path: Annotated[
            str,
            Field(
                description="Optional explicit path to project root. If not provided, automatically detects project root by searching for common markers (pyproject.toml, package.json, etc.) starting from current directory. Example: '.' or '/path/to/project'."
            ),
        ] = None,
    ) -> str:
        """
        Smart expand: Get deeper dependency context for a specific chunk.

        Use this when 1-layer expansion isn't enough. The AI can request
        deeper traversal on-demand.

        Args:
            chunk_id: The chunk ID to expand from (from search results)
            depth: How many layers to expand (default 2)
            project_path: Optional explicit path to project root

        Returns:
            Additional related code chunks
        """
        project_root, error = get_project_root_or_error(project_path)
        if error:
            return error.get("message")
        retriever = get_retriever(project_root)
        if retriever is None or not retriever.is_available:
            pip_cmd = f"{sys.executable} -m pip install chromadb sentence-transformers"
            return (
                "❌ RAG module not available.\n\n"
                f"[Recommended Fix]\nRun this command: {pip_cmd}\n\n"
                "After installation, run `boring_rag_reload` to apply changes.\n\n"
                "💡 [Instructions for AI]: If you see this error, please ask the user if you should run the install command for them."
            )

        results = retriever.smart_expand(chunk_id, depth=depth)

        if not results:
            return f"🔍 No additional context found for chunk {chunk_id}"

        parts = [f"🔗 Smart Expand: +{len(results)} related chunks (depth={depth})\n"]

        for result in results[:10]:
            chunk = result.chunk
            parts.append(
                f"### `{chunk.file_path}` → `{chunk.name}`\n"
                f"```python\n{chunk.content[:300]}...\n```\n"
            )

        return "\n".join(parts)
