import { NextResponse } from "next/server"

import { hashPassword } from "better-auth/crypto"
import { ConvexHttpClient } from "convex/browser"
import type { UserRole } from "@/lib/prisma"
import type { Id } from "@/convex/_generated/dataModel"

import { prisma } from "@/lib/prisma"
import { DEFAULT_TENANT_ID } from "@/lib/constants"
import { assertAdminSession, assertStaffSession } from "@/lib/auth-server"
import { isAdmin } from "@/lib/authz"
import { api } from "@/convex/_generated/api"

export const runtime = "nodejs"

const ALLOWED_ROLES = ["MANAGER", "COLLABORATOR"] as const

type AllowedRole = (typeof ALLOWED_ROLES)[number]

function normalizeRole(role?: string | null): AllowedRole {
  const normalized = (role ?? "COLLABORATOR").toUpperCase()
  return ALLOWED_ROLES.includes(normalized as AllowedRole) ? (normalized as AllowedRole) : "COLLABORATOR"
}

function generatePassword(length = 12) {
  const charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%&*?"
  let password = ""
  const array = new Uint32Array(length)
  crypto.getRandomValues(array)
  for (let index = 0; index < length; index += 1) {
    password += charset[array[index] % charset.length]
  }
  return password
}

export async function GET() {
  const session = await assertStaffSession()
  if (!session) {
    return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
  }

  const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID

  const users = await prisma.user.findMany({
    where: {
      tenantId,
      role: { in: [...ALLOWED_ROLES] },
    },
    include: {
      company: {
        select: {
          id: true,
          name: true,
        },
      },
      manager: {
        select: {
          id: true,
          name: true,
          email: true,
        },
      },
    },
    orderBy: { createdAt: "desc" },
  })

  const emails = users.map((user) => user.email)
  const authUsers = await prisma.authUser.findMany({
    where: { email: { in: emails } },
    select: {
      id: true,
      email: true,
      updatedAt: true,
      createdAt: true,
    },
  })

  const sessions = await prisma.authSession.findMany({
    where: { userId: { in: authUsers.map((authUser) => authUser.id) } },
    orderBy: { updatedAt: "desc" },
    select: {
      userId: true,
      updatedAt: true,
    },
  })

  const sessionByUserId = new Map<string, Date>()
  for (const sessionRow of sessions) {
    if (!sessionByUserId.has(sessionRow.userId)) {
      sessionByUserId.set(sessionRow.userId, sessionRow.updatedAt)
    }
  }

  const authByEmail = new Map<string, { id: string; updatedAt: Date; createdAt: Date }>()
  for (const authUser of authUsers) {
    authByEmail.set(authUser.email.toLowerCase(), {
      id: authUser.id,
      updatedAt: authUser.updatedAt,
      createdAt: authUser.createdAt,
    })
  }

  const items = users.map((user) => {
    const auth = authByEmail.get(user.email.toLowerCase())
    const lastSeenAt = auth ? sessionByUserId.get(auth.id) ?? auth.updatedAt : null
    return {
      id: user.id,
      email: user.email,
      name: user.name,
      role: normalizeRole(user.role),
      companyId: user.companyId,
      companyName: user.company?.name ?? null,
      jobTitle: user.jobTitle ?? null,
      managerId: user.managerId,
      managerName: user.manager?.name ?? null,
      managerEmail: user.manager?.email ?? null,
      tenantId: user.tenantId,
      createdAt: user.createdAt.toISOString(),
      updatedAt: user.updatedAt.toISOString(),
      authUserId: auth?.id ?? null,
      lastSeenAt: lastSeenAt ? lastSeenAt.toISOString() : null,
    }
  })

  return NextResponse.json({ items })
}

