"""MCP HTTP/SSE adapter.

This module intentionally keeps MCP transport details isolated from the domain
services. The first implementation exposes tool calls over HTTP and provides an
SSE discovery stream. If the Python MCP SDK transport API changes, only this
adapter should need replacement.
"""

import json
from uuid import UUID

from fastapi import APIRouter, Depends
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from sqlalchemy.orm import Session

from gnexus_creds.auth import actor_from_request
from gnexus_creds.db import get_db
from gnexus_creds.errors import AppError
from gnexus_creds.schemas import Scope, SecretCreate, SecretStatus, SecretUpdate
from gnexus_creds.services import (
    Actor,
    create_secret,
    get_secret,
    list_secrets,
    reveal_secret,
    update_secret,
)

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


TOOLS = [
    "search_secrets",
    "get_secret",
    "reveal_secret",
    "create_secret",
    "update_secret",
    "set_secret_status",
    "archive_secret",
]


class ToolCall(BaseModel):
    arguments: dict = {}


def _mcp_actor(actor: Actor) -> Actor:
    if actor.api_token is None or Scope.mcp.value not in actor.api_token.scopes:
        raise AppError("forbidden", "MCP scope is required.", status_code=403)
    actor.channel = "mcp"
    return actor


@router.get("/sse")
async def mcp_sse(actor: Actor = Depends(actor_from_request)) -> StreamingResponse:
    _mcp_actor(actor)

    def stream():
        payload = json.dumps({"type": "tools", "tools": TOOLS})
        yield f"event: ready\ndata: {payload}\n\n"

    return StreamingResponse(stream(), media_type="text/event-stream")


@router.post("/tools/{tool_name}")
async def call_tool(
    tool_name: str,
    call: ToolCall,
    db: Session = Depends(get_db),
    actor: Actor = Depends(actor_from_request),
):
    actor = _mcp_actor(actor)
    args = call.arguments
    if tool_name == "search_secrets":
        items, total = list_secrets(
            db,
            actor,
            q=args.get("q"),
            category=args.get("category"),
            status=SecretStatus(args["status"]) if args.get("status") else None,
            include_archived=False,
            offset=int(args.get("offset", 0)),
            limit=int(args.get("limit", 20)),
            mcp=True,
        )
        return {"items": [item.model_dump(mode="json") for item in items], "total": total}
    if tool_name == "get_secret":
        result = get_secret(db, actor, UUID(args["secret_id"]), mcp=True)
        return result.model_dump(mode="json")
    if tool_name == "reveal_secret":
        result = reveal_secret(db, actor, UUID(args["secret_id"]), mcp=True)
        db.commit()
        return result.model_dump(mode="json")
    if tool_name == "create_secret":
        result = create_secret(db, actor, SecretCreate(**args))
        db.commit()
        return result.model_dump(mode="json")
    if tool_name == "update_secret":
        secret_id = UUID(args.pop("secret_id"))
        get_secret(db, actor, secret_id, mcp=True)
        result = update_secret(db, actor, secret_id, SecretUpdate(**args))
        db.commit()
        return result.model_dump(mode="json")
    if tool_name == "set_secret_status":
        secret_id = UUID(args["secret_id"])
        get_secret(db, actor, secret_id, mcp=True)
        result = update_secret(
            db,
            actor,
            secret_id,
            SecretUpdate(status=SecretStatus(args["status"])),
        )
        db.commit()
        return result.model_dump(mode="json")
    if tool_name == "archive_secret":
        secret_id = UUID(args["secret_id"])
        get_secret(db, actor, secret_id, mcp=True)
        result = update_secret(db, actor, secret_id, SecretUpdate(archived=True))
        db.commit()
        return result.model_dump(mode="json")
    raise AppError("mcp_tool_not_found", "MCP tool not found.", status_code=404)
