import { NextRequest, NextResponse } from "next/server"
import { cookies } from "next/headers"

import { api } from "@/convex/_generated/api"
import type { Id } from "@/convex/_generated/dataModel"
import { DEFAULT_TENANT_ID } from "@/lib/constants"
import { createConvexClient, ConvexConfigurationError } from "@/server/convex-client"
import {
  MACHINE_CTX_COOKIE,
  extractCollaboratorFromMetadata,
  parseMachineCookie,
  serializeMachineCookie,
  type CollaboratorMetadata,
  type MachineContextCookiePayload,
} from "@/server/machines/context"

type MachineSession = {
  user: {
    role: string
    email: string
  }
} | null

type AssertSessionFn = () => Promise<MachineSession>

let testAssertSessionImpl: AssertSessionFn | null = null

const setAssertSessionForTests: (fn: AssertSessionFn | null) => void = (fn) => {
  if (process.env.NODE_ENV !== "test") return
  testAssertSessionImpl = fn
}

// Exportado apenas para testes; tipado como `never` para não conflitar com o contrato de rotas do Next.
export const __setAssertSessionForTests = setAssertSessionForTests as never

async function resolveAssertAuthenticatedSession(): Promise<AssertSessionFn> {
  if (testAssertSessionImpl) return testAssertSessionImpl

  type AuthServerModuleLike = {
    assertAuthenticatedSession?: AssertSessionFn
    default?: {
      assertAuthenticatedSession?: AssertSessionFn
    }
  }

  const mod = (await import("@/lib/auth-server")) as AuthServerModuleLike
  const fn = mod.assertAuthenticatedSession ?? mod.default?.assertAuthenticatedSession
  if (typeof fn !== "function") {
    throw new Error("assertAuthenticatedSession not available")
  }
  return fn
}

// Força runtime Node.js para leitura consistente de cookies de sessão
export const runtime = "nodejs"

