from __future__ import annotations

import re
import time
import uuid

from fastapi import APIRouter, Depends, Request

from grantflow.api.errors import raise_agent_error
from grantflow.api.rate_limit import rate_limit
from grantflow.api.security import (
    api_key_configured,
    create_self_serve_agent_access_token,
    create_self_serve_agent_api_key,
    create_self_serve_oauth_client_secret,
    credential_from_request,
    introspect_api_key,
    revoke_self_serve_agent_credential,
    rotate_self_serve_agent_credential,
    self_serve_agent_keys_enabled,
)
from grantflow.api.schemas import (
    AgentCapabilitiesPublicResponse,
    AgentCredentialRevokePublicResponse,
    AgentCredentialRevokeRequest,
    AgentCredentialRotatePublicResponse,
    AgentCredentialRotateRequest,
    AgentIntrospectionPublicResponse,
    AgentOnboardingPublicResponse,
    AgentOnboardingRequest,
    AgentOAuthTokenPublicResponse,
    AgentOAuthTokenRequest,
    AgentRegistrationPublicResponse,
    AgentRegistrationRequest,
    AgentSessionPublicResponse,
    AgentSessionRequest,
    AgentToolManifestPublicResponse,
)
from grantflow.api.openapi_examples import AGENT_RECIPES

agents_router = APIRouter(tags=["agents"])


def _slugify(value: str, fallback: str) -> str:
    normalized = re.sub(r"[^a-z0-9]+", "_", value.strip().lower())
    normalized = normalized.strip("_")
    return normalized or fallback


def _object_schema(properties: dict[str, object], required: list[str] | None = None) -> dict[str, object]:
    return {
        "type": "object",
        "additionalProperties": False,
        "required": required or [],
        "properties": properties,
    }


