/**
 * Custom roles API — settings-roles (wave 11).
 * Mounted at /api/roles.
 *
 * GET    /         → CustomRoleDTO[] (requires users:manage)
 * POST   /         → 201 CustomRoleDTO (requires users:manage)
 * PATCH  /:id      → 200 CustomRoleDTO (requires users:manage)
 * DELETE /:id      → 200 { ok: true } (requires users:manage)
 *
 * System roles (is_system_role=true) are returned in GET but are
 * immutable — PATCH/DELETE on them returns 403.
 *
 * No raw Drizzle in this file — all DB access via @zync/db/queries.
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../types'
import { authMiddleware } from '../middleware/auth'
import { requirePermission } from '../middleware/guards'
import {
  listCustomRoles,
  createCustomRole,
  updateCustomRole,
  deleteCustomRole,
  TENANT_PERMISSION_VOCABULARY,
  SystemRoleImmutableError,
  RoleNameConflictError,
  RoleHasMembersError,
  RoleNotFoundError,
  UnknownPermissionKeyError,
} from '@zync/db/queries'

// ── Validation ────────────────────────────────────────────────────────────────

const permissionsArraySchema = z
  .array(z.string())
  .refine(
    (perms) =>
      perms.every((p) =>
        (TENANT_PERMISSION_VOCABULARY as readonly string[]).includes(p),
      ),
    { message: 'Contains unknown permission keys' },
  )

const roleNameSchema = z.string().trim().min(1).max(64)

const createRoleSchema = z.object({
  name: roleNameSchema,
  permissions: permissionsArraySchema,
})

const updateRoleSchema = z
  .object({
    name: roleNameSchema.optional(),
    permissions: permissionsArraySchema.optional(),
  })
  .refine(
    (b) => b.name !== undefined || b.permissions !== undefined,
    { message: 'At least one field (name or permissions) must be provided' },
  )

const deleteRoleSchema = z.object({
  reassign_to_role_id: z.string().uuid().optional(),
})

function permissionsExceedActor(
  requested: readonly string[],
  actorPermissions: readonly string[],
): boolean {
  const actorPerms = new Set(actorPermissions)
  return !requested.every((p) => actorPerms.has(p))
}

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

export const rolesRoute = new Hono<AppEnv>()

rolesRoute.use('*', authMiddleware)

// ── GET /api/roles ────────────────────────────────────────────────────────────

rolesRoute.get('/', requirePermission('users:manage'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const db = c.get('db')
  const list = await listCustomRoles(db, session.tid)
  return c.json(list, 200)
})

// ── POST /api/roles ───────────────────────────────────────────────────────────

rolesRoute.post('/', requirePermission('users:manage'), 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 parsed = createRoleSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)
  }

  // Anti-escalation: cannot define permissions beyond own set
  if (permissionsExceedActor(parsed.data.permissions, session.permissions)) {
    return c.json({ error: 'Cannot grant a role exceeding your own permissions' }, 403)
  }

  const db = c.get('db')

  try {
    const role = await createCustomRole(
      db,
      session.tid,
      session.sub,
      parsed.data.name,
      parsed.data.permissions,
    )
    return c.json(role, 201)
  } catch (err) {
    if (err instanceof RoleNameConflictError) {
      return c.json({ error: 'A role with this name already exists' }, 409)
    }
    if (err instanceof UnknownPermissionKeyError) {
      return c.json({ error: err.message }, 400)
    }
    throw err
  }
})

// ── PATCH /api/roles/:id ──────────────────────────────────────────────────────

rolesRoute.patch('/:id', requirePermission('users:manage'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const { id } = c.req.param()

  const body = await c.req.json().catch(() => null)
  const parsed = updateRoleSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)
  }

  // Anti-escalation: cannot define permissions beyond own set
  if (
    parsed.data.permissions !== undefined &&
    permissionsExceedActor(parsed.data.permissions, session.permissions)
  ) {
    return c.json({ error: 'Cannot grant a role exceeding your own permissions' }, 403)
  }

  const db = c.get('db')

  try {
    const updated = await updateCustomRole(db, session.tid, session.sub, id, parsed.data)
    return c.json(updated, 200)
  } catch (err) {
    if (err instanceof RoleNotFoundError) {
      // Return 404 for any tenant isolation failure (cross-tenant ids look like not-found)
      return c.json({ error: 'Not found' }, 404)
    }
    if (err instanceof SystemRoleImmutableError) {
      return c.json({ error: 'System roles cannot be modified' }, 403)
    }
    if (err instanceof RoleNameConflictError) {
      return c.json({ error: 'A role with this name already exists' }, 409)
    }
    if (err instanceof UnknownPermissionKeyError) {
      return c.json({ error: err.message }, 400)
    }
    throw err
  }
})

// ── DELETE /api/roles/:id ─────────────────────────────────────────────────────

rolesRoute.delete('/:id', requirePermission('users:manage'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const { id } = c.req.param()

  const body = await c.req.json().catch(() => ({}))
  const parsed = deleteRoleSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)
  }

  const db = c.get('db')

  try {
    await deleteCustomRole(db, session.tid, session.sub, id, parsed.data.reassign_to_role_id)
    return c.json({ ok: true }, 200)
  } catch (err) {
    if (err instanceof RoleNotFoundError) {
      return c.json({ error: 'Not found' }, 404)
    }
    if (err instanceof SystemRoleImmutableError) {
      return c.json({ error: 'System roles cannot be deleted' }, 403)
    }
    if (err instanceof RoleHasMembersError) {
      return c.json(
        { error: 'reassign_to_role_id required', member_count: err.memberCount },
        400,
      )
    }
    throw err
  }
})
