/**
 * GET /api/search            — palette search endpoint (search-completeness §4.1)
 * GET /api/search/full       — full results endpoint (search-completeness §4.2)
 *
 * Replaces the app-shell stub in routes/search.ts with full implementation.
 * Auth: zync_session cookie / Bearer JWT (authMiddleware).
 * Rate limit: 60 req/min per user → 429 { error: 'rate_limited' }.
 * Tenant + user + role + permissions extracted from session by authMiddleware.
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../types'
import { authMiddleware } from '../middleware/auth'
import { searchEntities } from '@zync/db/search'
import type { EntityType, SearchResponse, FullSearchResponse } from '@zync/types'
import { ENTITY_PRIORITY } from '@zync/types'

// ── Validation schemas ────────────────────────────────────────────────────────

const ENTITY_TYPES = ENTITY_PRIORITY

const paletteQuerySchema = z.object({
  q: z.string().min(1).max(201),
  limit: z.coerce.number().int().min(1).max(10).optional().default(5),
})

const fullQuerySchema = z.object({
  q: z.string().min(1).max(201),
  type: z.enum(ENTITY_TYPES as [EntityType, ...EntityType[]]).optional(),
  cursor: z.string().optional(),
  limit: z.coerce.number().int().min(1).max(50).optional().default(20),
})

// ── Rate limiter ──────────────────────────────────────────────────────────────
// 60 req/min per user via the Cloudflare RATE_LIMITER_SEARCH binding. An in-memory
// Map is useless on Workers — each isolate has its own, so the limit never holds
// across the fleet. The binding is cross-isolate consistent.

async function checkRateLimit(env: AppEnv['Bindings'], userId: string): Promise<boolean> {
  const { success } = await (async () => { try { const _r = await env.RATE_LIMITER_SEARCH?.limit({ key: `search:${userId}` }); return _r ?? { success: true }; } catch { return { success: true }; } })()
  return success
}

// ── Helper ────────────────────────────────────────────────────────────────────

function clampLimit(val: number, min: number, max: number): number {
  return Math.max(min, Math.min(max, val))
}

// ── Router ────────────────────────────────────────────────────────────────────

export const searchCompletenessRoute = new Hono<AppEnv>()

searchCompletenessRoute.use('*', authMiddleware)

// ── GET /search — palette endpoint ───────────────────────────────────────────

searchCompletenessRoute.get('/', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'unauthenticated' }, 401)
  }

  const userId = session.sub as string
  if (!(await checkRateLimit(c.env, userId))) {
    return c.json({ error: 'rate_limited' }, 429)
  }

  const parsed = paletteQuerySchema.safeParse({
    q: c.req.query('q'),
    limit: c.req.query('limit'),
  })

  if (!parsed.success) {
    const q = c.req.query('q') ?? ''
    if (q.length < 2) return c.json({ error: 'query_too_short' }, 400)
    if (q.length > 200) return c.json({ error: 'query_too_long' }, 400)
    return c.json({ error: 'invalid_request' }, 400)
  }

  const { q, limit } = parsed.data
  const trimmed = q.trim()
  if (trimmed.length < 2) return c.json({ error: 'query_too_short' }, 400)
  if (trimmed.length > 200) return c.json({ error: 'query_too_long' }, 400)

  const db = c.get('db')
  const { groups, totalGroups } = await searchEntities({
    db,
    tenantId: session.tid,
    userId,
    role: session.role,
    permissions: session.permissions,
    query: trimmed,
    limit: clampLimit(limit, 1, 10),
    types: null,
    cursor: null,
  })

  const response: SearchResponse = {
    query: trimmed,
    groups,
    total_groups: totalGroups,
  }

  return c.json(response)
})

// ── GET /search/full — full results endpoint ─────────────────────────────────

searchCompletenessRoute.get('/full', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'unauthenticated' }, 401)
  }

  const userId = session.sub as string
  if (!(await checkRateLimit(c.env, userId))) {
    return c.json({ error: 'rate_limited' }, 429)
  }

  const parsed = fullQuerySchema.safeParse({
    q: c.req.query('q'),
    type: c.req.query('type') || undefined,
    cursor: c.req.query('cursor') || undefined,
    limit: c.req.query('limit'),
  })

  if (!parsed.success) {
    const q = c.req.query('q') ?? ''
    if (q.length < 2) return c.json({ error: 'query_too_short' }, 400)
    if (q.length > 200) return c.json({ error: 'query_too_long' }, 400)
    // Check if type is invalid
    const rawType = c.req.query('type')
    if (rawType && !ENTITY_PRIORITY.includes(rawType as EntityType)) {
      return c.json({ error: 'invalid_type' }, 400)
    }
    return c.json({ error: 'invalid_request' }, 400)
  }

  const { q, type, cursor, limit } = parsed.data
  const trimmed = q.trim()
  if (trimmed.length < 2) return c.json({ error: 'query_too_short' }, 400)
  if (trimmed.length > 200) return c.json({ error: 'query_too_long' }, 400)

  const db = c.get('db')
  const { groups, nextCursor, hasMore } = await searchEntities({
    db,
    tenantId: session.tid,
    userId,
    role: session.role,
    permissions: session.permissions,
    query: trimmed,
    limit: clampLimit(limit, 1, 50),
    types: type ? [type] : null,
    cursor: cursor ?? null,
  })

  const response: FullSearchResponse = {
    query: trimmed,
    type: type ?? null,
    groups,
    next_cursor: nextCursor,
    has_more: hasMore,
  }

  return c.json(response)
})