def _agent_tool_manifest_payload() -> dict[str, object]:
    api_key_required = bool(api_key_configured())
    tenant_property = {
        "type": "string",
        "description": "Tenant scope returned by register_agent or provisioned by the operator.",
    }
    donor_property = {"type": "string", "description": "Donor strategy id, for example usaid."}
    input_context_property = {
        "type": "object",
        "description": "Project context object used by GrantFlow generation.",
        "additionalProperties": True,
    }
    return {
        "schema_version": "2026-04-01",
        "service": "GrantFlow",
        "transport": "http",
        "openapi_url": "/openapi.json",
        "capabilities_url": "/.well-known/agent-capabilities.json",
        "tools": [
            {
                "name": "register_agent",
                "description": "Create a sandbox-scoped agent identity and receive executable sample requests.",
                "transport": "http",
                "method": "POST",
                "path": "/agents/register",
                "auth_required": False,
                "idempotency_key_supported": True,
                "input_schema": _object_schema(
                    {
                        "agent_name": {"type": "string"},
                        "agent_type": {
                            "type": "string",
                            "enum": ["llm_agent", "workflow_agent", "integration", "human_operator"],
                            "default": "llm_agent",
                        },
                        "contact": {"type": "string"},
                        "purpose": {"type": "string"},
                        "requested_tenant_id": {"type": "string"},
                        "client_metadata": {"type": "object", "additionalProperties": True},
                    },
                    ["agent_name"],
                ),
                "output_schema_ref": "#/components/schemas/AgentRegistrationPublicResponse",
                "output_hints": ["Use sample_requests.preflight before sample_requests.generate."],
            },
            {
                "name": "onboard_agent",
                "description": "Request self-serve API-key or OAuth client-credentials onboarding metadata.",
                "transport": "http",
                "method": "POST",
                "path": "/agents/onboarding",
                "auth_required": False,
                "idempotency_key_supported": True,
                "input_schema": _object_schema(
                    {
                        "agent_name": {"type": "string"},
                        "agent_type": {
                            "type": "string",
                            "enum": ["llm_agent", "workflow_agent", "integration", "human_operator"],
                            "default": "workflow_agent",
                        },
                        "auth_type": {
                            "type": "string",
                            "enum": ["auto", "api_key", "oauth_client_credentials"],
                            "default": "auto",
                        },
                        "requested_tenant_id": tenant_property,
                        "requested_scopes": {"type": "array", "items": {"type": "string"}},
                        "ttl_seconds": {"type": "integer", "default": 86400},
                        "client_metadata": {"type": "object", "additionalProperties": True},
                    },
                    ["agent_name"],
                ),
                "output_schema_ref": "#/components/schemas/AgentOnboardingPublicResponse",
                "output_hints": ["Use credential.value as X-API-Key only when status is ready."],
            },
            {
                "name": "create_session",
                "description": "Create a short-lived runtime session credential for an onboarded agent.",
                "transport": "http",
                "method": "POST",
                "path": "/agents/session",
                "auth_required": False,
                "idempotency_key_supported": True,
                "input_schema": _object_schema(
                    {
                        "agent_id": {"type": "string"},
                        "tenant_id": tenant_property,
                        "requested_scopes": {"type": "array", "items": {"type": "string"}},
                        "ttl_seconds": {"type": "integer", "default": 3600},
                        "client_metadata": {"type": "object", "additionalProperties": True},
                    },
                    ["agent_id", "tenant_id"],
                ),
                "output_schema_ref": "#/components/schemas/AgentSessionPublicResponse",
                "output_hints": ["Use credential.value as X-API-Key for short-lived agent runtime calls."],
            },
            {
                "name": "introspect_agent",
                "description": "Validate X-API-Key or Authorization Bearer credentials and return tenant, scope, and expiry claims.",
                "transport": "http",
                "method": "POST",
                "path": "/agents/introspect",
                "auth_required": True,
                "input_schema": _object_schema({}),
                "output_schema_ref": "#/components/schemas/AgentIntrospectionPublicResponse",
                "output_hints": ["If active=false, request a fresh session before calling protected tools."],
            },
            {
                "name": "exchange_oauth_token",
                "description": "Exchange a self-serve OAuth client credential for a Bearer access token.",
                "transport": "http",
                "method": "POST",
                "path": "/agents/oauth/token",
                "auth_required": False,
                "input_schema": _object_schema(
                    {
                        "grant_type": {
                            "type": "string",
                            "enum": ["client_credentials"],
                            "default": "client_credentials",
                        },
                        "client_id": {"type": "string"},
                        "client_secret": {"type": "string"},
                        "scope": {"type": "string"},
                        "ttl_seconds": {"type": "integer", "default": 3600},
                    },
                    ["client_id", "client_secret"],
                ),
                "output_schema_ref": "#/components/schemas/AgentOAuthTokenPublicResponse",
                "output_hints": ["Send access_token as Authorization: Bearer <token> on protected endpoints."],
            },
            {
                "name": "rotate_credential",
                "description": "Rotate a self-serve API key or OAuth client secret, revoking the old credential.",
                "transport": "http",
                "method": "POST",
                "path": "/agents/credentials/rotate",
                "auth_required": False,
                "input_schema": _object_schema(
                    {"credential": {"type": "string"}, "ttl_seconds": {"type": "integer", "default": 3600}},
                    ["credential"],
                ),
                "output_schema_ref": "#/components/schemas/AgentCredentialRotatePublicResponse",
                "output_hints": ["Store the replacement credential securely; it is returned once."],
            },
            {
                "name": "revoke_credential",
                "description": "Revoke a self-serve API key, Bearer token, or OAuth client secret in the current process.",
                "transport": "http",
                "method": "POST",
                "path": "/agents/credentials/revoke",
                "auth_required": False,
                "input_schema": _object_schema(
                    {"credential": {"type": "string"}, "reason": {"type": "string"}},
                    ["credential"],
                ),
                "output_schema_ref": "#/components/schemas/AgentCredentialRevokePublicResponse",
                "output_hints": ["If status=revoked, discard the credential immediately."],
            },
            {
                "name": "run_preflight",
                "description": "Check readiness and grounding policy before a generation run.",
                "transport": "http",
                "method": "POST",
                "path": "/generate/preflight",
                "auth_required": api_key_required,
                "input_schema": _object_schema(
                    {
                        "donor_id": donor_property,
                        "tenant_id": tenant_property,
                        "architect_rag_enabled": {"type": "boolean", "default": True},
                        "input_context": input_context_property,
                        "client_metadata": {"type": "object", "additionalProperties": True},
                    },
                    ["donor_id"],
                ),
                "output_schema_ref": "#/components/schemas/GeneratePreflightPublicResponse",
                "output_hints": ["Inspect go_ahead, warnings, grounding_policy, and blocking_reasons."],
            },
            {
                "name": "start_generation",
                "description": "Start a grant generation job.",
                "transport": "http",
                "method": "POST",
                "path": "/generate",
                "auth_required": api_key_required,
                "idempotency_key_supported": True,
                "input_schema": _object_schema(
                    {
                        "donor_id": donor_property,
                        "tenant_id": tenant_property,
                        "request_id": {"type": "string"},
                        "input_context": input_context_property,
                        "llm_mode": {"type": "boolean", "default": False},
                        "architect_rag_enabled": {"type": "boolean", "default": True},
                        "hitl_enabled": {"type": "boolean", "default": False},
                        "strict_preflight": {"type": "boolean", "default": False},
                        "webhook_url": {"type": "string"},
                        "client_metadata": {"type": "object", "additionalProperties": True},
                    },
                    ["donor_id", "input_context"],
                ),
                "output_schema_ref": "#/components/schemas/GenerateAcceptedPublicResponse",
                "output_hints": ["Persist job_id and request_id. Poll get_status or consume webhook callbacks."],
            },
            {
                "name": "get_status",
                "description": "Read job status and generated state.",
                "transport": "http",
                "method": "GET",
                "path": "/status/{job_id}",
                "auth_required": api_key_required,
                "input_schema": _object_schema({"job_id": {"type": "string"}}, ["job_id"]),
                "output_schema_ref": "#/components/schemas/JobStatusPublicResponse",
                "output_hints": ["Terminal statuses are done, error, and pending_hitl."],
            },
            {
                "name": "get_quality",
                "description": "Read quality, critic, citation, and grounding summary signals.",
                "transport": "http",
                "method": "GET",
                "path": "/status/{job_id}/quality",
                "auth_required": api_key_required,
                "input_schema": _object_schema({"job_id": {"type": "string"}}, ["job_id"]),
                "output_schema_ref": "#/components/schemas/JobQualitySummaryPublicResponse",
                "output_hints": ["Inspect grounded gate, critic, citations, and readiness signals before export."],
            },
            {
                "name": "get_events",
                "description": "Read machine-consumable audit events for a job lifecycle.",
                "transport": "http",
                "method": "GET",
                "path": "/status/{job_id}/events",
                "auth_required": api_key_required,
                "input_schema": _object_schema({"job_id": {"type": "string"}}, ["job_id"]),
                "output_schema_ref": "#/components/schemas/JobEventsPublicResponse",
                "output_hints": ["Use event_count and events to reconstruct lifecycle progress."],
            },
            {
                "name": "ingest_text",
                "description": "Ingest pre-extracted text into the RAG corpus for a given donor and tenant. Agent-native alternative to PDF upload — no multipart encoding required.",
                "transport": "http",
                "method": "POST",
                "path": "/ingest/text",
                "auth_required": api_key_required,
                "idempotency_key_supported": False,
                "input_schema": _object_schema(
                    {
                        "text": {"type": "string", "description": "Raw text to ingest."},
                        "donor_id": donor_property,
                        "title": {"type": "string", "default": "document"},
                        "tenant_id": tenant_property,
                        "doc_family": {
                            "type": "string",
                            "description": "Document family tag, e.g. donor_results_guidance.",
                        },
                        "chunk_size": {"type": "integer", "default": 1000},
                        "overlap": {"type": "integer", "default": 200},
                        "metadata": {"type": "object", "additionalProperties": True},
                    },
                    ["text", "donor_id"],
                ),
                "output_hints": [
                    "Returns chunks_ingested and namespace. Run ingest_readiness after to confirm coverage."
                ],
            },
            {
                "name": "hitl_approve",
                "description": "Approve or reject a pending Human-in-the-Loop checkpoint. A job with status=pending_hitl cannot proceed until its checkpoint is decided.",
                "transport": "http",
                "method": "POST",
                "path": "/hitl/approve",
                "auth_required": api_key_required,
                "idempotency_key_supported": True,
                "input_schema": _object_schema(
                    {
                        "checkpoint_id": {"type": "string"},
                        "approved": {"type": "boolean"},
                        "feedback": {"type": "string"},
                        "request_id": {"type": "string"},
                    },
                    ["checkpoint_id", "approved"],
                ),
                "output_hints": [
                    "If already_decided=true, the checkpoint was previously actioned — idempotent replay."
                ],
            },
            {
                "name": "hitl_list_pending",
                "description": "List all pending HITL checkpoints waiting for a decision.",
                "transport": "http",
                "method": "GET",
                "path": "/hitl/pending",
                "auth_required": api_key_required,
                "input_schema": _object_schema(
                    {
                        "tenant_id": tenant_property,
                        "donor_id": donor_property,
                        "limit": {"type": "integer", "default": 50},
                    },
                    [],
                ),
                "output_hints": ["Use checkpoint_id from results as input to hitl_approve."],
            },
            {
                "name": "get_export_payload",
                "description": "Read the structured export payload for a completed job. Returns toc_draft, logframe, quality signals, and export_readiness status.",
                "transport": "http",
                "method": "GET",
                "path": "/status/{job_id}/export-payload",
                "auth_required": api_key_required,
                "input_schema": _object_schema({"job_id": {"type": "string"}}, ["job_id"]),
                "output_hints": ["Inspect export_readiness.readiness_status before triggering a document export."],
            },
        ],
        "recommended_workflows": {
            "sandbox_generate": [
                "onboard_agent",
                "create_session",
                "introspect_agent",
                "exchange_oauth_token",
                "rotate_credential",
                "revoke_credential",
                "ingest_text",
                "run_preflight",
                "start_generation",
                "get_status",
                "hitl_approve",
                "hitl_list_pending",
                "get_quality",
                "get_events",
                "get_export_payload",
            ]
        },
    }


