"""Per-user MCP credentials (BYOK) — REST for the settings UI.

A server config (``mcp_servers.d/*.json``) may declare a ``user_key`` slot;
only such servers appear here. Users save their own key, which overrides the
default plaintext credential for their tool calls; users without a key fall
back to the default. Keys are never returned — only presence and update time.
"""

from typing import Annotated

import structlog
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field

from navi.api.deps import require_user
from navi.auth import User

log = structlog.get_logger()

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


def _pool():
    from navi.api.deps import get_session_store

    return get_session_store()._get_pool()


def _eligible_configs():
    """Configs that accept a user key and are referenced by at least one profile.

    A server nobody connects to is noise in the settings UI even if its
    config declares a user_key slot.
    """
    from navi.api.deps import get_profile_registry
    from navi.mcp.config import load_mcp_servers

    referenced: set[str] = set()
    for profile in get_profile_registry().all():
        referenced.update(profile.tools.agent.mcp)
        referenced.update(profile.tools.subagent.mcp)

    return {
        name: cfg
        for name, cfg in load_mcp_servers().items()
        if name in referenced and cfg.user_key is not None
    }


class McpKeyItem(BaseModel):
    server_name: str
    transport: str
    key_type: str  # header | env
    key_location: str  # header name or env var name
    prefix: str | None = None
    instructions: str | None = None
    has_key: bool
    updated_at: str | None = None


@router.get("")
async def list_mcp_keys(
    user: Annotated[User, Depends(require_user)],
) -> dict:
    keyed = _eligible_configs()
    if not keyed:
        return {"items": []}

    from navi.mcp.keystore import McpKeyStore

    keys = await McpKeyStore(await _pool()).list_servers(user.id)

    def _item(name: str, cfg) -> dict:
        return McpKeyItem(
            server_name=name,
            transport=cfg.transport,
            key_type="header" if cfg.user_key.header else "env",
            key_location=cfg.user_key.header or cfg.user_key.env,
            prefix=cfg.user_key.prefix,
            instructions=cfg.instructions,
            has_key=name in keys,
            updated_at=keys[name].isoformat() if name in keys else None,
        ).model_dump()

    return {"items": [_item(name, cfg) for name, cfg in sorted(keyed.items())]}


class SaveMcpKeyRequest(BaseModel):
    key: str = Field(min_length=1, max_length=4096)


def _get_config(server_name: str):
    """The config for *server_name*; 404 when missing or key-less."""
    from navi.mcp.config import load_mcp_servers

    cfg = load_mcp_servers().get(server_name)
    if cfg is None or cfg.user_key is None:
        raise HTTPException(status_code=404, detail="Unknown server or it accepts no user key")
    return cfg


@router.put("/{server_name}")
async def save_mcp_key(
    server_name: str,
    payload: SaveMcpKeyRequest,
    user: Annotated[User, Depends(require_user)],
) -> dict:
    _get_config(server_name)

    from navi.mcp.keystore import McpKeyStore, get_key_resolver

    updated_at = await McpKeyStore(await _pool()).set(
        user.id, server_name, payload.key.strip(),
    )
    get_key_resolver().invalidate(user.id, server_name)
    log.info("mcp_key.saved", user_id=user.id, server=server_name)
    return {
        "server_name": server_name,
        "has_key": True,
        "updated_at": updated_at.isoformat(),
    }


@router.delete("/{server_name}", status_code=204)
async def delete_mcp_key(
    server_name: str,
    user: Annotated[User, Depends(require_user)],
) -> None:
    _get_config(server_name)

    from navi.mcp.keystore import McpKeyStore, get_key_resolver

    deleted = await McpKeyStore(await _pool()).delete(user.id, server_name)
    get_key_resolver().invalidate(user.id, server_name)
    if not deleted:
        # Key never existed — still 204, the desired end state is reached.
        return
    log.info("mcp_key.deleted", user_id=user.id, server=server_name)