import type { FastifyPluginAsync } from 'fastify'
import { PaginationSchema } from '@lunarfront/shared/schemas'
import { VaultKeyService, VaultPermissionService, VaultCategoryService, VaultEntryService } from '../../services/vault.service.js'
import { ValidationError } from '../../lib/errors.js'

export const vaultRoutes: FastifyPluginAsync = async (app) => {

  // --- Vault Status & Key Management ---

  app.get('/vault/status', { preHandler: [app.authenticate, app.requirePermission('vault.view')] }, async (request, reply) => {
    const initialized = await VaultKeyService.isInitialized(app.db)
    const unlocked = VaultKeyService.isUnlocked()
    return reply.send({ initialized, unlocked })
  })

  app.post('/vault/initialize', { preHandler: [app.authenticate, app.requirePermission('vault.admin')] }, async (request, reply) => {
    const { masterPassword } = request.body as { masterPassword?: string }
    if (!masterPassword || masterPassword.length < 8) throw new ValidationError('Master password must be at least 8 characters')

    try {
      await VaultKeyService.initialize(app.db, masterPassword)
      return reply.status(201).send({ message: 'Vault initialized and unlocked' })
    } catch (err: any) {
      return reply.status(400).send({ error: { message: err.message, statusCode: 400 } })
    }
  })

  app.post('/vault/unlock', { preHandler: [app.authenticate, app.requirePermission('vault.view')] }, async (request, reply) => {
    const { masterPassword } = request.body as { masterPassword?: string }
    if (!masterPassword) throw new ValidationError('Master password is required')

    const success = await VaultKeyService.unlock(app.db, masterPassword)
    if (!success) {
      return reply.status(401).send({ error: { message: 'Invalid master password', statusCode: 401 } })
    }
    return reply.send({ message: 'Vault unlocked' })
  })

  app.post('/vault/lock', { preHandler: [app.authenticate, app.requirePermission('vault.admin')] }, async (request, reply) => {
    VaultKeyService.lock()
    return reply.send({ message: 'Vault locked' })
  })

  app.post('/vault/change-master-password', { preHandler: [app.authenticate, app.requirePermission('vault.admin')] }, async (request, reply) => {
    const { currentPassword, newPassword } = request.body as { currentPassword?: string; newPassword?: string }
    if (!currentPassword || !newPassword) throw new ValidationError('Both current and new passwords are required')
    if (newPassword.length < 8) throw new ValidationError('New password must be at least 8 characters')

    try {
      await VaultKeyService.changeMasterPassword(app.db, currentPassword, newPassword)
      return reply.send({ message: 'Master password changed' })
    } catch (err: any) {
      return reply.status(400).send({ error: { message: err.message, statusCode: 400 } })
    }
  })

  // --- Middleware: require vault unlocked ---
  function requireUnlocked(_request: any, reply: any, done: () => void) {
    if (!VaultKeyService.isUnlocked()) {
      reply.status(403).send({ error: { message: 'Vault is locked', statusCode: 403 } })
      return
    }
    done()
  }

  // --- Categories ---

  app.post('/vault/categories', { preHandler: [app.authenticate, app.requirePermission('vault.edit'), requireUnlocked] }, async (request, reply) => {
    const { name, description, isPublic } = request.body as { name?: string; description?: string; isPublic?: boolean }
    if (!name?.trim()) throw new ValidationError('Category name is required')

    const category = await VaultCategoryService.create(app.db, { name: name.trim(), description, isPublic, createdBy: request.user.id })
    return reply.status(201).send(category)
  })

  app.get('/vault/categories', { preHandler: [app.authenticate, app.requirePermission('vault.view'), requireUnlocked] }, async (request, reply) => {
    const categories = await VaultCategoryService.listAccessible(app.db, request.user.id)
    // Include accessLevel for each category so the UI knows what actions to show
    const withAccess = await Promise.all(categories.map(async (cat) => {
      const accessLevel = await VaultPermissionService.getAccessLevel(app.db, cat.id, request.user.id)
      return { ...cat, accessLevel }
    }))
    return reply.send({ data: withAccess })
  })

  app.get('/vault/categories/:id', { preHandler: [app.authenticate, app.requirePermission('vault.view'), requireUnlocked] }, async (request, reply) => {
    const { id } = request.params as { id: string }
    const category = await VaultCategoryService.getById(app.db, id)
    if (!category) return reply.status(404).send({ error: { message: 'Category not found', statusCode: 404 } })

    const accessLevel = await VaultPermissionService.getAccessLevel(app.db, id, request.user.id)
    if (!accessLevel) return reply.status(403).send({ error: { message: 'Access denied', statusCode: 403 } })

    return reply.send({ ...category, accessLevel })
  })

  app.patch('/vault/categories/:id', { preHandler: [app.authenticate, app.requirePermission('vault.edit'), requireUnlocked] }, async (request, reply) => {
    const { id } = request.params as { id: string }
    const { name, description, isPublic } = request.body as { name?: string; description?: string; isPublic?: boolean }

    const hasEdit = await VaultPermissionService.hasAccess(app.db, id, request.user.id, 'edit')
    if (!hasEdit) return reply.status(403).send({ error: { message: 'No edit access', statusCode: 403 } })

    const category = await VaultCategoryService.update(app.db, id, { name, description, isPublic })
    if (!category) return reply.status(404).send({ error: { message: 'Category not found', statusCode: 404 } })
    return reply.send(category)
  })

  app.delete('/vault/categories/:id', { preHandler: [app.authenticate, app.requirePermission('vault.admin'), requireUnlocked] }, async (request, reply) => {
    const { id } = request.params as { id: string }
    const hasAdmin = await VaultPermissionService.hasAccess(app.db, id, request.user.id, 'admin')
    if (!hasAdmin) return reply.status(403).send({ error: { message: 'Admin access required', statusCode: 403 } })

    const category = await VaultCategoryService.delete(app.db, id)
    if (!category) return reply.status(404).send({ error: { message: 'Category not found', statusCode: 404 } })
    return reply.send(category)
  })

  // --- Category Permissions ---

  app.get('/vault/categories/:id/permissions', { preHandler: [app.authenticate, app.requirePermission('vault.view'), requireUnlocked] }, async (request, reply) => {
    const { id } = request.params as { id: string }
    const hasAdmin = await VaultPermissionService.hasAccess(app.db, id, request.user.id, 'admin')
    if (!hasAdmin) return reply.status(403).send({ error: { message: 'Admin access required', statusCode: 403 } })
    const permissions = await VaultPermissionService.listPermissions(app.db, id)
    return reply.send({ data: permissions })
  })

  app.post('/vault/categories/:id/permissions', { preHandler: [app.authenticate, app.requirePermission('vault.edit'), requireUnlocked] }, async (request, reply) => {
    const { id } = request.params as { id: string }
    const { roleId, userId, accessLevel } = request.body as { roleId?: string; userId?: string; accessLevel?: string }

    const hasAdmin = await VaultPermissionService.hasAccess(app.db, id, request.user.id, 'admin')
    if (!hasAdmin) return reply.status(403).send({ error: { message: 'Admin access required', statusCode: 403 } })

    if (!roleId && !userId) throw new ValidationError('Either roleId or userId is required')
    if (!accessLevel || !['view', 'edit', 'admin'].includes(accessLevel)) throw new ValidationError('accessLevel must be view, edit, or admin')

    const perm = await VaultPermissionService.setPermission(app.db, id, roleId, userId, accessLevel)
    return reply.status(201).send(perm)
  })

  app.delete('/vault/category-permissions/:id', { preHandler: [app.authenticate, app.requirePermission('vault.admin'), requireUnlocked] }, async (request, reply) => {
    const { id } = request.params as { id: string }
    const perm = await VaultPermissionService.getPermissionById(app.db, id)
    if (!perm) return reply.status(404).send({ error: { message: 'Permission not found', statusCode: 404 } })

    const hasAdmin = await VaultPermissionService.hasAccess(app.db, perm.categoryId, request.user.id, 'admin')
    if (!hasAdmin) return reply.status(403).send({ error: { message: 'Admin access required', statusCode: 403 } })

    await VaultPermissionService.removePermission(app.db, id)
    return reply.send(perm)
  })

  // --- Entries ---

  app.post('/vault/categories/:categoryId/entries', { preHandler: [app.authenticate, app.requirePermission('vault.edit'), requireUnlocked] }, async (request, reply) => {
    const { categoryId } = request.params as { categoryId: string }
    const { name, username, url, notes, secret } = request.body as {
      name?: string; username?: string; url?: string; notes?: string; secret?: string
    }

    if (!name?.trim()) throw new ValidationError('Entry name is required')

    const hasEdit = await VaultPermissionService.hasAccess(app.db, categoryId, request.user.id, 'edit')
    if (!hasEdit) return reply.status(403).send({ error: { message: 'No edit access', statusCode: 403 } })

    const entry = await VaultEntryService.create(app.db, {
      categoryId, name: name.trim(), username, url, notes, createdBy: request.user.id,
    }, secret)

    // Don't return encrypted value
    return reply.status(201).send({
      id: entry.id, categoryId: entry.categoryId, name: entry.name,
      username: entry.username, url: entry.url, notes: entry.notes,
      hasSecret: !!entry.encryptedValue,
      createdBy: entry.createdBy, updatedBy: entry.updatedBy,
      createdAt: entry.createdAt, updatedAt: entry.updatedAt,
    })
  })

  app.get('/vault/categories/:categoryId/entries', { preHandler: [app.authenticate, app.requirePermission('vault.view'), requireUnlocked] }, async (request, reply) => {
    const { categoryId } = request.params as { categoryId: string }

    const hasView = await VaultPermissionService.hasAccess(app.db, categoryId, request.user.id, 'view')
    if (!hasView) return reply.status(403).send({ error: { message: 'Access denied', statusCode: 403 } })

    const params = PaginationSchema.parse(request.query)
    const result = await VaultEntryService.listByCategory(app.db, categoryId, params)
    return reply.send(result)
  })

  app.get('/vault/entries/:id', { preHandler: [app.authenticate, app.requirePermission('vault.view'), requireUnlocked] }, async (request, reply) => {
    const { id } = request.params as { id: string }
    const entry = await VaultEntryService.getById(app.db, id)
    if (!entry) return reply.status(404).send({ error: { message: 'Entry not found', statusCode: 404 } })

    const hasView = await VaultPermissionService.hasAccess(app.db, entry.categoryId, request.user.id, 'view')
    if (!hasView) return reply.status(403).send({ error: { message: 'Access denied', statusCode: 403 } })

    return reply.send({
      id: entry.id, categoryId: entry.categoryId, name: entry.name,
      username: entry.username, url: entry.url, notes: entry.notes,
      hasSecret: !!entry.encryptedValue,
      createdBy: entry.createdBy, updatedBy: entry.updatedBy,
      createdAt: entry.createdAt, updatedAt: entry.updatedAt,
    })
  })

  app.post('/vault/entries/:id/reveal', { preHandler: [app.authenticate, app.requirePermission('vault.edit'), requireUnlocked] }, async (request, reply) => {
    const { id } = request.params as { id: string }
    const entry = await VaultEntryService.getById(app.db, id)
    if (!entry) return reply.status(404).send({ error: { message: 'Entry not found', statusCode: 404 } })

    const hasEdit = await VaultPermissionService.hasAccess(app.db, entry.categoryId, request.user.id, 'edit')
    if (!hasEdit) return reply.status(403).send({ error: { message: 'Edit access required to reveal secrets', statusCode: 403 } })

    const value = await VaultEntryService.reveal(id, app.db)
    return reply.send({ value })
  })

  app.patch('/vault/entries/:id', { preHandler: [app.authenticate, app.requirePermission('vault.edit'), requireUnlocked] }, async (request, reply) => {
    const { id } = request.params as { id: string }
    const { name, username, url, notes, secret } = request.body as {
      name?: string; username?: string; url?: string; notes?: string; secret?: string
    }

    const entry = await VaultEntryService.getById(app.db, id)
    if (!entry) return reply.status(404).send({ error: { message: 'Entry not found', statusCode: 404 } })

    const hasEdit = await VaultPermissionService.hasAccess(app.db, entry.categoryId, request.user.id, 'edit')
    if (!hasEdit) return reply.status(403).send({ error: { message: 'No edit access', statusCode: 403 } })

    const updated = await VaultEntryService.update(app.db, id, {
      name, username, url, notes, updatedBy: request.user.id,
    }, secret)

    return reply.send({
      id: updated!.id, categoryId: updated!.categoryId, name: updated!.name,
      username: updated!.username, url: updated!.url, notes: updated!.notes,
      hasSecret: !!updated!.encryptedValue,
      createdBy: updated!.createdBy, updatedBy: updated!.updatedBy,
      createdAt: updated!.createdAt, updatedAt: updated!.updatedAt,
    })
  })

  app.delete('/vault/entries/:id', { preHandler: [app.authenticate, app.requirePermission('vault.edit'), requireUnlocked] }, async (request, reply) => {
    const { id } = request.params as { id: string }
    const entry = await VaultEntryService.getById(app.db, id)
    if (!entry) return reply.status(404).send({ error: { message: 'Entry not found', statusCode: 404 } })

    const hasEdit = await VaultPermissionService.hasAccess(app.db, entry.categoryId, request.user.id, 'edit')
    if (!hasEdit) return reply.status(403).send({ error: { message: 'No edit access', statusCode: 403 } })

    await VaultEntryService.delete(app.db, id)
    return reply.send(entry)
  })

  app.get('/vault/entries/search', { preHandler: [app.authenticate, app.requirePermission('vault.view'), requireUnlocked] }, async (request, reply) => {
    const { q } = request.query as { q?: string }
    if (!q) return reply.send({ data: [], pagination: { page: 1, limit: 25, total: 0, totalPages: 0 } })

    const params = PaginationSchema.parse(request.query)
    const result = await VaultEntryService.search(app.db, q, params)

    // Filter by access
    const filtered = []
    for (const entry of result.data) {
      const hasView = await VaultPermissionService.hasAccess(app.db, (entry as any).categoryId, request.user.id, 'view')
      if (hasView) filtered.push(entry)
    }

    return reply.send({ data: filtered, pagination: { ...result.pagination, total: filtered.length } })
  })
}
