"""MCP server for jdocmunch-mcp."""

import argparse
import asyncio
import json
import os
import sys
import traceback
from typing import Any, Optional

from mcp.server import Server
from mcp.types import Tool, TextContent, Resource

from .tools.index_local import index_local
from .tools.index_repo import index_repo
from .tools.list_repos import list_repos
from .tools.get_toc import get_toc
from .tools.get_toc_tree import get_toc_tree
from .tools.get_document_outline import get_document_outline
from .tools.search_sections import search_sections
from .tools.get_section import get_section
from .tools.get_sections import get_sections
from .tools.get_section_context import get_section_context
from .tools.delete_index import delete_index


server = Server("jdocmunch-mcp")


@server.list_tools()
async def list_tools() -> list[Tool]:
    """List all available tools."""
    return [
        Tool(
            name="index_local",
            description="Index a local folder containing documentation files (.md, .txt, .rst). Parses by heading hierarchy into sections for efficient retrieval. Set use_embeddings=true to enable semantic search.",
            inputSchema={
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "Path to local folder (absolute or relative, supports ~ for home directory)"
                    },
                    "use_ai_summaries": {
                        "type": "boolean",
                        "description": "Use AI to generate section summaries (requires ANTHROPIC_API_KEY or GOOGLE_API_KEY). When false, uses heading text.",
                        "default": True
                    },
                    "use_embeddings": {
                        "type": "boolean",
                        "description": "Generate semantic embeddings for each section, enabling meaning-based search. Requires GOOGLE_API_KEY (Gemini) or OPENAI_API_KEY. Automatically activates semantic search on query.",
                        "default": False
                    },
                    "extra_ignore_patterns": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "Additional gitignore-style patterns to exclude from indexing"
                    },
                    "follow_symlinks": {
                        "type": "boolean",
                        "description": "Whether to follow symlinks. Default false for security.",
                        "default": False
                    },
                    "name": {
                        "type": "string",
                        "description": "Optional repo identifier override. Use this when two folders share the same name (e.g. both named 'docs'). If omitted, the folder name is used. Example: 'requests-docs', 'flask-docs'."
                    },
                    "incremental": {
                        "type": "boolean",
                        "description": "When true (default), only re-index files that changed since the last index. Set to false to force a full re-index.",
                        "default": True
                    }
                },
                "required": ["path"]
            }
        ),
        Tool(
            name="doc_index_repo",
            description="Index a GitHub repository's documentation. Fetches .md/.txt files, parses sections, and saves to local storage. Set use_embeddings=true to enable semantic search.",
            inputSchema={
                "type": "object",
                "properties": {
                    "url": {
                        "type": "string",
                        "description": "GitHub repository URL or owner/repo string"
                    },
                    "use_ai_summaries": {
                        "type": "boolean",
                        "description": "Use AI to generate section summaries.",
                        "default": True
                    },
                    "use_embeddings": {
                        "type": "boolean",
                        "description": "Generate semantic embeddings for each section, enabling meaning-based search. Requires GOOGLE_API_KEY (Gemini) or OPENAI_API_KEY.",
                        "default": False
                    },
                    "incremental": {
                        "type": "boolean",
                        "description": "When true (default), skip all HTTP fetches if the HEAD commit SHA is unchanged; otherwise only re-index changed files. Set to false to force a full re-index.",
                        "default": True
                    }
                },
                "required": ["url"]
            }
        ),
        Tool(
            name="doc_list_repos",
            description="List all indexed documentation repositories.",
            inputSchema={
                "type": "object",
                "properties": {}
            }
        ),
        Tool(
            name="get_toc",
            description="Get a flat table of contents for all sections in a repo, sorted by document order. Content is excluded — use get_section to retrieve content.",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo": {
                        "type": "string",
                        "description": "Repository identifier (owner/repo or just repo name)"
                    }
                },
                "required": ["repo"]
            }
        ),
        Tool(
            name="get_toc_tree",
            description="Get a nested table of contents tree per document. Shows parent/child heading relationships. Content is excluded.",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo": {
                        "type": "string",
                        "description": "Repository identifier (owner/repo or just repo name)"
                    }
                },
                "required": ["repo"]
            }
        ),
        Tool(
            name="get_document_outline",
            description="Get the section hierarchy for a single document file, without content.",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo": {
                        "type": "string",
                        "description": "Repository identifier"
                    },
                    "doc_path": {
                        "type": "string",
                        "description": "Path to the document within the repository (e.g., 'README.md')"
                    }
                },
                "required": ["repo", "doc_path"]
            }
        ),
        Tool(
            name="search_sections",
            description="Search sections by relevance. Uses semantic (embedding) search when the index was built with use_embeddings=true, otherwise uses weighted keyword scoring. Returns summaries only — use get_section for full content.",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo": {
                        "type": "string",
                        "description": "Repository identifier"
                    },
                    "query": {
                        "type": "string",
                        "description": "Search query"
                    },
                    "doc_path": {
                        "type": "string",
                        "description": "Optional: limit search to a specific document"
                    },
                    "max_results": {
                        "type": "integer",
                        "description": "Maximum number of results to return",
                        "default": 10
                    }
                },
                "required": ["repo", "query"]
            }
        ),
        Tool(
            name="get_section",
            description="Retrieve the full content of a specific section using byte-range reads. Use after identifying section IDs via search_sections or get_toc.",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo": {
                        "type": "string",
                        "description": "Repository identifier"
                    },
                    "section_id": {
                        "type": "string",
                        "description": "Section ID from get_toc, search_sections, or get_document_outline"
                    },
                    "verify": {
                        "type": "boolean",
                        "description": "Verify content hash matches stored hash (detects source drift)",
                        "default": False
                    }
                },
                "required": ["repo", "section_id"]
            }
        ),
        Tool(
            name="get_sections",
            description="Batch content retrieval for multiple sections in one call.",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo": {
                        "type": "string",
                        "description": "Repository identifier"
                    },
                    "section_ids": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "List of section IDs to retrieve"
                    },
                    "verify": {
                        "type": "boolean",
                        "description": "Verify content hashes",
                        "default": False
                    }
                },
                "required": ["repo", "section_ids"]
            }
        ),
        Tool(
            name="get_section_context",
            description="Retrieve a section with its full hierarchy context: ancestor headings (root → parent) for orientation, the target section's content, and immediate child summaries. Prevents 'section too thin' without falling back to whole-file reads.",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo": {
                        "type": "string",
                        "description": "Repository identifier (owner/repo or just repo name)"
                    },
                    "section_id": {
                        "type": "string",
                        "description": "Target section ID from get_toc, search_sections, etc."
                    },
                    "max_tokens": {
                        "type": "integer",
                        "description": "Approximate token budget for the target section's content (bytes/4 estimate). Ancestors and child summaries are always included.",
                        "default": 2000
                    },
                    "include_children": {
                        "type": "boolean",
                        "description": "Include immediate child section summaries (no content reads). Default true.",
                        "default": True
                    }
                },
                "required": ["repo", "section_id"]
            }
        ),
        Tool(
            name="delete_index",
            description="Remove a repo index and its cached raw files.",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo": {
                        "type": "string",
                        "description": "Repository identifier (owner/repo or just repo name)"
                    }
                },
                "required": ["repo"]
            }
        ),
    ]


