import { createError, deleteCookie, getCookie, getRequestProtocol, setCookie } from "h3"
import type { H3Event } from "h3"
import { createHmac, randomBytes } from "node:crypto"
import { prisma } from "~/server/utils/prisma"
import { AUTH_COOKIE_NAME } from "~/utils/auth"

const SESSION_DURATION_SECONDS = 60 * 60 * 24 * 14
const DEFAULT_AUTH_SESSION_SECRET = "comp-autos-session-secret"

export interface SessionUser {
  id: string
  email: string
  name: string | null
  imageUrl: string | null
  role: "USER" | "ADMIN"
}

interface SessionState {
  user: SessionUser | null
  checked: boolean
}

const getSessionSecret = () => process.env.AUTH_SESSION_SECRET ?? DEFAULT_AUTH_SESSION_SECRET

const assertSessionSecret = () => {
  if (process.env.NODE_ENV !== "production") {
    return
  }

  if (getSessionSecret() === DEFAULT_AUTH_SESSION_SECRET) {
    throw createError({
      statusCode: 503,
      statusMessage: "Debes configurar AUTH_SESSION_SECRET en produccion."
    })
  }
}

const hashToken = (token: string) => createHmac("sha256", getSessionSecret()).update(token).digest("base64url")

const getEventSessionState = (event: H3Event) => {
  const contextWithSession = event.context as typeof event.context & {
    __compAutosSessionState?: SessionState
  }

  if (!contextWithSession.__compAutosSessionState) {
    contextWithSession.__compAutosSessionState = {
      user: null,
      checked: false
    }
  }

  return contextWithSession.__compAutosSessionState
}

const getUserFromSessionToken = async (token: string): Promise<SessionUser | null> => {
  const session = await prisma.userSession.findUnique({
    where: {
      tokenHash: hashToken(token)
    },
    include: {
      user: {
        select: {
          id: true,
          email: true,
          name: true,
          imageUrl: true,
          role: true
        }
      }
    }
  })

  if (!session) {
    return null
  }

  if (session.expiresAt.getTime() <= Date.now()) {
    await prisma.userSession.delete({
      where: {
        id: session.id
      }
    })

    return null
  }

  return session.user
}

export const getAuthenticatedUser = async (event: H3Event) => {
  const state = getEventSessionState(event)

  if (state.checked) {
    return state.user
  }

  const token = getCookie(event, AUTH_COOKIE_NAME)

  if (!token) {
    state.checked = true
    return null
  }

  const user = await getUserFromSessionToken(token)
  state.user = user
  state.checked = true
  return user
}

export const getSessionPayload = async (event: H3Event) => {
  const user = await getAuthenticatedUser(event)

  return {
    authenticated: Boolean(user),
    user
  }
}

export const requireAuthenticatedRequest = async (event: H3Event) => {
  const user = await getAuthenticatedUser(event)

  if (!user) {
    throw createError({
      statusCode: 401,
      statusMessage: "No autorizado"
    })
  }

  return user
}

export const requireAdminRequest = async (event: H3Event) => {
  const user = await requireAuthenticatedRequest(event)

  if (user.role !== "ADMIN") {
    throw createError({
      statusCode: 403,
      statusMessage: "Acceso solo para administradores"
    })
  }

  return user
}

export const setAuthSession = async (event: H3Event, userId: string) => {
  assertSessionSecret()

  const token = randomBytes(32).toString("base64url")
  const tokenHash = hashToken(token)
  const expiresAt = new Date(Date.now() + SESSION_DURATION_SECONDS * 1000)

  await prisma.userSession.create({
    data: {
      userId,
      tokenHash,
      expiresAt
    }
  })

  const isSecureRequest = getRequestProtocol(event, {
    xForwardedProto: true
  }) === "https"

  setCookie(event, AUTH_COOKIE_NAME, token, {
    path: "/",
    sameSite: "lax",
    httpOnly: true,
    secure: isSecureRequest,
    maxAge: SESSION_DURATION_SECONDS
  })

  const state = getEventSessionState(event)
  state.user = null
  state.checked = false
}

export const clearAuthSession = async (event: H3Event) => {
  const token = getCookie(event, AUTH_COOKIE_NAME)

  if (token) {
    await prisma.userSession.deleteMany({
      where: {
        tokenHash: hashToken(token)
      }
    })
  }

  deleteCookie(event, AUTH_COOKIE_NAME, {
    path: "/"
  })

  const state = getEventSessionState(event)
  state.user = null
  state.checked = false
}

export const issuePasswordResetToken = async (userId: string) => {
  assertSessionSecret()

  const rawToken = randomBytes(32).toString("base64url")
  const tokenHash = hashToken(rawToken)

  await prisma.passwordResetToken.create({
    data: {
      userId,
      tokenHash,
      expiresAt: new Date(Date.now() + 1000 * 60 * 30)
    }
  })

  return rawToken
}

export const consumePasswordResetToken = async (rawToken: string) => {
  const tokenHash = hashToken(rawToken)

  const token = await prisma.passwordResetToken.findUnique({
    where: {
      tokenHash
    }
  })

  if (!token || token.usedAt || token.expiresAt.getTime() <= Date.now()) {
    return null
  }

  await prisma.passwordResetToken.update({
    where: {
      id: token.id
    },
    data: {
      usedAt: new Date()
    }
  })

  return {
    userId: token.userId,
    tokenId: token.id
  }
}
