import { Hono } from 'hono'
import type { OpenAPIV3 } from 'openapi-types'

/**
 * OpenAPI 3.0 spec for the Spanlens public REST API.
 *
 * Served as JSON at  GET /api/v1/openapi.json
 * Swagger UI at      GET /api/v1/docs
 *
 * Covers the externally useful endpoints only — internal cron, webhooks,
 * and invite-flow plumbing are intentionally omitted.
 *
 * P3.6 (2026-05-19): The SPEC constant is now typed as `OpenAPIV3.Document`
 * so structural typos (wrong keys, missing `responses`, etc.) become compile
 * errors instead of runtime spec drift. The drift-detector test in
 * `__tests__/openapi-drift.test.ts` additionally enforces that every
 * documented path is mounted on the real Hono app.
 */

export const SPEC: OpenAPIV3.Document = {
  openapi: '3.0.3',
  info: {
    title: 'Spanlens API',
    version: '1.0.0',
    description:
      'REST API for Spanlens LLM observability. All authenticated endpoints require a Supabase JWT in `Authorization: Bearer <token>`. Proxy endpoints use a Spanlens API key.',
    contact: { email: 'support@spanlens.io' },
    license: { name: 'MIT', url: 'https://github.com/spanlens/Spanlens/blob/main/LICENSE' },
  },
  externalDocs: {
    description: 'Full documentation',
    url: 'https://www.spanlens.io/docs',
  },
  servers: [
    { url: 'https://spanlens-server.vercel.app', description: 'Production' },
    { url: 'http://localhost:3001', description: 'Local dev' },
  ],
  tags: [
    { name: 'Health',         description: 'Service health check' },
    { name: 'Waitlist',       description: 'Early-access waitlist (public)' },
    { name: 'Organizations',  description: 'Workspace management' },
    { name: 'Projects',       description: 'Project scoping' },
    { name: 'API Keys',       description: 'API key CRUD' },
    { name: 'Provider Keys',  description: 'Encrypted provider credentials' },
    { name: 'Requests',       description: 'LLM request log' },
    { name: 'Stats',          description: 'Aggregated metrics & latency' },
    { name: 'Traces',         description: 'Agent span traces' },
    { name: 'Prompts',        description: 'Prompt versioning & A/B' },
    { name: 'Anomalies',      description: 'Statistical anomaly detection' },
    { name: 'Security',       description: 'PII & injection scan results' },
    { name: 'Alerts',         description: 'Threshold-based alerting' },
    { name: 'Recommendations',description: 'Model swap suggestions' },
    { name: 'Members',        description: 'Team membership & roles' },
    { name: 'Proxy',          description: 'LLM proxy passthrough (API-key auth)' },
  ],
  components: {
    securitySchemes: {
      BearerJWT: {
        type: 'http',
        scheme: 'bearer',
        description: 'Supabase JWT obtained from `/auth/v1/token`. Valid for 1h; refresh via Supabase client.',
      },
      ApiKey: {
        type: 'http',
        scheme: 'bearer',
        description: 'Spanlens API key (starts with `sl_`). Created in Settings → API Keys.',
      },
    },
    schemas: {
      Error: {
        type: 'object',
        properties: {
          error: { type: 'string', example: 'Organization not found' },
        },
        required: ['error'],
      },
      Organization: {
        type: 'object',
        properties: {
          id: { type: 'string', format: 'uuid' },
          name: { type: 'string', example: 'Acme Inc.' },
          slug: { type: 'string', example: 'acme-inc' },
          plan: { type: 'string', enum: ['free', 'starter', 'team', 'enterprise'] },
          created_at: { type: 'string', format: 'date-time' },
        },
      },
      Project: {
        type: 'object',
        properties: {
          id: { type: 'string', format: 'uuid' },
          name: { type: 'string', example: 'Production' },
          organization_id: { type: 'string', format: 'uuid' },
          created_at: { type: 'string', format: 'date-time' },
        },
      },
      ApiKey: {
        type: 'object',
        properties: {
          id: { type: 'string', format: 'uuid' },
          name: { type: 'string', example: 'prod-key' },
          key_preview: { type: 'string', example: 'sl_live_abc...xyz' },
          project_id: { type: 'string', format: 'uuid' },
          created_at: { type: 'string', format: 'date-time' },
        },
      },
      SecurityFlag: {
        type: 'object',
        properties: {
          type: { type: 'string', enum: ['pii', 'injection'], example: 'pii' },
          pattern: { type: 'string', example: 'ssn-us' },
          sample: { type: 'string', example: '12*****89', description: 'Masked 6-char excerpt — raw PII is never stored' },
        },
        required: ['type', 'pattern', 'sample'],
      },
      FlaggedRequest: {
        type: 'object',
        description: 'A request (or response) that matched one or more PII or injection rules.',
        properties: {
          id: { type: 'string', format: 'uuid' },
          provider: { type: 'string', enum: ['openai', 'anthropic', 'gemini', 'azure'] },
          model: { type: 'string', example: 'gpt-4o-mini' },
          status_code: { type: 'integer' },
          latency_ms: { type: 'integer' },
          cost_usd: { type: 'number', format: 'float', nullable: true },
          flags: { type: 'array', items: { $ref: '#/components/schemas/SecurityFlag' }, description: 'Flags from the request body (user input)' },
          response_flags: { type: 'array', items: { $ref: '#/components/schemas/SecurityFlag' }, description: 'Flags from the LLM response body (model output)' },
          created_at: { type: 'string', format: 'date-time' },
        },
      },
      Request: {
        type: 'object',
        properties: {
          id: { type: 'string', format: 'uuid' },
          provider: { type: 'string', enum: ['openai', 'anthropic', 'gemini', 'azure'] },
          model: { type: 'string', example: 'gpt-4o-mini' },
          prompt_tokens: { type: 'integer', description: 'Gross input tokens (cache portions included).' },
          completion_tokens: { type: 'integer' },
          total_tokens: { type: 'integer' },
          cache_read_tokens: { type: 'integer', description: 'Subset of prompt_tokens that hit a prompt cache (Anthropic cache_read_input_tokens / OpenAI cached_tokens). 0 if not applicable.' },
          cache_write_tokens: { type: 'integer', description: 'Subset of prompt_tokens that wrote a cache entry (Anthropic cache_creation_input_tokens). 0 if not applicable.' },
          cost_usd: { type: 'number', format: 'float', nullable: true },
          latency_ms: { type: 'integer' },
          proxy_overhead_ms: { type: 'integer', nullable: true },
          status_code: { type: 'integer' },
          user_id: { type: 'string', nullable: true, description: 'Customer-supplied end-user ID via x-spanlens-user header.' },
          session_id: { type: 'string', nullable: true, description: 'Customer-supplied session ID via x-spanlens-session header.' },
          flags: { type: 'array', items: { $ref: '#/components/schemas/SecurityFlag' }, description: 'Request-body security flags' },
          response_flags: { type: 'array', items: { $ref: '#/components/schemas/SecurityFlag' }, description: 'Response-body security flags' },
          created_at: { type: 'string', format: 'date-time' },
        },
      },
      UserAnalytics: {
        type: 'object',
        description: 'Per-end-user aggregate stats. Sourced from requests grouped by user_id (x-spanlens-user header).',
        properties: {
          user_id: { type: 'string' },
          total_requests: { type: 'integer' },
          total_tokens: { type: 'integer' },
          total_cost_usd: { type: 'number', format: 'float' },
          avg_latency_ms: { type: 'number', format: 'float', nullable: true },
          first_seen: { type: 'string', format: 'date-time' },
          last_seen: { type: 'string', format: 'date-time' },
          error_requests: { type: 'integer', description: 'Count of requests with status_code >= 400' },
          distinct_models: { type: 'integer', description: 'Number of distinct models used by this user' },
        },
      },
      Trace: {
        type: 'object',
        properties: {
          id: { type: 'string', format: 'uuid' },
          name: { type: 'string', example: 'agent.run' },
          status: { type: 'string', enum: ['ok', 'error'] },
          total_tokens: { type: 'integer' },
          total_cost_usd: { type: 'number', format: 'float', nullable: true },
          duration_ms: { type: 'integer', nullable: true },
          span_count: { type: 'integer' },
          created_at: { type: 'string', format: 'date-time' },
        },
      },
      StatsOverview: {
        type: 'object',
        properties: {
          totalRequests: { type: 'integer' },
          successRequests: { type: 'integer' },
          errorRequests: { type: 'integer' },
          totalCostUsd: { type: 'number' },
          totalTokens: { type: 'integer' },
          avgLatencyMs: { type: 'integer' },
        },
      },
      LatencyStats: {
        type: 'object',
        properties: {
          sampleCount: { type: 'integer' },
          overheadSampleCount: { type: 'integer' },
          hours: { type: 'integer' },
          provider: {
            type: 'object',
            properties: {
              p50Ms: { type: 'integer' },
              p95Ms: { type: 'integer' },
              p99Ms: { type: 'integer' },
              avgMs: { type: 'integer' },
            },
          },
          overhead: {
            type: 'object',
            properties: {
              p50Ms: { type: 'integer' },
              p95Ms: { type: 'integer' },
              p99Ms: { type: 'integer' },
              avgMs: { type: 'integer' },
              targetP95Ms: { type: 'integer', description: 'SLA target: p95 overhead < 50ms' },
              withinSla: { type: 'boolean' },
            },
          },
        },
      },
      Anomaly: {
        type: 'object',
        properties: {
          provider: { type: 'string' },
          model: { type: 'string' },
          metric: { type: 'string', enum: ['latency_ms', 'cost_usd'] },
          zScore: { type: 'number' },
          observed: { type: 'number' },
          baseline: { type: 'number' },
          severity: { type: 'string', enum: ['low', 'medium', 'high'] },
          detectedAt: { type: 'string', format: 'date-time' },
        },
      },
      ModelRecommendation: {
        type: 'object',
        properties: {
          currentModel: { type: 'string', example: 'gpt-4o' },
          suggestedModel: { type: 'string', example: 'gpt-4o-mini' },
          reason: { type: 'string' },
          estimatedMonthlySavingsUsd: { type: 'number' },
          sampleCount: { type: 'integer' },
        },
      },
      Member: {
        type: 'object',
        properties: {
          user_id: { type: 'string', format: 'uuid' },
          email: { type: 'string', format: 'email' },
          role: { type: 'string', enum: ['admin', 'editor', 'viewer'] },
          joined_at: { type: 'string', format: 'date-time' },
        },
      },
    },
  },
  paths: {
    '/health': {
      get: {
        tags: ['Health'],
        summary: 'Health check',
        operationId: 'getHealth',
        security: [],
        responses: {
          200: {
            description: 'Service is healthy',
            content: {
              'application/json': {
                schema: {
                  type: 'object',
                  properties: {
                    status: { type: 'string', example: 'ok' },
                    timestamp: { type: 'string', format: 'date-time' },
                  },
                },
              },
            },
          },
        },
      },
    },
    '/api/v1/waitlist': {
      post: {
        tags: ['Waitlist'],
        summary: 'Join early-access waitlist',
        operationId: 'joinWaitlist',
        security: [],
        requestBody: {
          required: true,
          content: {
            'application/json': {
              schema: {
                type: 'object',
                required: ['email'],
                properties: {
                  email: { type: 'string', format: 'email' },
                  name: { type: 'string' },
                  company: { type: 'string' },
                  use_case: { type: 'string' },
                },
              },
            },
          },
        },
        responses: {
          201: { description: 'Added to waitlist' },
          200: { description: 'Already registered (idempotent)' },
          400: { description: 'Invalid email', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } },
        },
      },
    },
    '/api/v1/organizations/me': {
      get: {
        tags: ['Organizations'],
        summary: 'Get active organization',
        operationId: 'getActiveOrg',
        security: [{ BearerJWT: [] }],
        responses: {
          200: {
            description: 'Active organization',
            content: { 'application/json': { schema: { $ref: '#/components/schemas/Organization' } } },
          },
          401: { description: 'Unauthorized' },
        },
      },
    },
    '/api/v1/projects': {
      get: {
        tags: ['Projects'],
        summary: 'List projects',
        operationId: 'listProjects',
        security: [{ BearerJWT: [] }],
        responses: {
          200: {
            description: 'Project list',
            content: {
              'application/json': {
                schema: {
                  type: 'object',
                  properties: {
                    success: { type: 'boolean' },
                    data: { type: 'array', items: { $ref: '#/components/schemas/Project' } },
                  },
                },
              },
            },
          },
        },
      },
      post: {
        tags: ['Projects'],
        summary: 'Create project',
        operationId: 'createProject',
        security: [{ BearerJWT: [] }],
        requestBody: {
          required: true,
          content: {
            'application/json': {
              schema: { type: 'object', required: ['name'], properties: { name: { type: 'string' } } },
            },
          },
        },
        responses: {
          201: { description: 'Created' },
          401: { description: 'Unauthorized' },
        },
      },
    },
    '/api/v1/api-keys': {
      get: {
        tags: ['API Keys'],
        summary: 'List API keys',
        operationId: 'listApiKeys',
        security: [{ BearerJWT: [] }],
        parameters: [{ name: 'projectId', in: 'query', schema: { type: 'string', format: 'uuid' } }],
        responses: {
          200: {
            description: 'API key list',
            content: {
              'application/json': {
                schema: {
                  type: 'object',
                  properties: { data: { type: 'array', items: { $ref: '#/components/schemas/ApiKey' } } },
                },
              },
            },
          },
        },
      },
    },
    '/api/v1/api-keys/issue': {
      post: {
        tags: ['API Keys'],
        summary: 'Issue API key',
        description: 'Issues a new Spanlens API key (`sl_live_*`). The raw key is returned exactly once in the response body — Spanlens stores only a SHA-256 hash, so this is the customer\'s only chance to capture it.',
        operationId: 'issueApiKey',
        security: [{ BearerJWT: [] }],
        requestBody: {
          required: true,
          content: {
            'application/json': {
              schema: {
                type: 'object',
                required: ['name', 'projectId'],
                properties: {
                  name: { type: 'string' },
                  projectId: { type: 'string', format: 'uuid' },
                },
              },
            },
          },
        },
        responses: {
          201: {
            description: 'Created — raw key returned once, store it now',
            content: {
              'application/json': {
                schema: {
                  type: 'object',
                  properties: {
                    key: { type: 'string', description: 'Full API key — only shown on creation' },
                    id: { type: 'string', format: 'uuid' },
                  },
                },
              },
            },
          },
        },
      },
    },
    '/api/v1/api-keys/{id}': {
      delete: {
        tags: ['API Keys'],
        summary: 'Revoke API key',
        operationId: 'deleteApiKey',
        security: [{ BearerJWT: [] }],
        parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }],
        responses: {
          204: { description: 'Revoked' },
          404: { description: 'Not found' },
        },
      },
    },
    '/api/v1/requests': {
      get: {
        tags: ['Requests'],
        summary: 'List LLM requests',
        operationId: 'listRequests',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'projectId', in: 'query', schema: { type: 'string', format: 'uuid' } },
          { name: 'provider', in: 'query', schema: { type: 'string', enum: ['openai', 'anthropic', 'gemini', 'azure'] } },
          { name: 'model', in: 'query', schema: { type: 'string' } },
          { name: 'from', in: 'query', schema: { type: 'string', format: 'date-time' } },
          { name: 'to', in: 'query', schema: { type: 'string', format: 'date-time' } },
          { name: 'limit', in: 'query', schema: { type: 'integer', default: 50, maximum: 200 } },
          { name: 'offset', in: 'query', schema: { type: 'integer', default: 0 } },
        ],
        responses: {
          200: {
            description: 'Request log',
            content: {
              'application/json': {
                schema: {
                  type: 'object',
                  properties: {
                    data: { type: 'array', items: { $ref: '#/components/schemas/Request' } },
                    meta: { type: 'object', properties: { total: { type: 'integer' }, limit: { type: 'integer' }, offset: { type: 'integer' } } },
                  },
                },
              },
            },
          },
        },
      },
    },
    '/api/v1/requests/{id}': {
      get: {
        tags: ['Requests'],
        summary: 'Get request detail',
        operationId: 'getRequest',
        security: [{ BearerJWT: [] }],
        parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }],
        responses: {
          200: { description: 'Request detail', content: { 'application/json': { schema: { $ref: '#/components/schemas/Request' } } } },
          404: { description: 'Not found' },
        },
      },
    },
    '/api/v1/users': {
      get: {
        tags: ['Users'],
        summary: 'List per-end-user analytics',
        description: 'Returns one aggregate row per distinct `x-spanlens-user` value within the org — totals (requests / tokens / cost), behaviour (avg latency, error count, distinct models), lifetime markers (first_seen / last_seen).',
        operationId: 'listUsers',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'projectId', in: 'query', schema: { type: 'string', format: 'uuid' } },
          { name: 'search', in: 'query', schema: { type: 'string' }, description: 'Substring match on user_id' },
          { name: 'from', in: 'query', schema: { type: 'string', format: 'date-time' } },
          { name: 'to', in: 'query', schema: { type: 'string', format: 'date-time' } },
          { name: 'sortBy', in: 'query', schema: { type: 'string', enum: ['cost', 'requests', 'tokens', 'last_seen'], default: 'cost' } },
          { name: 'sortDir', in: 'query', schema: { type: 'string', enum: ['asc', 'desc'], default: 'desc' } },
          { name: 'page', in: 'query', schema: { type: 'integer', default: 1 } },
          { name: 'limit', in: 'query', schema: { type: 'integer', default: 50, maximum: 100 } },
        ],
        responses: {
          200: {
            description: 'User analytics list',
            content: {
              'application/json': {
                schema: {
                  type: 'object',
                  properties: {
                    success: { type: 'boolean' },
                    data: { type: 'array', items: { $ref: '#/components/schemas/UserAnalytics' } },
                    meta: { type: 'object', properties: { total: { type: 'integer' }, page: { type: 'integer' }, limit: { type: 'integer' } } },
                  },
                },
              },
            },
          },
        },
      },
    },
    '/api/v1/users/{userId}': {
      get: {
        tags: ['Users'],
        summary: 'Get end-user detail with recent requests',
        description: 'Aggregate stats + most recent 50 requests for a single user_id. Mirrors the list endpoint filters.',
        operationId: 'getUser',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'userId', in: 'path', required: true, schema: { type: 'string' } },
          { name: 'projectId', in: 'query', schema: { type: 'string', format: 'uuid' } },
          { name: 'from', in: 'query', schema: { type: 'string', format: 'date-time' } },
          { name: 'to', in: 'query', schema: { type: 'string', format: 'date-time' } },
        ],
        responses: {
          200: {
            description: 'User detail',
            content: {
              'application/json': {
                schema: {
                  type: 'object',
                  properties: {
                    success: { type: 'boolean' },
                    data: {
                      allOf: [
                        { $ref: '#/components/schemas/UserAnalytics' },
                        {
                          type: 'object',
                          properties: {
                            recent_requests: { type: 'array', items: { $ref: '#/components/schemas/Request' } },
                          },
                        },
                      ],
                    },
                  },
                },
              },
            },
          },
        },
      },
    },
    '/api/v1/stats/overview': {
      get: {
        tags: ['Stats'],
        summary: 'Usage overview (totals)',
        operationId: 'statsOverview',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'projectId', in: 'query', schema: { type: 'string', format: 'uuid' } },
          { name: 'from', in: 'query', schema: { type: 'string', format: 'date-time' } },
          { name: 'to', in: 'query', schema: { type: 'string', format: 'date-time' } },
        ],
        responses: {
          200: {
            description: 'Stats overview',
            content: { 'application/json': { schema: { $ref: '#/components/schemas/StatsOverview' } } },
          },
        },
      },
    },
    '/api/v1/stats/timeseries': {
      get: {
        tags: ['Stats'],
        summary: 'Daily request/cost timeseries',
        operationId: 'statsTimeseries',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'projectId', in: 'query', schema: { type: 'string', format: 'uuid' } },
          { name: 'from', in: 'query', schema: { type: 'string', format: 'date-time' } },
          { name: 'to', in: 'query', schema: { type: 'string', format: 'date-time' } },
        ],
        responses: { 200: { description: 'Timeseries data' } },
      },
    },
    '/api/v1/stats/latency': {
      get: {
        tags: ['Stats'],
        summary: 'Proxy latency percentiles',
        description: 'Returns p50/p95/p99 for provider latency and proxy overhead. Use `overhead.withinSla` to verify p95 overhead < 50ms SLA.',
        operationId: 'statsLatency',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'hours', in: 'query', schema: { type: 'integer', default: 24 }, description: 'Lookback window (max 720h)' },
        ],
        responses: {
          200: {
            description: 'Latency percentiles',
            content: { 'application/json': { schema: { $ref: '#/components/schemas/LatencyStats' } } },
          },
        },
      },
    },
    '/api/v1/traces': {
      get: {
        tags: ['Traces'],
        summary: 'List agent traces',
        operationId: 'listTraces',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'projectId', in: 'query', schema: { type: 'string', format: 'uuid' } },
          { name: 'limit', in: 'query', schema: { type: 'integer', default: 50 } },
          { name: 'offset', in: 'query', schema: { type: 'integer', default: 0 } },
        ],
        responses: {
          200: { description: 'Trace list', content: { 'application/json': { schema: { type: 'object', properties: { data: { type: 'array', items: { $ref: '#/components/schemas/Trace' } } } } } } },
        },
      },
    },
    '/api/v1/traces/{id}': {
      get: {
        tags: ['Traces'],
        summary: 'Get trace + spans',
        operationId: 'getTrace',
        security: [{ BearerJWT: [] }],
        parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }],
        responses: { 200: { description: 'Trace with spans' }, 404: { description: 'Not found' } },
      },
    },
    '/api/v1/anomalies': {
      get: {
        tags: ['Anomalies'],
        summary: 'List detected anomalies',
        description: '3-sigma deviations vs 7-day rolling baseline per model/provider.',
        operationId: 'listAnomalies',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'projectId', in: 'query', schema: { type: 'string', format: 'uuid' } },
          { name: 'hours', in: 'query', schema: { type: 'integer', default: 1 } },
        ],
        responses: {
          200: {
            description: 'Anomalies list',
            content: { 'application/json': { schema: { type: 'object', properties: { data: { type: 'array', items: { $ref: '#/components/schemas/Anomaly' } } } } } },
          },
        },
      },
    },
    '/api/v1/recommendations': {
      get: {
        tags: ['Recommendations'],
        summary: 'Model swap recommendations',
        description: 'Ranked list of cheaper model alternatives based on your actual traffic.',
        operationId: 'listRecommendations',
        security: [{ BearerJWT: [] }],
        responses: {
          200: {
            description: 'Recommendations',
            content: { 'application/json': { schema: { type: 'object', properties: { data: { type: 'array', items: { $ref: '#/components/schemas/ModelRecommendation' } } } } } },
          },
        },
      },
    },
    '/api/v1/security/flagged': {
      get: {
        tags: ['Security'],
        summary: 'List flagged requests',
        description: 'Paginated list of requests (or responses) that matched PII or injection rules. Both `flags` (request) and `response_flags` (LLM output) are included per row.',
        operationId: 'listSecurityFlagged',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'limit', in: 'query', schema: { type: 'integer', default: 50, maximum: 200 } },
          { name: 'offset', in: 'query', schema: { type: 'integer', default: 0 } },
        ],
        responses: {
          200: {
            description: 'Flagged requests',
            content: {
              'application/json': {
                schema: {
                  type: 'object',
                  properties: {
                    data: { type: 'array', items: { $ref: '#/components/schemas/FlaggedRequest' } },
                    meta: { type: 'object', properties: { total: { type: 'integer' }, limit: { type: 'integer' }, offset: { type: 'integer' } } },
                  },
                },
              },
            },
          },
        },
      },
    },
    '/api/v1/security/summary': {
      get: {
        tags: ['Security'],
        summary: 'Security flag summary',
        description: 'Counts by flag type and pattern over a rolling time window.',
        operationId: 'securitySummary',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'hours', in: 'query', schema: { type: 'integer', default: 24, maximum: 720 }, description: 'Look-back window in hours (max 720 = 30 days)' },
        ],
        responses: {
          200: {
            description: 'Flag counts',
            content: {
              'application/json': {
                schema: {
                  type: 'object',
                  properties: {
                    data: { type: 'array', items: { type: 'object', properties: { type: { type: 'string' }, pattern: { type: 'string' }, count: { type: 'integer' } } } },
                    meta: { type: 'object', properties: { hours: { type: 'integer' }, totalFlags: { type: 'integer' } } },
                  },
                },
              },
            },
          },
        },
      },
    },
    '/api/v1/security/settings': {
      get: {
        tags: ['Security'],
        summary: 'Security settings',
        description: 'Returns the org-level alert email toggle and per-project blocking settings.',
        operationId: 'getSecuritySettings',
        security: [{ BearerJWT: [] }],
        responses: {
          200: {
            description: 'Security settings',
            content: {
              'application/json': {
                schema: {
                  type: 'object',
                  properties: {
                    data: {
                      type: 'object',
                      properties: {
                        alertEnabled: { type: 'boolean', description: 'Whether alert emails are sent to the workspace owner on flag detection' },
                        projects: {
                          type: 'array',
                          items: {
                            type: 'object',
                            properties: {
                              id: { type: 'string', format: 'uuid' },
                              name: { type: 'string' },
                              blockEnabled: { type: 'boolean', description: 'When true, injection-flagged requests are blocked (422) before reaching the LLM' },
                            },
                          },
                        },
                      },
                    },
                  },
                },
              },
            },
          },
        },
      },
    },
    '/api/v1/security/alert': {
      patch: {
        tags: ['Security'],
        summary: 'Toggle alert emails',
        description: 'Enable or disable security alert emails for the organization. Emails are sent to the workspace owner, rate-limited to once per 5 minutes.',
        operationId: 'toggleSecurityAlert',
        security: [{ BearerJWT: [] }],
        requestBody: {
          required: true,
          content: { 'application/json': { schema: { type: 'object', required: ['enabled'], properties: { enabled: { type: 'boolean' } } } } },
        },
        responses: {
          200: {
            description: 'Updated',
            content: { 'application/json': { schema: { type: 'object', properties: { data: { type: 'object', properties: { alertEnabled: { type: 'boolean' } } } } } } },
          },
        },
      },
    },
    '/api/v1/security/projects/{projectId}/block': {
      patch: {
        tags: ['Security'],
        summary: 'Toggle per-project injection blocking',
        description: 'When enabled, any request to this project that contains an injection flag is rejected with 422 before reaching the LLM. PII flags are never blocked.',
        operationId: 'toggleProjectBlock',
        security: [{ BearerJWT: [] }],
        parameters: [{ name: 'projectId', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }],
        requestBody: {
          required: true,
          content: { 'application/json': { schema: { type: 'object', required: ['enabled'], properties: { enabled: { type: 'boolean' } } } } },
        },
        responses: {
          200: {
            description: 'Updated',
            content: { 'application/json': { schema: { type: 'object', properties: { data: { type: 'object', properties: { projectId: { type: 'string' }, blockEnabled: { type: 'boolean' } } } } } } },
          },
          404: { description: 'Project not found or does not belong to this org' },
        },
      },
    },
    '/api/v1/organizations/{orgId}/members': {
      get: {
        tags: ['Members'],
        summary: 'List members',
        operationId: 'listMembers',
        security: [{ BearerJWT: [] }],
        parameters: [{ name: 'orgId', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } }],
        responses: {
          200: { description: 'Member list', content: { 'application/json': { schema: { type: 'object', properties: { data: { type: 'array', items: { $ref: '#/components/schemas/Member' } } } } } } },
        },
      },
    },
    '/api/v1/organizations/{orgId}/members/{userId}': {
      patch: {
        tags: ['Members'],
        summary: 'Update member role',
        operationId: 'updateMemberRole',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'orgId', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } },
          { name: 'userId', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } },
        ],
        requestBody: {
          required: true,
          content: { 'application/json': { schema: { type: 'object', required: ['role'], properties: { role: { type: 'string', enum: ['admin', 'editor', 'viewer'] } } } } },
        },
        responses: { 200: { description: 'Updated' }, 403: { description: 'Admin required' } },
      },
      delete: {
        tags: ['Members'],
        summary: 'Remove member',
        operationId: 'removeMember',
        security: [{ BearerJWT: [] }],
        parameters: [
          { name: 'orgId', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } },
          { name: 'userId', in: 'path', required: true, schema: { type: 'string', format: 'uuid' } },
        ],
        responses: { 204: { description: 'Removed' }, 400: { description: 'Cannot remove last admin' } },
      },
    },
    '/proxy/openai/v1/{path}': {
      post: {
        tags: ['Proxy'],
        summary: 'OpenAI passthrough',
        description: 'Drop-in replacement for `https://api.openai.com/v1`. Uses the provider key stored in Settings.',
        operationId: 'proxyOpenai',
        security: [{ ApiKey: [] }],
        parameters: [{ name: 'path', in: 'path', required: true, schema: { type: 'string', example: 'chat/completions' } }],
        requestBody: { description: 'Standard OpenAI request body', required: true, content: { 'application/json': { schema: { type: 'object' } } } },
        responses: { 200: { description: 'Proxied OpenAI response' }, 400: { description: 'No provider key configured' }, 429: { description: 'Quota exceeded' } },
      },
    },
    '/proxy/anthropic/v1/{path}': {
      post: {
        tags: ['Proxy'],
        summary: 'Anthropic passthrough',
        operationId: 'proxyAnthropic',
        security: [{ ApiKey: [] }],
        parameters: [{ name: 'path', in: 'path', required: true, schema: { type: 'string', example: 'messages' } }],
        requestBody: { description: 'Standard Anthropic request body', required: true, content: { 'application/json': { schema: { type: 'object' } } } },
        responses: { 200: { description: 'Proxied Anthropic response' }, 400: { description: 'No provider key configured' } },
      },
    },
    '/proxy/gemini/v1/{path}': {
      post: {
        tags: ['Proxy'],
        summary: 'Gemini passthrough',
        operationId: 'proxyGemini',
        security: [{ ApiKey: [] }],
        parameters: [{ name: 'path', in: 'path', required: true, schema: { type: 'string', example: 'models/gemini-1.5-flash:generateContent' } }],
        requestBody: { description: 'Standard Gemini request body', required: true, content: { 'application/json': { schema: { type: 'object' } } } },
        responses: { 200: { description: 'Proxied Gemini response' }, 400: { description: 'No provider key configured' } },
      },
    },
  },
}

export const openapiRouter = new Hono()

// Serve raw OpenAPI JSON spec
openapiRouter.get('/openapi.json', (c) => {
  return c.json(SPEC)
})

// Swagger UI (CDN, no extra npm deps)
openapiRouter.get('/docs', (c) => {
  const html = `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <title>Spanlens API Docs</title>
  <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css"/>
</head>
<body>
  <div id="swagger-ui"></div>
  <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js" crossorigin></script>
  <script>
    SwaggerUIBundle({
      url: '/api/v1/openapi.json',
      dom_id: '#swagger-ui',
      presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
      layout: 'BaseLayout',
      deepLinking: true,
    })
  </script>
</body>
</html>`
  return c.html(html)
})