@server.list_resources()
async def list_resources() -> list[Resource]:
    """Return empty resource list for client compatibility (e.g. Windsurf)."""
    return []


@server.list_prompts()
async def list_prompts() -> list:
    """Return empty prompt list for client compatibility (e.g. Windsurf)."""
    return []


@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    """Handle tool calls."""
    storage_path = os.environ.get("DOC_INDEX_PATH")

    try:
        if name == "index_local":
            result = index_local(
                path=arguments["path"],
                name=arguments.get("name"),
                use_ai_summaries=arguments.get("use_ai_summaries", True),
                use_embeddings=arguments.get("use_embeddings", False),
                storage_path=storage_path,
                extra_ignore_patterns=arguments.get("extra_ignore_patterns"),
                follow_symlinks=arguments.get("follow_symlinks", False),
                incremental=arguments.get("incremental", True),
            )
        elif name in ("doc_index_repo", "index_repo"):  # index_repo kept for backward compat
            result = await index_repo(
                url=arguments["url"],
                use_ai_summaries=arguments.get("use_ai_summaries", True),
                use_embeddings=arguments.get("use_embeddings", False),
                storage_path=storage_path,
                incremental=arguments.get("incremental", True),
            )
        elif name in ("doc_list_repos", "list_repos"):  # list_repos kept for backward compat
            result = list_repos(storage_path=storage_path)
        elif name == "get_toc":
            result = get_toc(
                repo=arguments["repo"],
                storage_path=storage_path,
            )
        elif name == "get_toc_tree":
            result = get_toc_tree(
                repo=arguments["repo"],
                storage_path=storage_path,
            )
        elif name == "get_document_outline":
            result = get_document_outline(
                repo=arguments["repo"],
                doc_path=arguments["doc_path"],
                storage_path=storage_path,
            )
        elif name == "search_sections":
            result = search_sections(
                repo=arguments["repo"],
                query=arguments["query"],
                doc_path=arguments.get("doc_path"),
                max_results=arguments.get("max_results", 10),
                storage_path=storage_path,
            )
        elif name == "get_section":
            result = get_section(
                repo=arguments["repo"],
                section_id=arguments["section_id"],
                verify=arguments.get("verify", False),
                storage_path=storage_path,
            )
        elif name == "get_sections":
            result = get_sections(
                repo=arguments["repo"],
                section_ids=arguments["section_ids"],
                verify=arguments.get("verify", False),
                storage_path=storage_path,
            )
        elif name == "get_section_context":
            result = get_section_context(
                repo=arguments["repo"],
                section_id=arguments["section_id"],
                max_tokens=arguments.get("max_tokens", 2000),
                include_children=arguments.get("include_children", True),
                storage_path=storage_path,
            )
        elif name == "delete_index":
            result = delete_index(
                repo=arguments["repo"],
                storage_path=storage_path,
            )
        else:
            result = {"error": f"Unknown tool: {name}"}

        if isinstance(result, dict):
            result.setdefault("_meta", {})["powered_by"] = "jdocmunch-mcp by jgravelle · https://github.com/jgravelle/jdocmunch-mcp"
        return [TextContent(type="text", text=json.dumps(result, indent=2))]

    except Exception as e:
        print(traceback.format_exc(), file=sys.stderr)
        return [TextContent(type="text", text=json.dumps({"error": str(e)}, indent=2))]


async def run_server():
    """Run the MCP server."""
    from jdocmunch_mcp import __version__
    from mcp.server.stdio import stdio_server
    print(f"jdocmunch-mcp {__version__} by jgravelle · https://github.com/jgravelle/jdocmunch-mcp", file=sys.stderr)

    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            server.create_initialization_options()
        )


def main(argv: Optional[list] = None):
    """Main entry point."""
    from .security import verify_package_integrity
    verify_package_integrity()

    parser = argparse.ArgumentParser(
        prog="jdocmunch-mcp",
        description="Run the jDocMunch MCP stdio server.",
    )
    parser.parse_args(argv)
    asyncio.run(run_server())


if __name__ == "__main__":
    main()
