"""Vastian MCP server — allows external AI tools to connect to Vastian.

HTTP at http://localhost:4550/mcp
Auth: Bearer token from VASTIAN_MCP_KEY in .env

Tools: kanban_list, kanban_create, kanban_update, session_list, session_create,
persona_lookup, knowledge_search, agent_delegate, sprint_status, backup_trigger,
send_message.
"""

from __future__ import annotations

__all__ = ["_handle_json_rpc", "_validate_auth", "run_mcp_server"]

import json
import logging
import os
from http.server import HTTPServer
from pathlib import Path
from typing import Any

logger = logging.getLogger(__name__)

# src/vastian/mcp/server.py -> project root = parents[3] (for config paths only)
_PROJECT_ROOT = Path(__file__).resolve().parents[3]
DEFAULT_PORT = 4550


def _state_dir() -> Path:
    """State dir from data root (VASTIAN_DATA_ROOT). Never use repo for kanban/sessions."""
    from vastian.config import get_settings

    return get_settings().state_dir


def _get_mcp_key() -> str:
    """VASTIAN_MCP_KEY from env, or mcp_api_key from config/local/settings.json."""
    key = (os.environ.get("VASTIAN_MCP_KEY") or "").strip()
    if key:
        return key
    try:
        from vastian.config import get_settings
        from vastian.config_store import get_config_value

        dr = get_settings().data_root
        if dr:
            return (get_config_value("mcp_api_key", dr) or "").strip()
    except Exception:
        pass
    return ""


def _validate_auth(auth_header: str | None) -> bool:
    """Validate Bearer token."""
    key = _get_mcp_key()
    if not key:
        return True  # No key configured = open (dev)
    if not auth_header or not auth_header.startswith("Bearer "):
        return False
    token = auth_header[7:].strip()
    return token == key


def _tools_definitions() -> list[dict[str, Any]]:
    """MCP tool definitions."""
    return [
        {
            "name": "kanban_list",
            "description": "List Kanban tasks, optionally filtered by status.",
            "inputSchema": {
                "type": "object",
                "properties": {"status": {"type": "string", "description": "Filter by status"}},
            },
        },
        {
            "name": "kanban_create",
            "description": "Create a new Kanban task.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "title": {"type": "string"},
                    "description": {"type": "string"},
                    "domain": {"type": "string"},
                    "priority": {"type": "string"},
                },
                "required": ["title"],
            },
        },
        {
            "name": "kanban_update",
            "description": "Update a Kanban task status.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "kanban_id": {"type": "string"},
                    "status": {"type": "string"},
                },
                "required": ["kanban_id"],
            },
        },
        {
            "name": "session_list",
            "description": "List recent sessions.",
            "inputSchema": {"type": "object"},
        },
        {
            "name": "session_create",
            "description": "Create a new session.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "topic": {"type": "string"},
                    "agent_ids": {"type": "array", "items": {"type": "string"}},
                },
            },
        },
        {
            "name": "persona_lookup",
            "description": "Get persona archetype data by ID.",
            "inputSchema": {
                "type": "object",
                "properties": {"persona_id": {"type": "string"}},
                "required": ["persona_id"],
            },
        },
        {
            "name": "knowledge_search",
            "description": "Search the knowledge bank.",
            "inputSchema": {
                "type": "object",
                "properties": {"query": {"type": "string"}},
                "required": ["query"],
            },
        },
        {
            "name": "agent_delegate",
            "description": "Delegate a task to a specific agent.",
            "inputSchema": {
                "type": "object",
                "properties": {"agent_id": {"type": "string"}, "task": {"type": "string"}},
                "required": ["agent_id", "task"],
            },
        },
        {
            "name": "sprint_status",
            "description": "Current sprint/cycle burndown status.",
            "inputSchema": {"type": "object"},
        },
        {
            "name": "backup_trigger",
            "description": "Trigger a backup.",
            "inputSchema": {"type": "object"},
        },
        {
            "name": "send_message",
            "description": "Send SMS or email via messaging provider.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "channel": {"type": "string", "enum": ["sms", "email"]},
                    "body": {"type": "string"},
                    "subject": {"type": "string"},
                },
                "required": ["channel", "body"],
            },
        },
    ]


