"""Full JSON-RPC 2.0 MCP stdio server for Mesh v2.0."""

import json
import sys
from pathlib import Path
from typing import Any

from mesh.core.storage import MeshStorage
from mesh.analysis.builder import AnalysisBuilder
from mesh.mcp.summary import get_architectural_context


class GraphLoader:
    """Async graph loader."""

    def __init__(self, codebase_root: Path):
        self.codebase_root = codebase_root
        self.storage = MeshStorage(codebase_root)
        self._loaded = False

    def load(self) -> bool:
        """Load graphs."""
        if not self.storage.graphs_exist():
            builder = AnalysisBuilder(self.codebase_root)
            builder.run_full_analysis()
            builder.close()
        self._loaded = True
        return True

    @property
    def is_loaded(self) -> bool:
        return self._loaded


class MCPServer:
    """Full JSON-RPC 2.0 MCP stdio server."""

    def __init__(self, codebase_root: Path):
        self.root = codebase_root
        self.loader = GraphLoader(codebase_root)
        self._running = False

    def start(self):
        """Start server - handles JSON-RPC 2.0 stdio communication."""
        self._running = True
        self._handle_rpc_loop()

    def _handle_rpc_loop(self):
        """Main loop reading JSON-RPC requests from stdin."""
        while self._running:
            try:
                line = sys.stdin.readline()
                if not line:
                    break

                request = json.loads(line.strip())
                response = self._handle_request(request)

                if response:
                    print(json.dumps(response), flush=True)

            except json.JSONDecodeError:
                self._send_error(None, -32700, "Parse error")
            except Exception as e:
                self._send_error(None, -32603, f"Internal error: {e}")

    def _handle_request(self, request: dict) -> dict | None:
        """Handle a JSON-RPC request."""
        method = request.get("method")
        request_id = request.get("id")

        if method == "initialize":
            return self._response(
                request_id,
                {
                    "protocolVersion": "2024-11-05",
                    "capabilities": {"tools": {}},
                    "serverInfo": {"name": "mesh-mcp", "version": "2.0.0"},
                },
            )

        if method == "tools/list":
            return self._response(request_id, {"tools": self.get_tools()})

        if method == "tools/call":
            tool_name = request.get("params", {}).get("name")
            tool_args = request.get("params", {}).get("arguments", {})
            result = self.call_tool(tool_name, tool_args)
            return self._response(
                request_id,
                {
                    "content": [
                        {"type": "text", "text": str(result.get("summary", result))}
                    ]
                },
            )

        if method == "shutdown":
            self._running = False
            return self._response(request_id, None)

        if method == "$/cancel":
            return None

        return self._send_error(request_id, -32601, "Method not found")

    def _response(self, id: Any, result: Any) -> dict:
        """Create JSON-RPC response."""
        response = {"jsonrpc": "2.0", "id": id}
        if result is not None:
            response["result"] = result
        return response

    def _send_error(self, id: Any, code: int, message: str) -> dict:
        """Create JSON-RPC error response."""
        return {
            "jsonrpc": "2.0",
            "id": id,
            "error": {"code": code, "message": message},
        }

    def get_tools(self):
        """Get available tools."""
        return [
            {
                "name": "mesh_architecture",
                "description": "Get architectural summary of codebase including modules, dependencies, and structure",
                "inputSchema": {"type": "object", "properties": {}},
            },
            {
                "name": "mesh_check",
                "description": "Check code for architectural violations and anti-patterns",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "code": {"type": "string", "description": "Code to check"},
                        "target_file": {
                            "type": "string",
                            "description": "File path to check",
                        },
                    },
                },
            },
            {
                "name": "mesh_locate",
                "description": "Locate functions, classes, or symbols in the codebase",
                "inputSchema": {
                    "type": "object",
                    "properties": {
                        "symbol": {
                            "type": "string",
                            "description": "Symbol name to locate",
                        },
                        "type": {
                            "type": "string",
                            "description": "Type: function, class, or module",
                        },
                    },
                },
            },
        ]

    def call_tool(self, name: str, args: dict) -> dict:
        """Call a tool by name."""
        if name == "mesh_architecture":
            return self._mesh_architecture()
        elif name == "mesh_check":
            return self._mesh_check(args.get("code", ""), args.get("target_file", ""))
        elif name == "mesh_locate":
            return self._mesh_locate(args.get("symbol", ""), args.get("type", ""))
        return {"error": f"Unknown tool: {name}"}

    def _mesh_architecture(self) -> dict:
        """Get architectural summary."""
        if not self.loader.is_loaded:
            self.loader.load()

        storage = self.loader.storage
        try:
            cg = storage.load_call_graph()
        except Exception as e:
            return {"error": f"No graph available. Run mesh init first: {e}"}

        summary = get_architectural_context(cg, None, None, self.root)
        return {"summary": summary}

    def _mesh_check(self, code: str, target_file: str) -> dict:
        """Check code for violations."""
        violations = []
        warnings = []

        if not self.loader.is_loaded:
            self.loader.load()

        storage = self.loader.storage

        try:
            cg = storage.load_call_graph()
            # Check for function name collisions
            import re

            func_names = re.findall(r"def (\w+)\(", code)
            existing_names = {n.get("name", "") for n in cg.nodes()}

            for name in func_names:
                if name in existing_names:
                    violations.append(
                        {
                            "type": "duplicate",
                            "message": f"Function {name}() already exists",
                        }
                    )
        except Exception as e:
            warnings.append(
                {"type": "analysis", "message": f"Could not run analysis: {e}"}
            )

        return {
            "is_clean": len(violations) == 0,
            "violations": violations,
            "warnings": warnings,
        }

    def _mesh_locate(self, symbol: str, sym_type: str) -> dict:
        """Locate symbols in the codebase."""
        if not self.loader.is_loaded:
            self.loader.load()

        storage = self.loader.storage
        results = []

        try:
            cg = storage.load_call_graph()
            for node_data in cg.nodes():
                name = node_data.get("name", "")
                if symbol.lower() in name.lower():
                    node_type = node_data.get("kind", "function")
                    if sym_type and sym_type != node_type:
                        continue
                    results.append(
                        {
                            "name": name,
                            "file": node_data.get("file_path", ""),
                            "line": node_data.get("line_start", 0),
                            "type": node_type,
                        }
                    )
        except Exception as e:
            return {"error": f"Could not locate symbol: {e}"}

        return {"symbol": symbol, "results": results[:10]}


def create_server(codebase_root: Path) -> MCPServer:
    """Create MCP server."""
    return MCPServer(codebase_root)


def main():
    """Main entry point for MCP server."""
    root = Path.cwd()
    server = create_server(root)
    server.start()


if __name__ == "__main__":
    main()