def _agent_policy_payload() -> dict[str, object]:
    return {
        "schema_version": "2026-04-01",
        "service": "GrantFlow",
        "scopes": {
            "generate:write": "Run preflight and start generation.",
            "status:read": "Read job status and lifecycle events.",
            "quality:read": "Read quality, grounding, and review readiness signals.",
            "export:write": "Create export artifacts when authorized.",
            "ingest:write": "Ingest text or documents into the RAG corpus.",
        },
        "credentials": {
            "self_serve_api_keys": {
                "enabled": self_serve_agent_keys_enabled(),
                "max_ttl_seconds": 604800,
                "session_default_ttl_seconds": 3600,
                "tenant_bound": True,
                "scope_bound": True,
                "revocation": "redis_jti_denylist_with_in_process_fallback",
                "revocation_scope": "redis_and_in_process_or_in_process",
                "restart_safe": "depends_on_redis",
                "revocation_note": "Revocation writes to Redis (GRANTFLOW_REDIS_URL) when configured, surviving restarts. Without Redis, an in-process JTI denylist is used and does not survive restarts. Enforce revocation at the gateway layer for production multi-replica deployments.",
                "rotation_endpoint": "/agents/credentials/rotate",
                "revoke_endpoint": "/agents/credentials/revoke",
            },
            "oauth_client_credentials": {
                "supported": True,
                "issuer": "gateway-provided",
                "grant_type": "client_credentials",
                "self_serve_token_endpoint": "/agents/oauth/token",
                "token_auth_methods": ["client_secret_basic", "client_secret_post", "private_key_jwt"],
                "access_token_type": "Bearer",
                "scope_claim": "scope",
                "tenant_claim": "tenant_id",
                "agent_claim": "client_id",
                "introspection_endpoint": "/agents/introspect",
                "note": "GrantFlow exposes metadata; production token issuance belongs at the gateway layer.",
            },
        },
        "runtime_rules": {
            "preflight_required_before_generate": True,
            "idempotency_required_for_repeated_writes": True,
            "terminal_statuses": ["done", "error", "pending_hitl"],
            "pending_hitl_requires_human_authorization": True,
        },
        "webhooks": {
            "signature_header": "X-GrantFlow-Signature",
            "timestamp_header": "X-GrantFlow-Timestamp",
            "delivery_header": "X-GrantFlow-Delivery",
            "signature_scheme": "sha256=<hex-hmac-of-raw-body>",
            "timestamp_tolerance_seconds": 300,
        },
        "export_rules": {
            "inspect_quality_before_export": True,
            "inspect_events_before_export": True,
            "production_exports_require_operator_policy": True,
        },
    }