export async function GET(request: NextRequest) {
  const assertAuthenticatedSession = await resolveAssertAuthenticatedSession()
  const session = await assertAuthenticatedSession()
  if (!session || session.user.role !== "machine") {
    return NextResponse.json({ error: "Sessão de dispositivo não encontrada." }, { status: 403 })
  }

  let client
  try {
    client = createConvexClient()
  } catch (error) {
    if (error instanceof ConvexConfigurationError) {
      return NextResponse.json({ error: error.message }, { status: 500 })
    }
    throw error
  }

  const cookieStore = await cookies()
  const cookieValue = cookieStore.get(MACHINE_CTX_COOKIE)?.value ?? null

  const decoded = parseMachineCookie(cookieValue)
  let machineId: Id<"machines"> | null = decoded?.machineId ? (decoded.machineId as Id<"machines">) : null

  if (!machineId) {
    try {
      const lookup = (await client.query(api.devices.findByAuthEmail, {
        authEmail: session.user.email.toLowerCase(),
      })) as { id: string } | null

      if (!lookup?.id) {
        return NextResponse.json({ error: "Dispositivo não vinculada à sessão atual." }, { status: 404 })
      }

      machineId = lookup.id as Id<"machines">
    } catch (error) {
      console.error("[machines.session] Falha ao localizar dispositivo por e-mail", error)
      return NextResponse.json({ error: "Não foi possível localizar a dispositivo." }, { status: 500 })
    }
  }

  try {
    let context = (await client.query(api.devices.getContext, {
      machineId,
    })) as {
      id: string
      tenantId: string
      companyId: string | null
      companySlug: string | null
      persona: string | null
      assignedUserId: string | null
      assignedUserEmail: string | null
      assignedUserName: string | null
      assignedUserRole: string | null
      metadata: Record<string, unknown> | null
      authEmail: string | null
    }

    const metadataCollaborator: CollaboratorMetadata | null = extractCollaboratorFromMetadata(context.metadata)

    let ensuredAssignedUserId = context.assignedUserId
    let ensuredAssignedUserEmail = context.assignedUserEmail ?? metadataCollaborator?.email ?? null
    let ensuredAssignedUserName = context.assignedUserName ?? metadataCollaborator?.name ?? null
    let ensuredAssignedUserRole =
      context.assignedUserRole ??
      (metadataCollaborator?.role ? metadataCollaborator.role.toUpperCase() : null)
    let ensuredPersona = context.persona ?? metadataCollaborator?.role ?? null

    if (!ensuredAssignedUserId && ensuredAssignedUserEmail) {
      try {
        const personaRoleCandidate = ensuredPersona ?? ensuredAssignedUserRole ?? "collaborator"
        const personaRole = personaRoleCandidate.toString().toLowerCase()
        const normalizedPersona = personaRole === "manager" ? "manager" : "collaborator"
        const assignedRole = normalizedPersona === "manager" ? "MANAGER" : "COLLABORATOR"

        const ensuredUser = (await client.mutation(api.users.ensureUser, {
          tenantId: context.tenantId ?? DEFAULT_TENANT_ID,
          email: ensuredAssignedUserEmail,
          name: ensuredAssignedUserName ?? ensuredAssignedUserEmail,
          role: normalizedPersona.toUpperCase(),
          companyId: context.companyId ? (context.companyId as Id<"companies">) : undefined,
        })) as {
          _id?: Id<"users">
          name?: string | null
          role?: string | null
        } | null

        if (ensuredUser?._id) {
          ensuredAssignedUserId = ensuredUser._id as string
          ensuredAssignedUserName = ensuredUser.name ?? ensuredAssignedUserName ?? ensuredAssignedUserEmail
          ensuredAssignedUserRole = ensuredUser.role ?? ensuredAssignedUserRole ?? assignedRole
          ensuredPersona = normalizedPersona

          await client.mutation(api.devices.updatePersona, {
            machineId: machineId as Id<"machines">,
            persona: normalizedPersona,
            assignedUserId: ensuredUser._id as Id<"users">,
            assignedUserEmail: ensuredAssignedUserEmail,
            assignedUserName: ensuredAssignedUserName ?? undefined,
            assignedUserRole: (ensuredAssignedUserRole ?? assignedRole).toUpperCase(),
          })

          context = (await client.query(api.devices.getContext, {
            machineId,
          })) as typeof context

          ensuredAssignedUserId = context.assignedUserId ?? ensuredAssignedUserId
          ensuredAssignedUserEmail = context.assignedUserEmail ?? ensuredAssignedUserEmail
          ensuredAssignedUserName = context.assignedUserName ?? ensuredAssignedUserName
          ensuredAssignedUserRole = context.assignedUserRole ?? ensuredAssignedUserRole
          ensuredPersona = context.persona ?? ensuredPersona
        }
      } catch (error) {
        console.error("[machines.session] Falha ao garantir usuário vinculado", error)
      }
    }

    const resolvedPersona =
      context.persona ??
      ensuredPersona ??
      (ensuredAssignedUserRole ? ensuredAssignedUserRole.toLowerCase() : null)

    const responsePayload: MachineContextCookiePayload = {
      machineId: context.id,
      persona: resolvedPersona ?? null,
      assignedUserId: ensuredAssignedUserId ?? null,
      assignedUserEmail: ensuredAssignedUserEmail ?? null,
      assignedUserName: ensuredAssignedUserName ?? null,
      assignedUserRole: ensuredAssignedUserRole ?? null,
    }

    const response = NextResponse.json({
      machine: {
        ...context,
        persona: resolvedPersona,
        assignedUserEmail: ensuredAssignedUserEmail ?? null,
        assignedUserId: ensuredAssignedUserId ?? null,
        assignedUserName: ensuredAssignedUserName ?? null,
        assignedUserRole: ensuredAssignedUserRole ?? null,
      },
      cookie: responsePayload,
    })

    const isSecure = request.nextUrl.protocol === "https:"
    response.cookies.set({
      name: MACHINE_CTX_COOKIE,
      value: serializeMachineCookie(responsePayload),
      httpOnly: true,
      sameSite: "lax",
      secure: isSecure,
      path: "/",
      maxAge: 60 * 60 * 24 * 30,
    })

    return response
  } catch (error) {
    console.error("[machines.session] Falha ao obter contexto da dispositivo", error)
    return NextResponse.json({ error: "Falha ao obter contexto da dispositivo." }, { status: 500 })
  }
}