def _execute_tool(name: str, arguments: dict[str, Any] | None) -> dict[str, Any]:
    """Execute an MCP tool and return result."""
    args = arguments or {}

    try:
        if name == "kanban_list":
            from vastian.storage.file_store import FileStore

            store = FileStore(_state_dir() / "kanban.json", id_field="kanban_id")
            tasks = store.load_all()
            status = args.get("status")
            if status:
                tasks = [t for t in tasks if t.get("status") == status]
            return {"content": [{"type": "text", "text": json.dumps(tasks, indent=2)}]}

        if name == "kanban_create":
            import uuid

            from vastian.storage.file_store import FileStore

            store = FileStore(_state_dir() / "kanban.json", id_field="kanban_id")
            task = {
                "kanban_id": str(uuid.uuid4()),
                "title": args.get("title", ""),
                "description": args.get("description", ""),
                "domain": args.get("domain", "general"),
                "status": "suggested",
                "priority": args.get("priority", "normal"),
            }
            store.insert(task)
            return {
                "content": [
                    {
                        "type": "text",
                        "text": json.dumps({"ok": True, "kanban_id": task["kanban_id"]}),
                    }
                ]
            }

        if name == "kanban_update":
            from vastian.storage.file_store import FileStore

            store = FileStore(_state_dir() / "kanban.json", id_field="kanban_id")
            kid = args.get("kanban_id")
            status = args.get("status")
            if not kid:
                return {
                    "content": [
                        {"type": "text", "text": json.dumps({"error": "kanban_id required"})}
                    ],
                    "isError": True,
                }
            rec = store.get(kid)
            if not rec:
                return {
                    "content": [{"type": "text", "text": json.dumps({"error": "task not found"})}],
                    "isError": True,
                }
            if status:
                store.update(kid, {"status": status})
            return {"content": [{"type": "text", "text": json.dumps({"ok": True})}]}

        if name == "session_list":
            sessions_path = _state_dir() / "sessions.json"
            if sessions_path.exists():
                data = json.loads(sessions_path.read_text(encoding="utf-8"))
                items = (
                    data
                    if isinstance(data, list)
                    else data.get("sessions", [])
                    if isinstance(data, dict)
                    else []
                )
                return {"content": [{"type": "text", "text": json.dumps(items[:20], indent=2)}]}
            return {"content": [{"type": "text", "text": "[]"}]}

        if name == "session_create":
            return {
                "content": [
                    {
                        "type": "text",
                        "text": json.dumps(
                            {"message": "Use /council or /meeting in CLI to create sessions"}
                        ),
                    }
                ]
            }

        if name == "persona_lookup":
            from vastian.storage.data_root import get_config_dir

            cfg = get_config_dir(_PROJECT_ROOT)
            pid = args.get("persona_id", "")
            path = cfg / "personas" / "archetypes" / f"persona_{pid}.yaml"
            if not path.exists():
                path = cfg / "personas" / "archetypes" / f"{pid}.yaml"
            if path.exists():
                import yaml

                data = yaml.safe_load(path.read_text(encoding="utf-8"))
                return {"content": [{"type": "text", "text": json.dumps(data or {}, indent=2)}]}
            return {
                "content": [{"type": "text", "text": json.dumps({"error": "persona not found"})}],
                "isError": True,
            }

        if name == "knowledge_search":
            try:
                from vastian.config import get_settings
                from vastian.knowledge.vector import VectorStore

                settings = get_settings()
                store = VectorStore(persist_dir=settings.chroma_persist_dir)
                store.initialize()
                results = store.search(args.get("query", ""), top_k=5)
                return {"content": [{"type": "text", "text": json.dumps(results, indent=2)}]}
            except Exception as e:
                return {
                    "content": [{"type": "text", "text": json.dumps({"error": str(e)})}],
                    "isError": True,
                }

        if name == "agent_delegate":
            try:
                from vastian.config import get_settings
                from vastian.tasks.queue import TaskQueue

                settings = get_settings()
                tq = TaskQueue(
                    settings.sqlite_path.parent / "tasks.db",
                    state_dir=settings.state_dir,
                )
                tq.initialize()
                task = tq.enqueue(
                    task_type="delegation",
                    agent_id=args.get("agent_id", "charles"),
                    description=args.get("task", ""),
                )
                return {
                    "content": [
                        {"type": "text", "text": json.dumps({"ok": True, "task_id": task.task_id})}
                    ]
                }
            except Exception as e:
                return {
                    "content": [{"type": "text", "text": json.dumps({"error": str(e)})}],
                    "isError": True,
                }

        if name == "sprint_status":
            from vastian.storage.file_store import FileStore

            store = FileStore(_state_dir() / "kanban.json", id_field="kanban_id")
            tasks = store.load_all()
            by_status: dict[str, int] = {}
            for t in tasks:
                s = t.get("status", "unknown")
                by_status[s] = by_status.get(s, 0) + 1
            return {"content": [{"type": "text", "text": json.dumps(by_status, indent=2)}]}

        if name == "backup_trigger":
            try:
                from vastian.tasks.backup import create_backup_manager

                mgr = create_backup_manager(_PROJECT_ROOT)
                if mgr:
                    mgr.backup_now()
                    return {
                        "content": [
                            {
                                "type": "text",
                                "text": json.dumps({"ok": True, "message": "Backup triggered"}),
                            }
                        ]
                    }
                return {
                    "content": [
                        {
                            "type": "text",
                            "text": json.dumps({"error": "No backup provider configured"}),
                        }
                    ],
                    "isError": True,
                }
            except Exception as e:
                return {
                    "content": [{"type": "text", "text": json.dumps({"error": str(e)})}],
                    "isError": True,
                }

        if name == "send_message":
            return _handle_send_message_sync(args)

        return {
            "content": [{"type": "text", "text": json.dumps({"error": f"Unknown tool: {name}"})}],
            "isError": True,
        }
    except Exception as e:
        logger.exception("MCP tool %s failed: %s", name, e)
        return {
            "content": [{"type": "text", "text": json.dumps({"error": str(e)})}],
            "isError": True,
        }