def _agent_descriptor_payload() -> dict[str, object]:
    return {
        "schema_version": "2026-04-01",
        "name": "GrantFlow",
        "description": "Agent-native grant workflow API for governed proposal operations.",
        "auth": {
            "type": "api_key_or_gateway_oauth",
            "api_key_header": "X-API-Key",
            "onboarding_url": "/agents/onboarding",
            "oauth_client_credentials": "gateway-provided",
        },
        "api": {
            "type": "openapi",
            "url": "/openapi.json",
            "is_user_authenticated": True,
        },
        "links": {
            "capabilities": "/.well-known/agent-capabilities.json",
            "policy": "/.well-known/agent-policy.json",
            "tools": "/.well-known/agent-tools.json",
            "recipes": "/.well-known/agent-recipes.json",
            "quickstart": "/docs/agents/quickstart.md",
            "agents_md": "/AGENTS.md",
            "mcp": "/docs/agents/mcp.md",
        },
        "entrypoints": {
            "onboard": "POST /agents/onboarding",
            "session": "POST /agents/session",
            "introspect": "POST /agents/introspect",
            "token": "POST /agents/oauth/token",
            "rotate_credential": "POST /agents/credentials/rotate",
            "revoke_credential": "POST /agents/credentials/revoke",
            "register": "POST /agents/register",
            "preflight": "POST /generate/preflight",
            "generate": "POST /generate",
            "status": "GET /status/{job_id}",
            "quality": "GET /status/{job_id}/quality",
            "events": "GET /status/{job_id}/events",
        },
        "operational_rules": [
            "Run preflight before generation.",
            "Use request_id for idempotent writes.",
            "Treat pending_hitl as a stop point unless explicitly authorized.",
            "Inspect quality and events before export.",
        ],
    }


