/**
 * KB article version routes — kb-versioning spec (119).
 * Mounted onto kbRouter in apps/zync-api/src/routes/kb.ts.
 *
 * All routes require: authMiddleware, requireModuleEnabled('kb'), requireTier(BUSINESS).
 *
 * GET    /api/kb/articles/:id/versions                      → paginated list (no content)
 * POST   /api/kb/articles/:id/versions                      → create snapshot
 * GET    /api/kb/articles/:id/versions/:versionId           → full version incl content
 * POST   /api/kb/articles/:id/versions/:versionId/restore   → restore article
 */
import { Hono } from 'hono'
import type { Context, Next } from 'hono'
import { z } from 'zod'
import {
  createDb,
  kbTenantQuery,
  createArticleVersion,
  listArticleVersions,
  getArticleVersion,
  restoreArticleVersion,
  getKbSettings,
} from '@zync/db/queries'
import type { AppEnv } from '../../types'
import { requirePermission, requireTier } from '../../middleware/guards'
import { requireModuleEnabled } from '../../middleware/require-module-enabled'
import { authMiddleware } from '../../middleware/auth'
import { TenantTier } from '@zync/types/enums'

// ── helpers ───────────────────────────────────────────────────────────────────

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

function buildPaginated<T>(data: T[], page: number, limit: number, total: number) {
  return {
    data,
    meta: {
      page,
      limit,
      total,
      totalPages: Math.ceil(total / limit),
      hasMore: page * limit < total,
    },
  }
}

const VERSIONING_DISABLED_BODY = {
  error: 'Article versioning is disabled for this workspace',
  code: 'VERSIONING_DISABLED',
} as const

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

export const kbVersionsRouter = new Hono<AppEnv>()

// All version routes: auth + module + tier gate
kbVersionsRouter.use('*', authMiddleware)
kbVersionsRouter.use('*', requireModuleEnabled('kb'))
kbVersionsRouter.use('*', requireTier(TenantTier.BUSINESS))

const kbVersionIdParamSchema = z.object({ versionId: z.string().uuid() })

async function validateKbVersionIdParam(c: Context, next: Next) {
  const parsed = kbVersionIdParamSchema.safeParse({ versionId: c.req.param('versionId') })
  if (!parsed.success) return c.json({ error: 'Invalid version id' }, 400)
  await next()
}

for (const pattern of [
  '/articles/:id/versions/:versionId',
  '/articles/:id/versions/:versionId/*',
]) {
  kbVersionsRouter.use(pattern, validateKbVersionIdParam)
}

// ── GET /articles/:id/versions ────────────────────────────────────────────────

const listVersionsQuerySchema = z.object({
  page: z.coerce.number().int().min(1).default(1),
  limit: z.coerce.number().int().min(1).max(20).default(20),
})

kbVersionsRouter.get('/articles/:id/versions', requirePermission('kb:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const parseResult = listVersionsQuerySchema.safeParse(c.req.query())
  if (!parseResult.success) {
    return c.json({ error: 'Invalid query params', issues: parseResult.error.issues }, 400)
  }

  const { page, limit } = parseResult.data
  const clampedLimit = clampLimit(limit, 20)
  const articleId = c.req.param('id')
  const db = createDb(c.env)
  const q = kbTenantQuery(db, session.tid)

  const kbSettings = await getKbSettings(db, session.tid)
  if (!kbSettings.kb_versioning_enabled) {
    return c.json(buildPaginated([], page, clampedLimit, 0), 200)
  }

  // Verify article belongs to tenant
  const article = await q.getArticleById(articleId)
  if (!article) return c.json({ error: 'Article not found' }, 404)

  const { data, total } = await listArticleVersions(db, {
    articleId,
    tenantId: session.tid,
    page,
    limit: clampedLimit,
  })

  return c.json(buildPaginated(data, page, clampedLimit, total), 200)
})

// ── POST /articles/:id/versions ───────────────────────────────────────────────

const createVersionBodySchema = z.object({
  label: z.string().max(300).nullable().optional(),
})

kbVersionsRouter.post('/articles/:id/versions', requirePermission('kb:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const body = await c.req.json().catch(() => null)
  const parseResult = createVersionBodySchema.safeParse(body ?? {})
  if (!parseResult.success) {
    return c.json({ error: 'Invalid body', issues: parseResult.error.issues }, 400)
  }

  const articleId = c.req.param('id')
  const db = createDb(c.env)
  const q = kbTenantQuery(db, session.tid)

  const kbSettings = await getKbSettings(db, session.tid)
  if (!kbSettings.kb_versioning_enabled) {
    return c.json(VERSIONING_DISABLED_BODY, 409)
  }

  const article = await q.getArticleById(articleId)
  if (!article) return c.json({ error: 'Article not found' }, 404)

  try {
    const version = await createArticleVersion(db, {
      articleId: article.id,
      tenantId: session.tid,
      title: article.title,
      content: article.content,
      label: parseResult.data.label ?? null,
      createdBy: session.sub,
    })
    return c.json(version, 201)
  } catch (err) {
    const pg = err as { code?: string }
    if (pg.code === '23505') {
      // Retry once on race
      const version = await createArticleVersion(db, {
        articleId: article.id,
        tenantId: session.tid,
        title: article.title,
        content: article.content,
        label: parseResult.data.label ?? null,
        createdBy: session.sub,
      })
      return c.json(version, 201)
    }
    throw err
  }
})

// ── GET /articles/:id/versions/:versionId ─────────────────────────────────────

kbVersionsRouter.get('/articles/:id/versions/:versionId', requirePermission('kb:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const articleId = c.req.param('id')
  const versionId = c.req.param('versionId')
  const db = createDb(c.env)
  const q = kbTenantQuery(db, session.tid)

  const kbSettings = await getKbSettings(db, session.tid)
  if (!kbSettings.kb_versioning_enabled) {
    return c.json(VERSIONING_DISABLED_BODY, 409)
  }

  // Verify article belongs to tenant
  const article = await q.getArticleById(articleId)
  if (!article) return c.json({ error: 'Article not found' }, 404)

  const version = await getArticleVersion(db, {
    versionId,
    articleId,
    tenantId: session.tid,
  })

  if (!version) return c.json({ error: 'Version not found' }, 404)

  return c.json(version, 200)
})

// ── POST /articles/:id/versions/:versionId/restore ────────────────────────────

kbVersionsRouter.post(
  '/articles/:id/versions/:versionId/restore',
  requirePermission('kb:write'),
  async (c) => {
    const session = c.get('session')
    if (!session || session.type !== 'user' || !session.tid) {
      return c.json({ error: 'Unauthorized' }, 401)
    }

    const articleId = c.req.param('id')
    const versionId = c.req.param('versionId')
    const db = createDb(c.env)

    const kbSettings = await getKbSettings(db, session.tid)
    if (!kbSettings.kb_versioning_enabled) {
      return c.json(VERSIONING_DISABLED_BODY, 409)
    }

    try {
      const result = await restoreArticleVersion(db, {
        articleId,
        versionId,
        tenantId: session.tid,
        userId: session.sub,
      })
      return c.json(result, 200)
    } catch (err) {
      const typed = err as { status?: number; message?: string }
      if (typed.status === 404) {
        return c.json({ error: typed.message ?? 'Not found' }, 404)
      }
      throw err
    }
  },
)
