import { randomUUID, createHash } from "crypto"
import { cookies, headers } from "next/headers"
import { prisma } from "@/lib/prisma"
import { SESSION_COOKIE_NAME, SESSION_MAX_AGE_SEC } from "@/lib/session-constants"
export type SessionPayload = {
  userId: string
  role: "admin" | "employee"
}

function hashSessionToken(raw: string): string {
  return createHash("sha256").update(raw, "utf8").digest("hex")
}

function isLegacyHashedToken(value: string): boolean {
  return value.length === 64 && /^[0-9a-f]+$/i.test(value)
}

function cookieOptions() {
  return {
    httpOnly: true as const,
    secure: process.env.NODE_ENV === "production",
    sameSite: "lax" as const,
    path: "/",
    maxAge: SESSION_MAX_AGE_SEC,
  }
}

function expiresAt() {
  return new Date(Date.now() + SESSION_MAX_AGE_SEC * 1000)
}

async function getRawTokenFromRequest(): Promise<string | null> {
  const store = await cookies()
  const fromCookie = store.get(SESSION_COOKIE_NAME)?.value
  if (fromCookie) return fromCookie
  const h = await headers()
  const auth = h.get("authorization")
  if (auth?.toLowerCase().startsWith("bearer ")) {
    return auth.slice(7).trim() || null
  }
  return null
}

const sessionWithUser = {
  User: { select: { id: true, role: true, status: true } },
} as const

async function findSessionByBearer(raw: string) {
  if (!raw) return null
  const byRaw = await prisma.session.findFirst({
    where: { sessionToken: raw },
    include: sessionWithUser,
  })
  if (byRaw) return byRaw
  if (!isLegacyHashedToken(raw)) {
    const hashed = hashSessionToken(raw)
    return prisma.session.findFirst({
      where: { sessionToken: hashed },
      include: sessionWithUser,
    })
  }
  return null
}

/**
 * True if the session token is present, exists in the DB, not expired, and user is active.
 * For use in Route Handlers; Edge middleware should call /api/auth/verify instead of Prisma.
 */
export async function isSessionValid(): Promise<boolean> {
  const raw = await getRawTokenFromRequest()
  if (!raw) return false
  const session = await findSessionByBearer(raw)
  if (!session) return false
  if (session.expires.getTime() < Date.now()) {
    await prisma.session
      .delete({ where: { sessionToken: session.sessionToken } })
      .catch(() => {})
    return false
  }
  if (session.User.status !== "active") {
    await prisma.session
      .delete({ where: { sessionToken: session.sessionToken } })
      .catch(() => {})
    return false
  }
  return true
}

/**
 * Creates or reuses the single DB session for this user and sets the httpOnly cookie.
 * - If a non-expired session exists, reuses it (and returns the same bearer to the client).
 * - New sessions store the raw token in `Session.sessionToken` (older rows may use a SHA-256 hash).
 */
export async function createSession(userId: string): Promise<void> {
  const now = Date.now()
  const incomingRaw = (await cookies()).get(SESSION_COOKIE_NAME)?.value
  const exp = expiresAt()

  // `findFirst` avoids Prisma "multiple rows" if DB predates the unique `userId` migration.
  const existing = await prisma.session.findFirst({
    where: { userId },
    orderBy: { expires: "desc" },
  })

  if (existing && existing.expires.getTime() > now) {
    if (isLegacyHashedToken(existing.sessionToken)) {
      if (incomingRaw && hashSessionToken(incomingRaw) === existing.sessionToken) {
        const store = await cookies()
        store.set(SESSION_COOKIE_NAME, incomingRaw, cookieOptions())
        await prisma.session.update({
          where: { userId },
          data: { expires: exp },
        })
        return
      }
    } else {
      if (incomingRaw === existing.sessionToken) {
        const store = await cookies()
        store.set(SESSION_COOKIE_NAME, existing.sessionToken, cookieOptions())
        await prisma.session.update({
          where: { userId },
          data: { expires: exp },
        })
        return
      }
    }
  }

  const rawToken = randomUUID()
  await prisma.$transaction([
    prisma.session.deleteMany({ where: { userId } }),
    prisma.session.create({
      data: {
        sessionToken: rawToken,
        userId,
        expires: exp,
      },
    }),
  ])

  const store = await cookies()
  store.set(SESSION_COOKIE_NAME, rawToken, cookieOptions())
}

/**
 * Validates cookie (or `Authorization: Bearer`) + DB session, refreshes DB expiration on success.
 * Does not mutate cookies: `getSession` runs from Server Components / layouts,
 * where Next.js only allows reading `cookies()`, not `set`/`delete`.
 * Returns `{ userId, role }` or `null`.
 */
export async function getSession(): Promise<SessionPayload | null> {
  const raw = await getRawTokenFromRequest()
  if (!raw) return null

  const session = await findSessionByBearer(raw)
  if (!session) {
    return null
  }

  if (session.expires.getTime() < Date.now()) {
    await prisma.session
      .delete({ where: { sessionToken: session.sessionToken } })
      .catch(() => {})
    return null
  }

  if (session.User.status !== "active") {
    await prisma.session
      .delete({ where: { sessionToken: session.sessionToken } })
      .catch(() => {})
    return null
  }

  const newExpires = expiresAt()
  await prisma.session.update({
    where: { sessionToken: session.sessionToken },
    data: { expires: newExpires },
  })

  return {
    userId: session.User.id,
    role: session.User.role,
  }
}

/** Deletes the session row for this user and clears the cookie. */
export async function deleteSession(): Promise<void> {
  const store = await cookies()
  const raw = store.get(SESSION_COOKIE_NAME)?.value
  const h = await headers()
  const auth = h.get("authorization")
  const bearer =
    auth?.toLowerCase().startsWith("bearer ") && auth.length > 7 ? auth.slice(7).trim() : null
  const token = raw ?? bearer
  if (token) {
    const hashed = hashSessionToken(token)
    await prisma.session.deleteMany({
      where: { OR: [{ sessionToken: token }, { sessionToken: hashed }] },
    })
  }
  store.delete(SESSION_COOKIE_NAME)
}