@agents_router.get("/.well-known/agent-policy.json", response_model_exclude_none=True)
def agent_policy():
    return _agent_policy_payload()


@agents_router.get(
    "/.well-known/agent-tools.json",
    response_model=AgentToolManifestPublicResponse,
    response_model_exclude_none=True,
)
def agent_tool_manifest():
    return _agent_tool_manifest_payload()


@agents_router.get("/.well-known/agent-recipes.json", response_model_exclude_none=True)
def agent_recipes():
    return {
        "schema_version": "2026-04-01",
        "service": "GrantFlow",
        "openapi_url": "/openapi.json",
        "tool_manifest_url": "/.well-known/agent-tools.json",
        "recipes": AGENT_RECIPES,
    }


@agents_router.get("/.well-known/agent.json", response_model_exclude_none=True)
def agent_descriptor():
    return _agent_descriptor_payload()


@agents_router.get("/.well-known/ai-plugin.json", response_model_exclude_none=True)
def ai_plugin_descriptor():
    return _agent_descriptor_payload()


@agents_router.get(
    "/.well-known/agent-capabilities.json",
    response_model=AgentCapabilitiesPublicResponse,
    response_model_exclude_none=True,
)
def agent_capabilities():
    api_key_required = bool(api_key_configured())
    return {
        "schema_version": "2026-04-01",
        "service": "GrantFlow",
        "description": "Agent-native grant workflow automation API for readiness checks, proposal generation, review, and exports.",
        "openapi_url": "/openapi.json",
        "registration_url": "/agents/register",
        "onboarding_url": "/agents/onboarding",
        "session_url": "/agents/session",
        "agent_descriptor_url": "/.well-known/agent.json",
        "ai_plugin_url": "/.well-known/ai-plugin.json",
        "agent_policy_url": "/.well-known/agent-policy.json",
        "tool_manifest_url": "/.well-known/agent-tools.json",
        "agent_recipes_url": "/.well-known/agent-recipes.json",
        "auth": {
            "mode": "api_key_header",
            "header": "X-API-Key",
            "api_key_required": api_key_required,
            "sandbox_registration": True,
            "self_serve_api_keys_enabled": self_serve_agent_keys_enabled(),
            "oauth_client_credentials": {
                "supported": True,
                "token_endpoint": "gateway-provided",
                "self_serve_token_endpoint": "/agents/oauth/token",
                "note": "GrantFlow exposes onboarding metadata; production OAuth token issuance belongs at the gateway layer.",
            },
            "production_note": "Production API keys or OAuth client credentials are provisioned by policy; sandbox self-serve keys require GRANTFLOW_AGENT_SELF_SERVE_KEYS_ENABLED=true.",
        },
        "endpoints": [
            {
                "method": "POST",
                "path": "/agents/register",
                "description": "Create a sandbox-scoped agent identity and receive next actions.",
                "auth_required": False,
                "idempotency_key_supported": True,
            },
            {
                "method": "POST",
                "path": "/agents/session",
                "description": "Create a short-lived runtime session credential for an onboarded agent.",
                "auth_required": False,
                "idempotency_key_supported": True,
            },
            {
                "method": "POST",
                "path": "/agents/introspect",
                "description": "Validate the current X-API-Key and return active tenant, scope, and expiry claims.",
                "auth_required": True,
            },
            {
                "method": "POST",
                "path": "/agents/oauth/token",
                "description": "Exchange self-serve OAuth client credentials for a Bearer access token.",
                "auth_required": False,
            },
            {
                "method": "POST",
                "path": "/agents/credentials/rotate",
                "description": "Rotate a self-serve API key or OAuth client secret and revoke the old credential.",
                "auth_required": False,
            },
            {
                "method": "POST",
                "path": "/agents/credentials/revoke",
                "description": "Revoke a self-serve API key, Bearer token, or OAuth client secret in the current process.",
                "auth_required": False,
            },
            {
                "method": "POST",
                "path": "/agents/onboarding",
                "description": "Request self-serve API-key or OAuth client-credentials onboarding metadata.",
                "auth_required": False,
                "idempotency_key_supported": True,
            },
            {
                "method": "POST",
                "path": "/generate/preflight",
                "description": "Check donor readiness and grounding policy before generation.",
                "auth_required": api_key_required,
            },
            {
                "method": "POST",
                "path": "/generate",
                "description": "Start a grant generation job with optional HITL checkpoints and webhook callback.",
                "auth_required": api_key_required,
                "idempotency_key_supported": True,
            },
            {
                "method": "GET",
                "path": "/status/{job_id}",
                "description": "Read job status, generated sections, HITL state, and traceability signals.",
                "auth_required": api_key_required,
            },
            {
                "method": "GET",
                "path": "/status/{job_id}/quality",
                "description": "Read quality, critic, citation, and grounded generation summary signals.",
                "auth_required": api_key_required,
            },
            {
                "method": "GET",
                "path": "/status/{job_id}/events",
                "description": "Read machine-consumable audit events for the job lifecycle.",
                "auth_required": api_key_required,
            },
            {
                "method": "POST",
                "path": "/hitl/approve",
                "description": "Resolve a human-in-the-loop checkpoint when an agent is authorized to continue.",
                "auth_required": api_key_required,
                "idempotency_key_supported": True,
            },
            {
                "method": "POST",
                "path": "/export",
                "description": "Export generated artifacts in supported document formats.",
                "auth_required": api_key_required,
                "idempotency_key_supported": True,
            },
        ],
        "workflows": [
            {
                "name": "sandbox_generate",
                "description": "Register an agent, run preflight, start deterministic generation, poll status, then inspect quality and events.",
                "steps": [
                    "POST /agents/register",
                    "POST /agents/onboarding",
                    "POST /agents/session",
                    "POST /agents/oauth/token",
                    "POST /agents/introspect",
                    "POST /agents/credentials/rotate",
                    "POST /agents/credentials/revoke",
                    "POST /generate/preflight",
                    "POST /generate",
                    "GET /status/{job_id}",
                    "GET /status/{job_id}/quality",
                    "GET /status/{job_id}/events",
                ],
            }
        ],
        "error_contract": {
            "preferred_shape": {
                "detail": {
                    "code": "machine_readable_error_code",
                    "message": "human readable summary",
                    "retryable": False,
                    "next_action": "specific recovery action",
                }
            },
            "legacy_detail_supported": True,
        },
        "docs": {
            "agent_quickstart": "/docs/agents/quickstart.md",
            "agents_md": "/AGENTS.md",
            "openapi": "/openapi.json",
            "policy": "/.well-known/agent-policy.json",
            "tool_manifest": "/.well-known/agent-tools.json",
            "recipes": "/.well-known/agent-recipes.json",
            "mcp": "/docs/agents/mcp.md",
        },
        "sandbox": {
            "endpoint": "GET /demo/run",
            "auth_required": False,
            "description": (
                "Zero-auth smoke run. Executes a deterministic non-LLM cycle and returns "
                "structured quality/status output in under 2 seconds. "
                "Use this to verify the API is reachable before onboarding."
            ),
            "response_headers": {
                "x-agent-sandbox": "true",
                "x-auth-required": "false",
            },
            "note": "A 'conditional' verdict in the demo response is expected — no corpus is ingested in the sandbox.",
        },
        "mcp": {
            "description": "17 MCP tools mirroring the full HTTP API surface.",
            "stdio": {
                "command": "python -m grantflow.mcp.server",
                "transport": "stdio",
                "tools_count": 17,
                "auth_required": False,
                "env_required": ["GRANTFLOW_BASE_URL"],
                "env_optional": ["GRANTFLOW_API_KEY"],
                "note": "No extra dependencies. Works with any MCP-compatible runtime.",
            },
            "streamable_http": {
                "command": "python -m grantflow.mcp.fastmcp_server",
                "transport": "streamable-http",
                "install": "pip install 'grantflow[mcp]'",
                "env_required": ["GRANTFLOW_BASE_URL"],
                "env_optional": ["GRANTFLOW_API_KEY", "GRANTFLOW_MCP_TRANSPORT"],
                "note": "Production MCP transport using the official Python SDK.",
            },
        },
    }


