from __future__ import annotations

from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, Field

from niannian.external_data.capability import CapabilityMeta, load_capabilities, save_capabilities
from niannian.mcp.server.tool_catalog import list_client_tools
from niannian.external_data.schema import PROTOCOL_V2
from niannian.external_data.tool_audit import read_tool_audit
from niannian.mcp.audit import read_mcp_audit

if TYPE_CHECKING:
    from niannian.client import NianNian


class CapabilitiesBody(BaseModel):
    capabilities: list[dict[str, Any]] = Field(default_factory=list)


class CapabilityPatchBody(BaseModel):
    chat_enabled: bool | None = None
    label: str | None = None
    memory_space: str | None = None
    client_allowlist: list[str] | None = None
    functions: list[str] | None = None
    scopes: list[str] | None = None
    auto_learn_default: str | None = None


class ConfigPreviewBody(BaseModel):
    transport: str = "url"
    url: str = ""
    api_url: str = ""
    bearer_token: str = ""
    memory_space: str = "global"


class EnvelopePreviewBody(BaseModel):
    fields: list[str] = Field(default_factory=list)
    defaults: dict[str, Any] = Field(default_factory=dict)


def _build_mcp_json(body: ConfigPreviewBody) -> dict[str, Any]:
    transport = str(body.transport or "url").strip().lower()
    memory_space = str(body.memory_space or "global").strip() or "global"
    if transport == "stdio":
        api_url = str(body.api_url or body.url or "").strip()
        server: dict[str, Any] = {
            "command": "niannian-mcp",
            "env": {
                "NIANNIAN_API": api_url,
                "NIANNIAN_PROJECT": memory_space,
            },
        }
    else:
        url = str(body.url or "").strip()
        server = {"url": url}
        token = str(body.bearer_token or "").strip()
        if token:
            server["headers"] = {"Authorization": f"Bearer {token}"}
    return {"mcpServers": {"niannian": server}}


def _set_nested(target: dict[str, Any], path: str, value: Any) -> None:
    parts = [p for p in path.split(".") if p]
    if not parts:
        return
    node = target
    for part in parts[:-1]:
        child = node.get(part)
        if not isinstance(child, dict):
            child = {}
            node[part] = child
        node = child
    node[parts[-1]] = value


def _build_envelope_preview(fields: list[str], defaults: dict[str, Any]) -> dict[str, Any]:
    selected = [str(f).strip() for f in fields if str(f).strip()]
    envelope: dict[str, Any] = {}
    if selected:
        envelope["protocol"] = PROTOCOL_V2
    for field in selected:
        if field in defaults:
            value = defaults[field]
        elif "." not in field:
            value = defaults.get(field, "")
        else:
            value = defaults.get(field, "")
        if "." in field:
            _set_nested(envelope, field, value)
        else:
            envelope[field] = value
    return envelope


def create_mcp_console_router(nn: NianNian):
    from fastapi import APIRouter, HTTPException

    router = APIRouter(prefix="/v1/mcp", tags=["mcp-console"])

    @router.get("/audit")
    def get_mcp_audit(
        limit: int = 100,
        since: str = "",
        direction: str = "",
        tool: str = "",
        capability_id: str = "",
    ) -> dict[str, Any]:
        entries = read_mcp_audit(
            nn.home,
            nn._config,
            since=since,
            direction=direction,
            tool=tool,
            capability_id=capability_id,
            limit=limit,
        )
        return {"count": len(entries), "entries": entries}

    @router.get("/logs")
    def get_mcp_logs(
        project: str,
        since: str = "",
        until: str = "",
        actor: str = "",
        trace_id: str = "",
    ) -> dict[str, Any]:
        project_val = str(project or "").strip()
        if not project_val:
            raise HTTPException(status_code=422, detail="project required")
        entries = read_tool_audit(
            nn.home,
            project_val,
            since=since,
            until=until,
            actor=actor,
            trace_id=trace_id,
        )
        return {"project": project_val, "count": len(entries), "entries": entries}

    @router.get("/tools")
    def get_mcp_tools() -> dict[str, Any]:
        tools = list_client_tools()
        return {"tools": tools, "count": len(tools)}

    @router.get("/capabilities")
    def get_capabilities() -> dict[str, Any]:
        caps = load_capabilities(nn._config)
        return {"capabilities": caps, "count": len(caps)}

    @router.get("/capabilities/for-chat")
    def get_capabilities_for_chat() -> dict[str, Any]:
        caps = load_capabilities(nn._config)
        enabled = [c for c in caps if CapabilityMeta.from_dict(c).chat_enabled]
        return {"capabilities": enabled, "count": len(enabled)}

    @router.patch("/capabilities/{capability_id}")
    def patch_capability(capability_id: str, body: CapabilityPatchBody) -> dict[str, Any]:
        cap_id = str(capability_id or "").strip()
        if not cap_id:
            raise HTTPException(status_code=422, detail="capability_id required")

        patches = body.model_dump(exclude_none=True)
        if not patches:
            raise HTTPException(status_code=422, detail="no fields to update")

        caps = load_capabilities(nn._config)
        updated_cap: dict[str, Any] | None = None
        new_caps: list[dict[str, Any]] = []
        for cap in caps:
            if str(cap.get("capability_id") or "").strip() == cap_id:
                merged = dict(cap)
                merged.update(patches)
                updated_cap = merged
                new_caps.append(merged)
            else:
                new_caps.append(cap)

        if updated_cap is None:
            raise HTTPException(status_code=404, detail=f"capability not found: {cap_id}")

        try:
            save_capabilities(new_caps, config=nn._config)
        except ValueError as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc
        return {"capability": updated_cap}

    @router.post("/capabilities")
    def post_capabilities(body: CapabilitiesBody) -> dict[str, Any]:
        try:
            caps = save_capabilities(body.capabilities, config=nn._config)
        except ValueError as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc
        return {"capabilities": caps, "count": len(caps)}

    @router.post("/config/preview")
    def preview_mcp_config(body: ConfigPreviewBody) -> dict[str, Any]:
        mcp_json = _build_mcp_json(body)
        return {"mcp_json": mcp_json}

    @router.post("/envelope/preview")
    def preview_envelope(body: EnvelopePreviewBody) -> dict[str, Any]:
        envelope = _build_envelope_preview(body.fields, body.defaults)
        return {"envelope": envelope}

    return router