export async function POST(request: Request) {
  const session = await assertAdminSession()
  if (!session) {
    return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
  }

  const body = (await request.json().catch(() => null)) as {
    name?: string
    email?: string
    role?: string
    tenantId?: string
    jobTitle?: string | null
    managerId?: string | null
  } | null

  if (!body || typeof body !== "object") {
    return NextResponse.json({ error: "Payload inválido" }, { status: 400 })
  }

  const name = body.name?.trim() ?? ""
  const email = body.email?.trim().toLowerCase() ?? ""
  const tenantId = (body.tenantId ?? session.user.tenantId ?? DEFAULT_TENANT_ID).trim() || DEFAULT_TENANT_ID

  if (!name) {
    return NextResponse.json({ error: "Informe o nome do usuário" }, { status: 400 })
  }
  if (!email || !email.includes("@")) {
    return NextResponse.json({ error: "Informe um e-mail válido" }, { status: 400 })
  }

  const rawJobTitle = typeof body.jobTitle === "string" ? body.jobTitle.trim() : null
  const jobTitle = rawJobTitle ? rawJobTitle : null
  const rawManagerId = typeof body.managerId === "string" ? body.managerId.trim() : ""
  const managerId = rawManagerId.length > 0 ? rawManagerId : null

  let managerRecord: { id: string; email: string; tenantId: string; name: string } | null = null
  if (managerId) {
    managerRecord = await prisma.user.findUnique({
      where: { id: managerId },
      select: { id: true, email: true, tenantId: true, name: true },
    })
    if (!managerRecord) {
      return NextResponse.json({ error: "Gestor informado não foi encontrado." }, { status: 400 })
    }
    if (managerRecord.tenantId !== tenantId) {
      return NextResponse.json({ error: "Gestor pertence a outro cliente." }, { status: 400 })
    }
  }

  const normalizedRole = normalizeRole(body.role)
  const authRole = normalizedRole.toLowerCase()
  const userRole = normalizedRole as UserRole

  const existingAuth = await prisma.authUser.findUnique({ where: { email } })
  if (existingAuth) {
    return NextResponse.json({ error: "Já existe um usuário com este e-mail" }, { status: 409 })
  }

  const temporaryPassword = generatePassword()
  const hashedPassword = await hashPassword(temporaryPassword)

  const [authUser, domainUser] = await prisma.$transaction(async (tx) => {
    const createdAuthUser = await tx.authUser.create({
      data: {
        email,
        name,
        role: authRole,
        tenantId,
        accounts: {
          create: {
            providerId: "credential",
            accountId: email,
            password: hashedPassword,
          },
        },
      },
    })

    const createdDomainUser = await tx.user.upsert({
      where: { id: createdAuthUser.id },
      update: {
        name,
        role: userRole,
        tenantId,
        jobTitle,
        managerId: managerRecord?.id ?? null,
      },
      create: {
        id: createdAuthUser.id,
        name,
        email,
        role: userRole,
        tenantId,
        jobTitle,
        managerId: managerRecord?.id ?? null,
      },
    })

    return [createdAuthUser, createdDomainUser] as const
  })

  const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL
  if (convexUrl) {
    try {
      const convex = new ConvexHttpClient(convexUrl)
      let managerConvexId: Id<"users"> | undefined
      if (managerRecord?.email) {
        try {
          const convexManager = await convex.query(api.users.findByEmail, {
            tenantId,
            email: managerRecord.email,
          })
          if (convexManager?._id) {
            managerConvexId = convexManager._id as Id<"users">
          }
        } catch (error) {
          console.warn("[admin/users] Falha ao localizar gestor no Convex", error)
        }
      }
      await convex.mutation(api.users.ensureUser, {
        tenantId,
        email,
        name,
        avatarUrl: authUser.avatarUrl ?? undefined,
        role: userRole,
        jobTitle: jobTitle ?? undefined,
        managerId: managerConvexId,
      })
    } catch (error) {
      console.error("[admin/users] ensureUser failed", error)
    }
  }

  return NextResponse.json({
    user: {
      id: domainUser.id,
      authUserId: authUser.id,
      email: domainUser.email,
      name: domainUser.name,
      role: authRole,
      tenantId: domainUser.tenantId,
      createdAt: domainUser.createdAt.toISOString(),
      jobTitle,
      managerId: managerRecord?.id ?? null,
      managerName: managerRecord?.name ?? null,
      managerEmail: managerRecord?.email ?? null,
    },
    temporaryPassword,
  })
}

export async function DELETE(request: Request) {
  const session = await assertStaffSession()
  if (!session) {
    return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
  }

  if (!isAdmin(session.user.role)) {
    return NextResponse.json({ error: "Apenas administradores podem excluir usuários." }, { status: 403 })
  }

  const json = await request.json().catch(() => null)
  const ids = Array.isArray(json?.ids) ? (json.ids as string[]) : []
  if (ids.length === 0) {
    return NextResponse.json({ error: "Nenhum usuário selecionado." }, { status: 400 })
  }

  const tenantId = session.user.tenantId ?? DEFAULT_TENANT_ID

  const users = await prisma.user.findMany({
    where: {
      id: { in: ids },
      tenantId,
      role: { in: [...ALLOWED_ROLES] },
    },
    select: {
      id: true,
      email: true,
    },
  })

  if (users.length === 0) {
    return NextResponse.json({ deletedIds: [] })
  }

  const emails = users.map((user) => user.email.toLowerCase())
  const authUsers = await prisma.authUser.findMany({
    where: {
      email: { in: emails },
    },
    select: {
      id: true,
    },
  })

  const authUserIds = authUsers.map((authUser) => authUser.id)

  await prisma.$transaction(async (tx) => {
    if (authUserIds.length > 0) {
      await tx.authSession.deleteMany({ where: { userId: { in: authUserIds } } })
      await tx.authAccount.deleteMany({ where: { userId: { in: authUserIds } } })
      await tx.authUser.deleteMany({ where: { id: { in: authUserIds } } })
    }
    await tx.user.deleteMany({ where: { id: { in: users.map((user) => user.id) } } })
  })

  return NextResponse.json({ deletedIds: users.map((user) => user.id) })
}