@agents_router.post(
    "/agents/register", response_model=AgentRegistrationPublicResponse, response_model_exclude_none=True
)
def register_agent(payload: AgentRegistrationRequest):
    agent_slug = _slugify(payload.agent_name, "agent")
    tenant_id = _slugify(payload.requested_tenant_id or f"sandbox_{agent_slug}", "sandbox_agent")
    input_context = {
        "project": "AI civil service capability",
        "country": "Kazakhstan",
        "timeframe": "2026-2027",
    }
    return {
        "status": "registered",
        "agent_id": f"agent_{agent_slug}",
        "tenant_id": tenant_id,
        "auth_mode": "api_key_header",
        "api_key_required": bool(api_key_configured()),
        "next_actions": [
            "Read /.well-known/agent-capabilities.json",
            "Call /generate/preflight with the returned tenant_id",
            "Call /generate with request_id for idempotency and optional webhook_url",
            "Poll /status/{job_id} or consume webhook callbacks",
            "Inspect /status/{job_id}/quality and /status/{job_id}/events before exporting",
        ],
        "links": {
            "capabilities": "/.well-known/agent-capabilities.json",
            "openapi": "/openapi.json",
            "quickstart": "/docs/agents/quickstart.md",
        },
        "sample_requests": {
            "preflight": {
                "donor_id": "usaid",
                "tenant_id": tenant_id,
                "architect_rag_enabled": False,
                "input_context": input_context,
                "client_metadata": {"agent_id": f"agent_{agent_slug}"},
            },
            "generate": {
                "donor_id": "usaid",
                "tenant_id": tenant_id,
                "request_id": f"agent_{agent_slug}:sandbox-generate-001",
                "input_context": input_context,
                "llm_mode": False,
                "architect_rag_enabled": False,
                "hitl_enabled": False,
                "strict_preflight": False,
                "client_metadata": {"agent_id": f"agent_{agent_slug}"},
            },
        },
    }