def _handle_send_message_sync(args: dict[str, Any]) -> dict[str, Any]:
    """Synchronous send_message for HTTP server context."""
    import asyncio

    channel = args.get("channel", "email")
    body = args.get("body", "")
    subject = args.get("subject", "")

    async def _send() -> dict[str, Any]:
        from vastian.bootstrap import register_all
        from vastian.loader import get

        register_all()
        adapter_cls = get("zo_messaging")
        if not adapter_cls:
            return {"error": "No messaging provider"}
        from vastian.config import get_settings

        key = get_settings().zo_api_key
        if not key:
            return {"error": "ZO_API_KEY not configured"}
        inst = adapter_cls(key)
        if channel == "sms":
            return await inst.send_sms(body)
        return await inst.send_email(subject, body)

    try:
        out = asyncio.run(_send())
        return {"content": [{"type": "text", "text": json.dumps(out)}]}
    except Exception as e:
        return {
            "content": [{"type": "text", "text": json.dumps({"error": str(e)})}],
            "isError": True,
        }


def _handle_json_rpc(body: dict) -> dict | None:
    """Handle JSON-RPC request, return response or None for notifications."""
    meth = body.get("method")
    params = body.get("params", {})
    req_id = body.get("id")

    if meth == "initialize":
        return {
            "jsonrpc": "2.0",
            "id": req_id,
            "result": {
                "protocolVersion": "2024-11-05",
                "capabilities": {"tools": {}},
                "serverInfo": {"name": "vastian-mcp", "version": "0.1.0"},
            },
        }
    if meth == "notifications/initialized":
        return None  # 202 Accepted
    if meth == "tools/list":
        return {
            "jsonrpc": "2.0",
            "id": req_id,
            "result": {"tools": _tools_definitions()},
        }
    if meth == "tools/call":
        name = params.get("name", "")
        args = params.get("arguments") or {}
        if name == "send_message":
            result = _handle_send_message_sync(args)
        else:
            result = _execute_tool(name, args)
        return {
            "jsonrpc": "2.0",
            "id": req_id,
            "result": {
                "content": result.get("content", []),
                "isError": result.get("isError", False),
            },
        }
    return {
        "jsonrpc": "2.0",
        "id": req_id,
        "error": {"code": -32601, "message": f"Method not found: {meth}"},
    }


