import bcrypt from "bcrypt";
import { randomBytes } from "node:crypto";
import { prisma } from "./prisma";
import { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
import { cookies } from "next/headers";
import { User } from "@prisma/client";

/**
 * Hash a password using bcrypt (native).
 *
 * Switched from bcryptjs (pure-JS) for ~10x speedup on login. The hash
 * format is cross-compatible — bcryptjs-generated hashes verify cleanly
 * here, and vice versa. Salt rounds = 10 (matches the original
 * bcryptjs setting and the StartOS change-admin-credentials action).
 */
export async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, 10);
}

export async function verifyPassword(
  password: string,
  hash: string,
): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

/**
 * Create a session token for a user (30-day expiration).
 *
 * Token is 256 bits of CSPRNG output, hex-encoded (64 chars). Do not
 * weaken this — predictable tokens enable cross-user impersonation,
 * and under multi-user that means anyone-can-be-anyone if guessable.
 */
export async function createSession(
  userId: string
): Promise<{ token: string; expiresAt: Date }> {
  const token = randomBytes(32).toString("hex");

  const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days

  // Create the session and stamp the user's lastLoginAt in the same
  // transaction. Surfaced in the admin Users table so admins can spot
  // dormant accounts.
  await prisma.$transaction([
    prisma.session.create({
      data: {
        token,
        userId,
        expiresAt,
      },
    }),
    prisma.user.update({
      where: { id: userId },
      data: { lastLoginAt: new Date() },
    }),
  ]);

  return { token, expiresAt };
}

/**
 * Validate a session token and return the associated user
 */
export async function validateSession(token: string): Promise<User | null> {
  const session = await prisma.session.findUnique({
    where: { token },
    include: { user: true },
  });

  if (!session) {
    return null;
  }

  // Check if session has expired
  if (session.expiresAt < new Date()) {
    await prisma.session.delete({ where: { token } });
    return null;
  }

  return session.user;
}

/**
 * Delete a session token
 */
export async function deleteSession(token: string): Promise<void> {
  await prisma.session.delete({
    where: { token },
  });
}

/**
 * Revoke every session for a user except the optional `keepToken`.
 * Used by password-change to log the user out of every other device
 * when they rotate their password (defense against compromised
 * sessions).
 */
export async function deleteOtherSessions(
  userId: string,
  keepToken: string | null,
): Promise<number> {
  const result = await prisma.session.deleteMany({
    where: {
      userId,
      ...(keepToken ? { NOT: { token: keepToken } } : {}),
    },
  });
  return result.count;
}

/**
 * Get session from cookies object
 */
export async function getSessionFromCookies(
  cookieStore: ReadonlyRequestCookies
): Promise<User | null> {
  const sessionToken = cookieStore.get("sessionToken")?.value;

  if (!sessionToken) {
    return null;
  }

  return validateSession(sessionToken);
}

/**
 * Get the current user from request cookies
 */
export async function getCurrentUser(): Promise<User | null> {
  const cookieStore = await cookies();
  return getSessionFromCookies(cookieStore);
}
