import { NextResponse } from "next/server"
import { randomBytes } from "crypto"

import { Prisma } from "@prisma/client"
import { ConvexHttpClient } from "convex/browser"

import { api } from "@/convex/_generated/api"
import { assertStaffSession } from "@/lib/auth-server"
import { DEFAULT_TENANT_ID } from "@/lib/constants"
import { ROLE_OPTIONS, type RoleOption, isAdmin } from "@/lib/authz"
import { env } from "@/lib/env"
import { prisma } from "@/lib/prisma"
import { computeInviteStatus, normalizeInvite, type InviteWithEvents, type NormalizedInvite } from "@/server/invite-utils"

const DEFAULT_EXPIRATION_DAYS = 7

function toJsonPayload(payload: unknown): Prisma.InputJsonValue | Prisma.NullableJsonNullValueInput {
  if (payload === null || payload === undefined) {
    return Prisma.JsonNull
  }
  return payload as Prisma.InputJsonValue
}

function normalizeRole(input: string | null | undefined): RoleOption {
  const role = (input ?? "agent").toLowerCase() as RoleOption
  return (ROLE_OPTIONS as readonly string[]).includes(role) ? role : "agent"
}

function generateToken() {
  return randomBytes(32).toString("hex")
}

async function syncInviteWithConvex(invite: NormalizedInvite) {
  const convexUrl = env.NEXT_PUBLIC_CONVEX_URL
  if (!convexUrl) return

  const client = new ConvexHttpClient(convexUrl)
  await client.mutation(api.invites.sync, {
    tenantId: invite.tenantId,
    inviteId: invite.id,
    email: invite.email,
    name: invite.name ?? undefined,
    role: invite.role.toUpperCase(),
    status: invite.status,
    token: invite.token,
    expiresAt: Date.parse(invite.expiresAt),
    createdAt: Date.parse(invite.createdAt),
    createdById: invite.createdById ?? undefined,
    acceptedAt: invite.acceptedAt ? Date.parse(invite.acceptedAt) : undefined,
    acceptedById: invite.acceptedById ?? undefined,
    revokedAt: invite.revokedAt ? Date.parse(invite.revokedAt) : undefined,
    revokedById: invite.revokedById ?? undefined,
    revokedReason: invite.revokedReason ?? undefined,
  })
}

async function appendEvent(inviteId: string, type: string, actorId: string | null, payload: unknown = null) {
  return prisma.authInviteEvent.create({
    data: {
      inviteId,
      type,
      payload: toJsonPayload(payload),
      actorId,
    },
  })
}

async function refreshInviteStatus(invite: InviteWithEvents, now: Date) {
  const computedStatus = computeInviteStatus(invite, now)
  if (computedStatus === invite.status) {
    return invite
  }

  const updated = await prisma.authInvite.update({
    where: { id: invite.id },
    data: { status: computedStatus },
  })

  const event = await appendEvent(invite.id, computedStatus, null)

  const inviteWithEvents: InviteWithEvents = {
    ...updated,
    events: [...invite.events, event],
  }
  return inviteWithEvents
}

function buildInvitePayload(invite: InviteWithEvents, now: Date) {
  const normalized = normalizeInvite(invite, now)
  return normalized
}

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

  const invites = await prisma.authInvite.findMany({
    orderBy: { createdAt: "desc" },
    include: {
      events: {
        orderBy: { createdAt: "asc" },
      },
    },
  })

  const now = new Date()
  const results: NormalizedInvite[] = []

  for (const invite of invites) {
    const updatedInvite = await refreshInviteStatus(invite, now)
    const normalized = buildInvitePayload(updatedInvite, now)
    await syncInviteWithConvex(normalized)
    results.push(normalized)
  }

  return NextResponse.json({ invites: results })
}

type CreateInvitePayload = {
  email: string
  name?: string
  role?: RoleOption
  tenantId?: string
  expiresInDays?: number
}

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

  const body = (await request.json().catch(() => null)) as Partial<CreateInvitePayload> | null
  if (!body || typeof body !== "object") {
    return NextResponse.json({ error: "Payload inválido" }, { status: 400 })
  }

  const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : ""
  if (!email || !email.includes("@")) {
    return NextResponse.json({ error: "Informe um e-mail válido" }, { status: 400 })
  }

  const name = typeof body.name === "string" ? body.name.trim() : undefined
  const role = normalizeRole(body.role)
  const isSessionAdmin = isAdmin(session.user.role)
  if (!isSessionAdmin && !["manager", "collaborator"].includes(role)) {
    return NextResponse.json({ error: "Agentes só podem convidar gestores ou colaboradores" }, { status: 403 })
  }
  const tenantId = typeof body.tenantId === "string" && body.tenantId.trim() ? body.tenantId.trim() : session.user.tenantId || DEFAULT_TENANT_ID
  const expiresInDays = Number.isFinite(body.expiresInDays) ? Math.max(1, Number(body.expiresInDays)) : DEFAULT_EXPIRATION_DAYS

  const existing = await prisma.authInvite.findFirst({
    where: {
      email,
      status: { in: ["pending", "accepted"] },
    },
    include: { events: true },
  })

  const now = new Date()
  if (existing) {
    const computed = computeInviteStatus(existing, now)
    if (computed === "pending") {
      return NextResponse.json({ error: "Já existe um convite pendente para este e-mail" }, { status: 409 })
    }
    if (computed === "accepted") {
      return NextResponse.json({ error: "Este e-mail já possui acesso ativo" }, { status: 409 })
    }
    if (existing.status !== computed) {
      await prisma.authInvite.update({ where: { id: existing.id }, data: { status: computed } })
      await appendEvent(existing.id, computed, session.user.id ?? null)
      const refreshed = await prisma.authInvite.findUnique({
        where: { id: existing.id },
        include: { events: true },
      })
      if (refreshed) {
        const normalizedExisting = buildInvitePayload(refreshed, now)
        await syncInviteWithConvex(normalizedExisting)
      }
    }
  }

  const token = generateToken()
  const expiresAt = new Date(Date.now() + expiresInDays * 24 * 60 * 60 * 1000)

  const invite = await prisma.authInvite.create({
    data: {
      email,
      name,
      role,
      tenantId,
      token,
      status: "pending",
      expiresAt,
      createdById: session.user.id ?? null,
    },
  })

  const event = await appendEvent(invite.id, "created", session.user.id ?? null, {
    role,
    tenantId,
    expiresAt: expiresAt.toISOString(),
  })

  const inviteWithEvents: InviteWithEvents = {
    ...invite,
    events: [event],
  }

  const normalized = buildInvitePayload(inviteWithEvents, now)
  await syncInviteWithConvex(normalized)

  return NextResponse.json({ invite: normalized })
}