class MCPHandler:
    """HTTP handler for MCP JSON-RPC over POST."""

    def __init__(self, request, client_address, server) -> None:
        from http.server import BaseHTTPRequestHandler

        BaseHTTPRequestHandler.__init__(self, request, client_address, server)

    def do_OPTIONS(self) -> None:  # noqa: N802
        self.send_response(204)
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
        self.send_header(
            "Access-Control-Allow-Headers", "Content-Type, Authorization, MCP-Protocol-Version"
        )
        self.end_headers()

    def do_POST(self) -> None:  # noqa: N802
        if self.path != "/mcp" and not self.path.endswith("/mcp"):
            self.send_error(404)
            return
        auth = self.headers.get("Authorization")
        if not _validate_auth(auth):
            self.send_response(401)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": "Unauthorized"}).encode())
            return
        try:
            length = int(self.headers.get("Content-Length", 0))
            raw = self.rfile.read(length)
            body = json.loads(raw.decode("utf-8"))
        except Exception as e:
            self.send_response(400)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())
            return
        response = _handle_json_rpc(body)
        if response is None:
            self.send_response(202)
            self.end_headers()
            return
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(response).encode())

    def log_message(self, format: str, *args: Any) -> None:
        logger.debug("MCP %s", args[0] if args else format)


def run_mcp_server(port: int = DEFAULT_PORT, host: str = "127.0.0.1") -> None:
    """Run the MCP HTTP server (blocks)."""
    # Use a simple handler - BaseHTTPRequestHandler subclasses need 3-arg form
    from http.server import BaseHTTPRequestHandler

    class Handler(BaseHTTPRequestHandler):
        def do_OPTIONS(self) -> None:
            self.send_response(204)
            self.send_header("Access-Control-Allow-Origin", "*")
            self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
            self.send_header(
                "Access-Control-Allow-Headers", "Content-Type, Authorization, MCP-Protocol-Version"
            )
            self.end_headers()

        def do_POST(self) -> None:
            if self.path != "/mcp" and not self.path.endswith("/mcp"):
                self.send_error(404)
                return
            auth = self.headers.get("Authorization")
            if not _validate_auth(auth):
                self.send_response(401)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": "Unauthorized"}).encode())
                return
            try:
                length = int(self.headers.get("Content-Length", 0))
                raw = self.rfile.read(length)
                body = json.loads(raw.decode("utf-8"))
            except Exception as e:
                self.send_response(400)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())
                return
            response = _handle_json_rpc(body)
            if response is None:
                self.send_response(202)
                self.end_headers()
                return
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps(response).encode())

        def log_message(self, format: str, *args: Any) -> None:
            logger.debug("MCP %s", args[0] if args else format)

    server = HTTPServer((host, port), Handler)
    logger.info("Vastian MCP server listening on http://%s:%d/mcp", host, port)
    server.serve_forever()