@agents_router.post(
    "/agents/onboarding",
    response_model=AgentOnboardingPublicResponse,
    response_model_exclude_none=True,
    dependencies=[Depends(rate_limit(limit=10, window_seconds=60, endpoint="onboarding"))],
)
def onboard_agent(payload: AgentOnboardingRequest):
    agent_slug = _slugify(payload.agent_name, "agent")
    tenant_id = _slugify(payload.requested_tenant_id or f"sandbox_{agent_slug}", "sandbox_agent")
    agent_id = f"agent_{agent_slug}"
    requested_scopes = payload.requested_scopes or ["generate:write", "status:read", "quality:read"]
    links = {
        "capabilities": "/.well-known/agent-capabilities.json",
        "tools": "/.well-known/agent-tools.json",
        "openapi": "/openapi.json",
        "quickstart": "/docs/agents/quickstart.md",
        "agents_md": "/AGENTS.md",
    }

    if payload.auth_type in {"auto", "api_key"} and self_serve_agent_keys_enabled():
        credential = create_self_serve_agent_api_key(
            agent_id=agent_id,
            tenant_id=tenant_id,
            scopes=requested_scopes,
            ttl_seconds=payload.ttl_seconds,
        )
        return {
            "status": "ready",
            "agent_id": agent_id,
            "tenant_id": tenant_id,
            "auth_type": "api_key",
            "credential": credential,
            "next_actions": [
                "Store credential.value securely; it is returned once.",
                "Send credential.value as X-API-Key on protected endpoints.",
                "Run /generate/preflight before /generate.",
                "Use request_id for idempotency on generation and export calls.",
            ],
            "links": links,
            "security_notes": [
                "Self-serve keys are signed bearer credentials for sandbox or controlled deployments.",
                "Use GRANTFLOW_AGENT_KEY_SIGNING_SECRET in shared environments.",
                "Use gateway-managed OAuth for production multi-tenant deployments.",
            ],
        }

    oauth_self_serve_ready = payload.auth_type == "oauth_client_credentials" and self_serve_agent_keys_enabled()
    return {
        "status": "ready" if oauth_self_serve_ready else "pending_operator",
        "agent_id": agent_id,
        "tenant_id": tenant_id,
        "auth_type": "oauth_client_credentials" if payload.auth_type == "oauth_client_credentials" else "api_key",
        "oauth_client": {
            "grant_type": "client_credentials",
            "requested_scopes": requested_scopes,
            "token_endpoint": "/agents/oauth/token" if self_serve_agent_keys_enabled() else "gateway-provided",
            "client_name": payload.agent_name,
            "tenant_id": tenant_id,
            "client_metadata": payload.client_metadata or {},
            **(
                create_self_serve_oauth_client_secret(
                    agent_id=agent_id,
                    tenant_id=tenant_id,
                    scopes=requested_scopes,
                    ttl_seconds=payload.ttl_seconds,
                )
                if oauth_self_serve_ready
                else {}
            ),
        },
        "next_actions": [
            "If oauth_client.client_secret is present, exchange it at /agents/oauth/token.",
            "Send the returned access_token as Authorization: Bearer <token> on protected endpoints.",
            "Use links.openapi and links.tools to build typed calls while credentials are pending.",
            "For production, provision OAuth client credentials at the gateway/IAM layer.",
            "For sandbox self-serve API keys, set GRANTFLOW_AGENT_SELF_SERVE_KEYS_ENABLED=true.",
        ],
        "links": links,
        "security_notes": [
            "GrantFlow does not mint production OAuth secrets inside the app process.",
            "Production OAuth issuance should be owned by the gateway, IAM, or API management layer.",
        ],
    }


