/**
 * Team member management routes — team-users-settings (wave-10 leaf 2).
 * Mounted at /api/users/team (behind authMiddleware in users/index.ts).
 *
 * GET    /team           → list all tenant members + pending invites
 * DELETE /team/:id       → remove a member
 * PATCH  /team/:id/role  → change a member's role
 * POST   /team/transfer  → transfer ownership to another member (owner only)
 * DELETE /team/invites/:id → revoke a pending invite
 *
 * Access: owner/admin for mutations; all authenticated for list.
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../../types'
import {
  createDb,
  listTeamMembers,
  listPendingInvites,
  updateMemberRole,
  updateUserHourlyCost,
  removeTenantMember,
  revokePendingInvite,
  approvePendingMember,
  unfreezeTenantMember,
  freezeMembership,
  getRoleById,
  getRoleByName,
  getPermissionsForRole,
  listTenantRolesAdmin,
} from '@zync/db/queries'
import type { TenantId, UserId } from '@zync/types'
import { requirePermission } from '../../middleware/guards'
import {
  isOwnerRole,
  MEMBER_ROLE,
  OWNER_ROLE,
} from '../../lib/system-roles'
import {
  guardDeleteTeamMember,
  guardPatchTeamMemberRole,
} from '../../lib/team-member-guards'
import { bumpUserVersion } from '../../middleware/user-version'

// ── Zod schemas ───────────────────────────────────────────────────────────────

const changeRoleSchema = z.object({
  roleId: z.string().uuid(),
})

const transferOwnershipSchema = z.object({
  targetUserId: z.string().uuid(),
})

const updateHourlyCostSchema = z.object({
  hourlyCost: z.union([z.string().regex(/^\d+(\.\d{1,2})?$/), z.null()]),
})

const freezeMemberSchema = z.object({
  reason: z.string().trim().min(1),
})

const rejectPendingMemberSchema = z.object({
  notify: z.boolean().optional(),
  reason: z.string().trim().optional(),
})

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

export const teamRoute = new Hono<AppEnv>()

// GET /team/roles — list tenant roles (for invite/role-change selects)
teamRoute.get('/team/roles', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const db = createDb(c.env)
  const roles = await listTenantRolesAdmin(db, session.tid)
  return c.json({
    roles: roles.map((r) => ({ id: r.id, name: r.name })),
  }, 200)
})

// GET /team — list members + pending invites
teamRoute.get('/team', 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 = createDb(c.env)
  const [members, invites] = await Promise.all([
    listTeamMembers(db, session.tid),
    listPendingInvites(db, session.tid),
  ])

  return c.json({ members, invites }, 200)
})

// DELETE /team/:id — remove a member
teamRoute.delete('/team/: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 targetUserId = c.req.param('id')
  if (targetUserId === session.sub) {
    return c.json({ error: 'Cannot remove yourself' }, 400)
  }

  const db = createDb(c.env)
  const members = await listTeamMembers(db, session.tid)
  const target = members.find((m) => m.id === targetUserId)
  const guard = guardDeleteTeamMember({
    actorUserId: session.sub,
    targetUserId,
    targetMember: target,
  })
  if (!guard.ok) {
    return c.json({ error: guard.error }, guard.status)
  }

  await removeTenantMember(
    db,
    session.tid as TenantId,
    targetUserId as UserId,
    session.sub as UserId,
    {
      actorIp: c.req.header('CF-Connecting-IP') ?? null,
      requestId: c.req.header('CF-Ray') ?? null,
    },
  )

  return c.json({ ok: true }, 200)
})

// PATCH /team/:id/role — change a member's role
teamRoute.patch('/team/:id/role', requirePermission('users:manage'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const targetUserId = c.req.param('id')
  const body = await c.req.json().catch(() => null)
  const parsed = changeRoleSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
  }
  const { roleId } = parsed.data

  const db = createDb(c.env)

  // Fetch current membership to guard owner invariant
  const members = await listTeamMembers(db, session.tid)
  const target = members.find((m) => m.id === targetUserId)

  // Verify role belongs to this tenant
  const role = await getRoleById(db, roleId)
  if (!role || role.tenantId !== session.tid) {
    return c.json({ error: 'Invalid role' }, 400)
  }

  const guard = guardPatchTeamMemberRole({
    targetMember: target,
    newRoleName: role.name,
  })
  if (!guard.ok) {
    return c.json({ error: guard.error }, guard.status)
  }

  // Anti-escalation: cannot grant role with permissions beyond own
  const grantedPerms = await getPermissionsForRole(db, roleId)
  const actorPerms = new Set(session.permissions)
  if (!grantedPerms.every((p) => actorPerms.has(p))) {
    return c.json({ error: 'Cannot grant a role exceeding your own permissions' }, 403)
  }

  await updateMemberRole(
    db,
    session.tid as TenantId,
    targetUserId as UserId,
    roleId,
    session.sub as UserId,
    {
      actorIp: c.req.header('CF-Connecting-IP') ?? null,
      requestId: c.req.header('CF-Ray') ?? null,
    },
  )

  return c.json({ ok: true }, 200)
})

teamRoute.patch('/team/:id/hourly-cost', requirePermission('users:invite'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!isOwnerRole(session.role)) {
    return c.json({ error: 'Forbidden — OWNER only' }, 403)
  }

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

  await updateUserHourlyCost(createDb(c.env), c.req.param('id'), parsed.data.hourlyCost)
  return c.json({ ok: true }, 200)
})

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

  const userId = c.req.param('id')
  const db = createDb(c.env)
  const approved = await approvePendingMember(db, session.tid, userId)
  if (!approved) {
    return c.json({ error: 'Pending member not found' }, 404)
  }

  await bumpUserVersion(c.env, userId)
  return c.json({ ok: true }, 200)
})

teamRoute.post('/team/:id/reject', 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(() => ({}))
  const parsed = rejectPendingMemberSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
  }
  void parsed.data

  const targetUserId = c.req.param('id')
  await removeTenantMember(
    createDb(c.env),
    session.tid as TenantId,
    targetUserId as UserId,
    session.sub as UserId,
    {
      actorIp: c.req.header('CF-Connecting-IP') ?? null,
      requestId: c.req.header('CF-Ray') ?? null,
    },
  )

  return c.json({ ok: true }, 200)
})

teamRoute.post('/team/:id/freeze', requirePermission('users:freeze'), 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 = freezeMemberSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Invalid request', issues: parsed.error.issues }, 400)
  }

  const userId = c.req.param('id')
  const db = createDb(c.env)
  await freezeMembership(db, {
    tenantId: session.tid as TenantId,
    targetUserId: userId as UserId,
    reason: parsed.data.reason,
    actorId: session.sub as UserId,
    actorIp: c.req.header('CF-Connecting-IP') ?? null,
    requestId: c.req.header('CF-Ray') ?? null,
  })
  await bumpUserVersion(c.env, userId)

  return c.json({ ok: true }, 200)
})

teamRoute.post('/team/:id/unfreeze', requirePermission('users:freeze'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const userId = c.req.param('id')
  const unfrozen = await unfreezeTenantMember(createDb(c.env), session.tid, userId)
  if (!unfrozen) {
    return c.json({ error: 'Frozen member not found' }, 404)
  }

  await bumpUserVersion(c.env, userId)
  return c.json({ ok: true }, 200)
})

// POST /team/transfer — transfer ownership
teamRoute.post('/team/transfer', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  // Only owner can transfer
  if (!isOwnerRole(session.role)) {
    return c.json({ error: 'Only the owner can transfer ownership' }, 403)
  }

  const body = await c.req.json().catch(() => null)
  const parsed = transferOwnershipSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Invalid request' }, 400)
  }
  const { targetUserId } = parsed.data

  if (targetUserId === session.sub) {
    return c.json({ error: 'Already the owner' }, 400)
  }

  const db = createDb(c.env)

  const ownerRole = await getRoleByName(db, session.tid as TenantId, OWNER_ROLE)
  const memberRole = await getRoleByName(db, session.tid as TenantId, MEMBER_ROLE)
  if (!ownerRole || !memberRole) {
    return c.json({ error: 'Required roles not found' }, 500)
  }

  // Grant owner role to target
  await updateMemberRole(
    db,
    session.tid as TenantId,
    targetUserId as UserId,
    ownerRole.id,
    session.sub as UserId,
    {
      actorIp: c.req.header('CF-Connecting-IP') ?? null,
      requestId: c.req.header('CF-Ray') ?? null,
    },
  )

  // Demote self to member
  await updateMemberRole(
    db,
    session.tid as TenantId,
    session.sub as UserId,
    memberRole.id,
    session.sub as UserId,
    {
      actorIp: c.req.header('CF-Connecting-IP') ?? null,
      requestId: c.req.header('CF-Ray') ?? null,
    },
  )

  return c.json({ ok: true }, 200)
})

// DELETE /team/invites/:id — revoke pending invite
teamRoute.delete('/team/invites/: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 inviteId = c.req.param('id')
  const db = createDb(c.env)
  await revokePendingInvite(db, session.tid, inviteId)

  return c.json({ ok: true }, 200)
})
