"""Console endpoint catalog for OpenAPI spec generation."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from pydantic import BaseModel

from turnstone.api.console_schemas import (
    AdminMemoryInfo,
    AssignRoleRequest,
    AuditEventInfo,
    AvailableModelInfo,
    BulkSetNodeMetadataRequest,
    ChannelUserInfo,
    ClusterNodesResponse,
    ClusterOverviewResponse,
    ClusterSnapshotResponse,
    ClusterWorkstreamsResponse,
    ConsoleCreateWsRequest,
    ConsoleCreateWsResponse,
    ConsoleHealthResponse,
    CreateChannelUserRequest,
    CreateMcpServerRequest,
    CreateModelDefinitionRequest,
    CreateRoleRequest,
    CreateSkillRequest,
    CreateSkillResourceRequest,
    CreateToolPolicyRequest,
    DetectModelRequest,
    DetectModelResponse,
    ImportMcpConfigRequest,
    ImportMcpConfigResponse,
    KnownModelsResponse,
    ListAdminMemoriesResponse,
    ListAuditEventsResponse,
    ListAvailableModelsResponse,
    ListChannelUsersResponse,
    ListMcpServersResponse,
    ListModelDefinitionsResponse,
    ListOrgsResponse,
    ListOutputAssessmentsResponse,
    ListRolesResponse,
    ListSettingSchemaResponse,
    ListSettingsResponse,
    ListSkillResourcesResponse,
    ListSkillsResponse,
    ListSkillVersionsResponse,
    ListToolPoliciesResponse,
    ListUserRolesResponse,
    ListVerdictsResponse,
    McpReloadResponse,
    McpServerDetail,
    ModelCapabilitiesResponse,
    ModelDefinitionInfo,
    ModelReloadResponse,
    NodeDetailResponse,
    NodeMetadataResponse,
    OrgInfo,
    OutputAssessmentInfo,
    RegistryInstallRequest,
    RegistrySearchResponse,
    RoleInfo,
    RouteCreateResponse,
    RouteResponse,
    SetNodeMetadataValueRequest,
    SettingInfo,
    SettingSchemaInfo,
    SkillDiscoverResponse,
    SkillInfo,
    SkillInstallRequest,
    SkillInstallResponse,
    SkillResourceInfo,
    SkillVersionInfo,
    ToolPolicyInfo,
    UpdateMcpServerRequest,
    UpdateModelDefinitionRequest,
    UpdateOrgRequest,
    UpdateRoleRequest,
    UpdateSettingRequest,
    UpdateSkillRequest,
    UpdateToolPolicyRequest,
    UsageBreakdownItem,
    UsageResponse,
    UserRoleInfo,
    VerdictInfo,
)
from turnstone.api.openapi import EndpointSpec, QueryParam, build_openapi
from turnstone.api.schemas import (
    AuthLoginRequest,
    AuthLoginResponse,
    AuthSetupRequest,
    AuthSetupResponse,
    AuthStatusResponse,
    AuthWhoamiResponse,
    CreateScheduleRequest,
    CreateTokenRequest,
    CreateTokenResponse,
    CreateUserRequest,
    DeleteSettingResponse,
    ErrorResponse,
    ListScheduleRunsResponse,
    ListSchedulesResponse,
    ListTokensResponse,
    ListUsersResponse,
    ScheduleInfo,
    StatusResponse,
    UpdateScheduleRequest,
    UserInfo,
)
from turnstone.api.server_schemas import (
    ListSkillSummaryResponse,
    SkillSummary,
)

CONSOLE_ENDPOINTS: list[EndpointSpec] = [
    # --- Cluster ---
    EndpointSpec(
        "/v1/api/cluster/overview",
        "GET",
        "Cluster state summary",
        response_model=ClusterOverviewResponse,
        tags=["Cluster"],
    ),
    EndpointSpec(
        "/v1/api/cluster/nodes",
        "GET",
        "Paginated node list",
        response_model=ClusterNodesResponse,
        query_params=[
            QueryParam(
                "sort", "Sort field", default="activity", enum=["activity", "tokens", "name"]
            ),
            QueryParam("limit", "Page size", schema_type="integer", default=100),
            QueryParam("offset", "Pagination offset", schema_type="integer", default=0),
        ],
        tags=["Cluster"],
    ),
    EndpointSpec(
        "/v1/api/cluster/workstreams",
        "GET",
        "Filtered workstream list",
        response_model=ClusterWorkstreamsResponse,
        query_params=[
            QueryParam(
                "state",
                "Filter by state",
                enum=["running", "thinking", "attention", "idle", "error"],
            ),
            QueryParam("node", "Filter by node_id"),
            QueryParam("search", "Search in name/title/node"),
            QueryParam("sort", "Sort field", default="state", enum=["state", "tokens", "name"]),
            QueryParam("page", "Page number", schema_type="integer", default=1),
            QueryParam("per_page", "Items per page (max 200)", schema_type="integer", default=50),
        ],
        tags=["Cluster"],
    ),
    EndpointSpec(
        "/v1/api/cluster/node/{node_id}",
        "GET",
        "Single node detail",
        response_model=NodeDetailResponse,
        error_codes=[404],
        tags=["Cluster"],
    ),
    EndpointSpec(
        "/v1/api/cluster/workstreams/new",
        "POST",
        "Create workstream via HTTP dispatch",
        request_model=ConsoleCreateWsRequest,
        response_model=ConsoleCreateWsResponse,
        error_codes=[400, 404, 503],
        tags=["Cluster"],
    ),
    EndpointSpec(
        "/v1/api/cluster/snapshot",
        "GET",
        "Full cluster state snapshot",
        description="Returns the complete cluster state: all nodes with their workstreams "
        "and overview aggregates. Used for initial load and reconnection.",
        response_model=ClusterSnapshotResponse,
        tags=["Cluster"],
    ),
    # --- Streaming ---
    EndpointSpec(
        "/v1/api/cluster/events",
        "GET",
        "Cluster SSE event stream",
        description="Server-Sent Events stream for real-time cluster updates. "
        "First event is a 'snapshot' with full cluster state, followed by "
        "node_joined, node_lost, cluster_state, ws_created, ws_closed, ws_rename events.",
        tags=["Streaming"],
    ),
    # --- Auth ---
    EndpointSpec(
        "/v1/api/auth/login",
        "POST",
        "Authenticate with a token",
        request_model=AuthLoginRequest,
        response_model=AuthLoginResponse,
        error_codes=[401],
        tags=["Auth"],
    ),
    EndpointSpec(
        "/v1/api/auth/setup",
        "POST",
        "Create first admin user",
        request_model=AuthSetupRequest,
        response_model=AuthSetupResponse,
        error_codes=[400, 409, 503],
        tags=["Auth"],
    ),
    EndpointSpec(
        "/v1/api/auth/status",
        "GET",
        "Return auth state",
        response_model=AuthStatusResponse,
        tags=["Auth"],
    ),
    EndpointSpec(
        "/v1/api/auth/logout",
        "POST",
        "Clear auth cookie",
        response_model=StatusResponse,
        tags=["Auth"],
    ),
    EndpointSpec(
        "/v1/api/auth/oidc/authorize",
        "GET",
        "Redirect to OIDC provider for SSO login",
        response_code=302,
        error_codes=[404, 503],
        tags=["Auth"],
    ),
    EndpointSpec(
        "/v1/api/auth/oidc/callback",
        "GET",
        "OIDC callback — validates code, provisions user, sets JWT cookie, redirects to app",
        response_code=302,
        tags=["Auth"],
    ),
    EndpointSpec(
        "/v1/api/auth/whoami",
        "GET",
        "Return authenticated user info and permissions",
        response_model=AuthWhoamiResponse,
        error_codes=[401],
        tags=["Auth"],
    ),
    # --- Admin ---
    EndpointSpec(
        "/v1/api/admin/users",
        "GET",
        "List all users",
        response_model=ListUsersResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/users",
        "POST",
        "Create a user",
        request_model=CreateUserRequest,
        response_model=UserInfo,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/users/{user_id}",
        "DELETE",
        "Delete a user and their tokens",
        response_model=StatusResponse,
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/users/{user_id}/tokens",
        "GET",
        "List tokens for a user",
        response_model=ListTokensResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/users/{user_id}/tokens",
        "POST",
        "Create an API token (raw token shown once)",
        request_model=CreateTokenRequest,
        response_model=CreateTokenResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/tokens/{token_id}",
        "DELETE",
        "Revoke an API token",
        response_model=StatusResponse,
        error_codes=[404],
        tags=["Admin"],
    ),
    # --- Channels ---
    EndpointSpec(
        "/v1/api/admin/users/{user_id}/channels",
        "GET",
        "List channel links for a user",
        response_model=ListChannelUsersResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/users/{user_id}/channels",
        "POST",
        "Link a channel account to a user",
        request_model=CreateChannelUserRequest,
        response_model=ChannelUserInfo,
        error_codes=[400, 404, 409],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/channels/{channel_type}/{channel_user_id}",
        "DELETE",
        "Unlink a channel account",
        response_model=StatusResponse,
        error_codes=[404],
        tags=["Admin"],
    ),
    # --- OIDC Identities ---
    EndpointSpec(
        "/v1/api/admin/users/{user_id}/oidc-identities",
        "GET",
        "List OIDC identities linked to a user",
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/oidc-identities",
        "DELETE",
        "Unlink an OIDC identity (issuer + subject as query params)",
        error_codes=[400, 404],
        tags=["Admin"],
    ),
    # --- Schedules ---
    EndpointSpec(
        "/v1/api/admin/schedules",
        "GET",
        "List all scheduled tasks",
        response_model=ListSchedulesResponse,
        tags=["Schedules"],
    ),
    EndpointSpec(
        "/v1/api/admin/schedules",
        "POST",
        "Create a scheduled task",
        request_model=CreateScheduleRequest,
        response_model=ScheduleInfo,
        error_codes=[400],
        tags=["Schedules"],
    ),
    EndpointSpec(
        "/v1/api/admin/schedules/{task_id}",
        "GET",
        "Get a scheduled task",
        response_model=ScheduleInfo,
        error_codes=[404],
        tags=["Schedules"],
    ),
    EndpointSpec(
        "/v1/api/admin/schedules/{task_id}",
        "PUT",
        "Update a scheduled task",
        request_model=UpdateScheduleRequest,
        response_model=ScheduleInfo,
        error_codes=[400, 404],
        tags=["Schedules"],
    ),
    EndpointSpec(
        "/v1/api/admin/schedules/{task_id}",
        "DELETE",
        "Delete a scheduled task",
        response_model=StatusResponse,
        error_codes=[404],
        tags=["Schedules"],
    ),
    EndpointSpec(
        "/v1/api/admin/schedules/{task_id}/runs",
        "GET",
        "List run history for a scheduled task",
        response_model=ListScheduleRunsResponse,
        query_params=[
            QueryParam(
                "limit", "Max results (default 50, max 200)", schema_type="integer", default=50
            ),
        ],
        error_codes=[404],
        tags=["Schedules"],
    ),
    # --- Governance: Roles ---
    EndpointSpec(
        "/v1/api/admin/roles",
        "GET",
        "List all roles",
        response_model=ListRolesResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/roles",
        "POST",
        "Create a custom role",
        request_model=CreateRoleRequest,
        response_model=RoleInfo,
        error_codes=[400],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/roles/{role_id}",
        "PUT",
        "Update a role",
        request_model=UpdateRoleRequest,
        response_model=RoleInfo,
        error_codes=[400, 404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/roles/{role_id}",
        "DELETE",
        "Delete a custom role",
        response_model=StatusResponse,
        error_codes=[400, 404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/users/{user_id}/roles",
        "GET",
        "List roles assigned to a user",
        response_model=ListUserRolesResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/users/{user_id}/roles",
        "POST",
        "Assign a role to a user",
        request_model=AssignRoleRequest,
        response_model=StatusResponse,
        error_codes=[400, 404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/users/{user_id}/roles/{role_id}",
        "DELETE",
        "Unassign a role from a user",
        response_model=StatusResponse,
        error_codes=[404],
        tags=["Admin"],
    ),
    # --- Governance: Orgs ---
    EndpointSpec(
        "/v1/api/admin/orgs",
        "GET",
        "List organizations",
        response_model=ListOrgsResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/orgs/{org_id}",
        "GET",
        "Get organization details",
        response_model=OrgInfo,
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/orgs/{org_id}",
        "PUT",
        "Update organization settings",
        request_model=UpdateOrgRequest,
        response_model=OrgInfo,
        error_codes=[404],
        tags=["Admin"],
    ),
    # --- Governance: Tool Policies ---
    EndpointSpec(
        "/v1/api/admin/policies",
        "GET",
        "List tool policies",
        response_model=ListToolPoliciesResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/policies",
        "POST",
        "Create a tool policy",
        request_model=CreateToolPolicyRequest,
        response_model=ToolPolicyInfo,
        error_codes=[400],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/policies/{policy_id}",
        "PUT",
        "Update a tool policy",
        request_model=UpdateToolPolicyRequest,
        response_model=ToolPolicyInfo,
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/policies/{policy_id}",
        "DELETE",
        "Delete a tool policy",
        response_model=StatusResponse,
        error_codes=[404],
        tags=["Admin"],
    ),
    # --- Governance: Skill Discovery ---
    EndpointSpec(
        "/v1/api/admin/skills/discover",
        "GET",
        "Search external skill registries for available skills",
        response_model=SkillDiscoverResponse,
        query_params=[
            QueryParam("q", "Search query"),
            QueryParam("limit", "Max results (default 20, max 100)", schema_type="integer"),
        ],
        error_codes=[502],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/skills/install",
        "POST",
        "Install skill(s) from an external source",
        request_model=SkillInstallRequest,
        response_model=SkillInstallResponse,
        error_codes=[400, 404, 409, 502],
        tags=["Admin"],
    ),
    # --- Governance: Skills ---
    EndpointSpec(
        "/v1/api/admin/skills",
        "GET",
        "List skills",
        response_model=ListSkillsResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/skills",
        "POST",
        "Create a skill",
        request_model=CreateSkillRequest,
        response_model=SkillInfo,
        error_codes=[400],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/skills/{skill_id}",
        "GET",
        "Get a skill by ID",
        response_model=SkillInfo,
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/skills/{skill_id}",
        "PUT",
        "Update a skill",
        request_model=UpdateSkillRequest,
        response_model=SkillInfo,
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/skills/{skill_id}",
        "DELETE",
        "Delete a skill",
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/skills/{skill_id}/versions",
        "GET",
        "List version history for a skill",
        response_model=ListSkillVersionsResponse,
        tags=["Admin"],
    ),
    # --- Models ---
    EndpointSpec(
        "/v1/api/models",
        "GET",
        "List enabled model aliases for workstream creation",
        response_model=ListAvailableModelsResponse,
        tags=["Models"],
    ),
    # --- Skills ---
    EndpointSpec(
        "/v1/api/skills",
        "GET",
        "List available skills (summary)",
        response_model=ListSkillSummaryResponse,
        tags=["Skills"],
    ),
    # --- Governance: Usage & Audit ---
    EndpointSpec(
        "/v1/api/admin/usage",
        "GET",
        "Aggregated usage data",
        response_model=UsageResponse,
        query_params=[
            QueryParam("since", "Start timestamp (ISO8601, defaults to last 7 days)"),
            QueryParam("until", "End timestamp (ISO8601)"),
            QueryParam("user_id", "Filter by user"),
            QueryParam("model", "Filter by model"),
            QueryParam(
                "group_by",
                "Group results",
                enum=["day", "hour", "model", "user"],
            ),
        ],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/audit",
        "GET",
        "Paginated audit events",
        response_model=ListAuditEventsResponse,
        query_params=[
            QueryParam("action", "Filter by action type"),
            QueryParam("user_id", "Filter by user"),
            QueryParam("since", "Start timestamp (ISO8601)"),
            QueryParam("until", "End timestamp (ISO8601)"),
            QueryParam("limit", "Page size", schema_type="integer", default=50),
            QueryParam("offset", "Pagination offset", schema_type="integer", default=0),
        ],
        tags=["Admin"],
    ),
    # --- Governance: Intent Verdicts ---
    EndpointSpec(
        "/v1/api/admin/verdicts",
        "GET",
        "Paginated intent verdicts",
        response_model=ListVerdictsResponse,
        query_params=[
            QueryParam("ws_id", "Filter by workstream"),
            QueryParam("since", "Start timestamp (ISO8601)"),
            QueryParam("until", "End timestamp (ISO8601)"),
            QueryParam(
                "risk_level",
                "Filter by risk level",
                enum=["low", "medium", "high", "critical"],
            ),
            QueryParam("limit", "Page size (max 500)", schema_type="integer", default=100),
            QueryParam("offset", "Pagination offset", schema_type="integer", default=0),
        ],
        tags=["Admin"],
    ),
    # --- Admin: Output Guard ---
    EndpointSpec(
        "/v1/api/admin/output-assessments",
        "GET",
        "Paginated output guard assessments",
        response_model=ListOutputAssessmentsResponse,
        query_params=[
            QueryParam("ws_id", "Filter by workstream"),
            QueryParam("risk_level", "Filter by risk level", enum=["low", "medium", "high"]),
            QueryParam("since", "Start timestamp (ISO8601)"),
            QueryParam("until", "End timestamp (ISO8601)"),
            QueryParam("limit", "Page size (max 500)", schema_type="integer", default=100),
            QueryParam("offset", "Pagination offset", schema_type="integer", default=0),
        ],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/skills/{skill_id}/rescan",
        "POST",
        "Re-scan a skill for security signals",
        tags=["Admin"],
    ),
    # --- Governance: Skill Resources ---
    EndpointSpec(
        "/v1/api/admin/skills/{skill_id}/resources",
        "GET",
        "List resource files for a skill",
        response_model=ListSkillResourcesResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/skills/{skill_id}/resources",
        "POST",
        "Upload a resource file to a skill",
        request_model=CreateSkillResourceRequest,
        response_model=SkillResourceInfo,
        response_code=201,
        error_codes=[400, 404, 409],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/skills/{skill_id}/resources/{path}",
        "GET",
        "Get a single skill resource by path",
        response_model=SkillResourceInfo,
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/skills/{skill_id}/resources/{path}",
        "DELETE",
        "Delete a skill resource by path",
        error_codes=[404],
        tags=["Admin"],
    ),
    # --- Admin: Memories ---
    EndpointSpec(
        "/v1/api/admin/memories",
        "GET",
        "List structured memories",
        response_model=ListAdminMemoriesResponse,
        query_params=[
            QueryParam("type", "Filter by memory type"),
            QueryParam("scope", "Filter by scope"),
            QueryParam("scope_id", "Filter by scope identifier"),
            QueryParam("limit", "Page size (max 200)", schema_type="integer", default=100),
        ],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/memories/search",
        "GET",
        "Search memories by query",
        response_model=ListAdminMemoriesResponse,
        query_params=[
            QueryParam("q", "Search query", required=True),
            QueryParam("type", "Filter by memory type"),
            QueryParam("scope", "Filter by scope"),
            QueryParam("scope_id", "Filter by scope identifier"),
            QueryParam("limit", "Max results", schema_type="integer", default=20),
        ],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/memories/{memory_id}",
        "GET",
        "Get a single memory by ID",
        response_model=AdminMemoryInfo,
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/memories/{memory_id}",
        "DELETE",
        "Delete a memory by ID",
        response_model=StatusResponse,
        error_codes=[404],
        tags=["Admin"],
    ),
    # --- Admin: System Settings ---
    EndpointSpec(
        "/v1/api/admin/settings",
        "GET",
        "List all settings with effective values",
        response_model=ListSettingsResponse,
        query_params=[
            QueryParam("reveal", "Show secret values in plaintext", schema_type="boolean"),
        ],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/settings/schema",
        "GET",
        "Return the full settings registry schema",
        response_model=ListSettingSchemaResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/settings/{key}",
        "PUT",
        "Set a configuration setting value",
        request_model=UpdateSettingRequest,
        response_model=SettingInfo,
        error_codes=[400],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/settings/{key}",
        "DELETE",
        "Reset a setting to its default value",
        response_model=DeleteSettingResponse,
        query_params=[
            QueryParam("node_id", "Node ID for node-scoped settings"),
        ],
        error_codes=[400, 404],
        tags=["Admin"],
    ),
    # --- Admin: MCP Registry ---
    EndpointSpec(
        "/v1/api/admin/mcp-registry/search",
        "GET",
        "Search the MCP Registry for available servers",
        response_model=RegistrySearchResponse,
        query_params=[
            QueryParam("search", "Search query"),
            QueryParam("limit", "Max results (default 20, max 100)", schema_type="integer"),
            QueryParam("cursor", "Pagination cursor for next page"),
        ],
        error_codes=[502],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/mcp-registry/install",
        "POST",
        "Install an MCP server from the registry",
        request_model=RegistryInstallRequest,
        response_model=McpServerDetail,
        error_codes=[400, 404, 409, 502],
        tags=["Admin"],
    ),
    # --- Admin: MCP Servers ---
    EndpointSpec(
        "/v1/api/admin/mcp-servers",
        "GET",
        "List MCP server definitions with live status",
        response_model=ListMcpServersResponse,
        query_params=[
            QueryParam("reveal", "Show secret env/header values", schema_type="boolean"),
        ],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/mcp-servers",
        "POST",
        "Create an MCP server definition",
        request_model=CreateMcpServerRequest,
        response_model=McpServerDetail,
        error_codes=[400, 409],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/mcp-servers/{server_id}",
        "GET",
        "Get a single MCP server with status",
        response_model=McpServerDetail,
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/mcp-servers/{server_id}",
        "PUT",
        "Update an MCP server definition",
        request_model=UpdateMcpServerRequest,
        response_model=McpServerDetail,
        error_codes=[400, 404, 409],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/mcp-servers/{server_id}",
        "DELETE",
        "Delete an MCP server definition",
        response_model=StatusResponse,
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/mcp-servers/reload",
        "POST",
        "Tell all nodes to re-read MCP server config from DB and reconcile",
        response_model=McpReloadResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/mcp-servers/import",
        "POST",
        "Import MCP servers from a JSON config file",
        request_model=ImportMcpConfigRequest,
        response_model=ImportMcpConfigResponse,
        error_codes=[400],
        tags=["Admin"],
    ),
    # --- Admin: Model Definitions ---
    EndpointSpec(
        "/v1/api/admin/model-definitions",
        "GET",
        "List model definitions with live status from cluster nodes",
        response_model=ListModelDefinitionsResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/model-definitions",
        "POST",
        "Create a model definition",
        request_model=CreateModelDefinitionRequest,
        response_model=ModelDefinitionInfo,
        error_codes=[400, 409],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/model-definitions/reload",
        "POST",
        "Tell all nodes to re-read model definitions from DB and rebuild registry",
        response_model=ModelReloadResponse,
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/model-definitions/{definition_id}",
        "GET",
        "Get a single model definition",
        response_model=ModelDefinitionInfo,
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/model-definitions/{definition_id}",
        "PUT",
        "Update a model definition",
        request_model=UpdateModelDefinitionRequest,
        response_model=ModelDefinitionInfo,
        error_codes=[400, 404, 409],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/model-definitions/{definition_id}",
        "DELETE",
        "Delete a model definition",
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/model-definitions/detect",
        "POST",
        "Probe a model endpoint: verify reachability, list models, detect context window and server type",
        request_model=DetectModelRequest,
        response_model=DetectModelResponse,
        error_codes=[400],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/model-capabilities",
        "GET",
        "Look up static capabilities for a known model",
        response_model=ModelCapabilitiesResponse,
        query_params=[
            QueryParam(name="provider", description="Provider name", required=True),
            QueryParam(name="model", description="Model ID to look up", required=True),
        ],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/model-capabilities/known",
        "GET",
        "List known model name prefixes for a provider",
        response_model=KnownModelsResponse,
        query_params=[
            QueryParam(name="provider", description="Provider name", required=True),
        ],
        tags=["Admin"],
    ),
    # --- Admin: Prompt Policies ---
    EndpointSpec(
        "/v1/api/admin/prompt-policies",
        "GET",
        "List all prompt policies for system message composition",
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/prompt-policies",
        "POST",
        "Create a prompt policy",
        error_codes=[400],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/prompt-policies/{policy_id}",
        "GET",
        "Get a single prompt policy",
        error_codes=[404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/prompt-policies/{policy_id}",
        "PUT",
        "Update a prompt policy",
        error_codes=[400, 404],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/prompt-policies/{policy_id}",
        "DELETE",
        "Delete a prompt policy",
        error_codes=[404],
        tags=["Admin"],
    ),
    # --- Admin: Node metadata ---
    EndpointSpec(
        "/v1/api/admin/node-metadata",
        "GET",
        "Get metadata for all nodes (bulk)",
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/nodes/{node_id}/metadata",
        "GET",
        "Get all metadata for a node",
        response_model=NodeMetadataResponse,
        error_codes=[400],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/nodes/{node_id}/metadata",
        "PUT",
        "Bulk set user metadata for a node",
        request_model=BulkSetNodeMetadataRequest,
        error_codes=[400],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/nodes/{node_id}/metadata/{key}",
        "PUT",
        "Set a single metadata key for a node",
        request_model=SetNodeMetadataValueRequest,
        error_codes=[400],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/nodes/{node_id}/metadata/{key}",
        "DELETE",
        "Delete a single metadata key for a node",
        error_codes=[400, 404],
        tags=["Admin"],
    ),
    # --- Admin: TLS / ACME ---
    EndpointSpec(
        "/v1/api/admin/tls/ca",
        "GET",
        "CA status: initialization state, CN, cert count, cert inventory",
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/tls/ca.pem",
        "GET",
        "Download CA root certificate (PEM format)",
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/tls/certs",
        "GET",
        "List all issued TLS certificates",
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/tls/certs/{domain}/renew",
        "POST",
        "Force-renew a certificate by domain",
        error_codes=[404, 500],
        tags=["Admin"],
    ),
    EndpointSpec(
        "/v1/api/admin/tls/certs/{domain}",
        "DELETE",
        "Delete a certificate by domain",
        error_codes=[404],
        tags=["Admin"],
    ),
    # --- Routing ---
    EndpointSpec(
        "/v1/api/route/workstreams/new",
        "POST",
        "Create workstream via hash-ring routing proxy",
        response_model=RouteCreateResponse,
        error_codes=[400, 503],
        tags=["Routing"],
    ),
    EndpointSpec(
        "/v1/api/route/send",
        "POST",
        "Proxy send to routed node",
        error_codes=[503],
        tags=["Routing"],
    ),
    EndpointSpec(
        "/v1/api/route/approve",
        "POST",
        "Proxy approve to routed node",
        error_codes=[503],
        tags=["Routing"],
    ),
    EndpointSpec(
        "/v1/api/route/cancel",
        "POST",
        "Proxy cancel to routed node",
        error_codes=[503],
        tags=["Routing"],
    ),
    EndpointSpec(
        "/v1/api/route/command",
        "POST",
        "Proxy command to routed node",
        error_codes=[503],
        tags=["Routing"],
    ),
    EndpointSpec(
        "/v1/api/route/workstreams/close",
        "POST",
        "Proxy workstream close to routed node",
        error_codes=[503],
        tags=["Routing"],
    ),
    EndpointSpec(
        "/v1/api/route",
        "GET",
        "Look up which node owns a workstream",
        response_model=RouteResponse,
        query_params=[
            QueryParam("ws_id", "Workstream ID to look up", required=True),
        ],
        error_codes=[400, 503],
        tags=["Routing"],
    ),
    # --- Observability ---
    EndpointSpec(
        "/health",
        "GET",
        "Console health check",
        response_model=ConsoleHealthResponse,
        tags=["Observability"],
    ),
]

_ALL_MODELS: list[type[BaseModel]] = [
    ErrorResponse,
    StatusResponse,
    DeleteSettingResponse,
    AuthLoginRequest,
    AuthLoginResponse,
    AuthSetupRequest,
    AuthSetupResponse,
    AuthStatusResponse,
    CreateUserRequest,
    UserInfo,
    ListUsersResponse,
    CreateTokenRequest,
    CreateTokenResponse,
    ListTokensResponse,
    ChannelUserInfo,
    CreateChannelUserRequest,
    ListChannelUsersResponse,
    ClusterOverviewResponse,
    ClusterNodesResponse,
    ClusterWorkstreamsResponse,
    NodeDetailResponse,
    ClusterSnapshotResponse,
    ConsoleCreateWsRequest,
    ConsoleCreateWsResponse,
    ConsoleHealthResponse,
    CreateScheduleRequest,
    UpdateScheduleRequest,
    ScheduleInfo,
    ListSchedulesResponse,
    ListScheduleRunsResponse,
    RoleInfo,
    CreateRoleRequest,
    UpdateRoleRequest,
    ListRolesResponse,
    AssignRoleRequest,
    UserRoleInfo,
    ListUserRolesResponse,
    OrgInfo,
    UpdateOrgRequest,
    ListOrgsResponse,
    ToolPolicyInfo,
    CreateToolPolicyRequest,
    UpdateToolPolicyRequest,
    ListToolPoliciesResponse,
    UsageBreakdownItem,
    UsageResponse,
    AuditEventInfo,
    ListAuditEventsResponse,
    VerdictInfo,
    ListVerdictsResponse,
    OutputAssessmentInfo,
    ListOutputAssessmentsResponse,
    AdminMemoryInfo,
    ListAdminMemoriesResponse,
    SettingInfo,
    ListSettingsResponse,
    SettingSchemaInfo,
    ListSettingSchemaResponse,
    UpdateSettingRequest,
    McpServerDetail,
    CreateMcpServerRequest,
    UpdateMcpServerRequest,
    ListMcpServersResponse,
    ImportMcpConfigRequest,
    ImportMcpConfigResponse,
    McpReloadResponse,
    ModelDefinitionInfo,
    CreateModelDefinitionRequest,
    UpdateModelDefinitionRequest,
    ListModelDefinitionsResponse,
    ModelReloadResponse,
    DetectModelRequest,
    DetectModelResponse,
    ModelCapabilitiesResponse,
    KnownModelsResponse,
    AvailableModelInfo,
    ListAvailableModelsResponse,
    RegistrySearchResponse,
    RegistryInstallRequest,
    SkillDiscoverResponse,
    SkillInstallRequest,
    SkillInstallResponse,
    SkillInfo,
    SkillVersionInfo,
    CreateSkillRequest,
    UpdateSkillRequest,
    ListSkillsResponse,
    ListSkillVersionsResponse,
    SkillResourceInfo,
    CreateSkillResourceRequest,
    ListSkillResourcesResponse,
    RouteResponse,
    RouteCreateResponse,
    SkillSummary,
    ListSkillSummaryResponse,
]


def build_console_spec() -> dict[str, Any]:
    """Build the OpenAPI spec for the turnstone console."""
    return build_openapi(
        title="turnstone Console API",
        description="Cluster-wide visibility and control across all turnstone nodes.",
        endpoints=CONSOLE_ENDPOINTS,
        models=_ALL_MODELS,
    )