@agents_router.post(
    "/agents/oauth/token",
    response_model=AgentOAuthTokenPublicResponse,
    response_model_exclude_none=True,
)
def exchange_agent_oauth_token(payload: AgentOAuthTokenRequest):
    requested_scopes = payload.scope.split() if payload.scope else []
    token = create_self_serve_agent_access_token(
        client_id=payload.client_id,
        client_secret=payload.client_secret,
        requested_scopes=requested_scopes,
        ttl_seconds=payload.ttl_seconds,
    )
    if token is None:
        raise_agent_error(
            status_code=401,
            code="invalid_client_credentials",
            message="OAuth client credentials are invalid, expired, disabled, or request unauthorized scopes.",
            retryable=False,
            next_action="Request fresh OAuth client credentials with /agents/onboarding.",
        )
    return token


@agents_router.post(
    "/agents/credentials/revoke",
    response_model=AgentCredentialRevokePublicResponse,
    response_model_exclude_none=True,
)
def revoke_agent_credential(payload: AgentCredentialRevokeRequest):
    return revoke_self_serve_agent_credential(payload.credential)


@agents_router.post(
    "/agents/credentials/rotate",
    response_model=AgentCredentialRotatePublicResponse,
    response_model_exclude_none=True,
    dependencies=[Depends(rate_limit(limit=10, window_seconds=60, endpoint="rotate"))],
)
def rotate_agent_credential(payload: AgentCredentialRotateRequest):
    rotated = rotate_self_serve_agent_credential(payload.credential, ttl_seconds=payload.ttl_seconds)
    if rotated is None:
        raise_agent_error(
            status_code=401,
            code="invalid_credential_rotation_request",
            message="Credential is invalid, expired, already revoked, or not self-serve.",
            retryable=False,
            next_action="Request a fresh credential with /agents/onboarding or /agents/session.",
        )
    return rotated


@agents_router.post("/agents/session", response_model=AgentSessionPublicResponse, response_model_exclude_none=True)
def create_agent_session(payload: AgentSessionRequest):
    requested_scopes = payload.requested_scopes or ["generate:write", "status:read", "quality:read"]
    ttl_seconds = min(max(int(payload.ttl_seconds), 1), 3600)
    constraints = {
        "tenant_bound": True,
        "scope_bound": True,
        "pending_hitl_requires_human_authorization": True,
        "inspect_quality_before_export": True,
    }
    links = {
        "capabilities": "/.well-known/agent-capabilities.json",
        "policy": "/.well-known/agent-policy.json",
        "tools": "/.well-known/agent-tools.json",
        "openapi": "/openapi.json",
    }
    session_id = f"ags_{uuid.uuid4().hex}"
    if self_serve_agent_keys_enabled():
        credential = create_self_serve_agent_api_key(
            agent_id=payload.agent_id,
            tenant_id=payload.tenant_id,
            scopes=requested_scopes,
            ttl_seconds=ttl_seconds,
        )
        return {
            "status": "ready",
            "session_id": session_id,
            "agent_id": payload.agent_id,
            "tenant_id": payload.tenant_id,
            "expires_at": credential["expires_at"],
            "credential": credential,
            "requested_scopes": requested_scopes,
            "constraints": constraints,
            "links": links,
        }
    return {
        "status": "pending_operator",
        "session_id": session_id,
        "agent_id": payload.agent_id,
        "tenant_id": payload.tenant_id,
        "expires_at": int(time.time()) + ttl_seconds,
        "requested_scopes": requested_scopes,
        "constraints": constraints,
        "links": links,
    }


@agents_router.post(
    "/agents/introspect",
    response_model=AgentIntrospectionPublicResponse,
    response_model_exclude_none=True,
)
def introspect_agent(request: Request):
    return introspect_api_key(credential_from_request(request))
