import crypto from "node:crypto";
import type { FastifyReply, FastifyRequest } from "fastify";
import bcrypt from "bcryptjs";
import { sql } from "kysely";
import { db } from "./db.js";

const SESSION_COOKIE = "inventory_session";

export type AuthUser = {
  id: string;
  email: string;
};

declare module "fastify" {
  interface FastifyRequest {
    user?: AuthUser;
  }
}

export function randomToken(bytes = 32) {
  return crypto.randomBytes(bytes).toString("base64url");
}

export function tokenHash(token: string) {
  return crypto.createHash("sha256").update(token).digest("hex");
}

export async function hashPassword(password: string) {
  return bcrypt.hash(password, 12);
}

export async function verifyPassword(password: string, passwordHash: string) {
  return bcrypt.compare(password, passwordHash);
}

export async function createSession(reply: FastifyReply, userId: string) {
  const token = randomToken();
  const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30);
  await db
    .insertInto("sessions")
    .values({
      user_id: userId,
      token_hash: tokenHash(token),
      expires_at: expiresAt
    })
    .execute();

  reply.setCookie(SESSION_COOKIE, token, {
    httpOnly: true,
    sameSite: "lax",
    secure: process.env.NODE_ENV === "production",
    path: "/",
    expires: expiresAt
  });
  return token;
}

export async function clearSession(request: FastifyRequest, reply: FastifyReply) {
  const token = request.cookies[SESSION_COOKIE] ?? bearerToken(request);
  if (token) {
    await db.deleteFrom("sessions").where("token_hash", "=", tokenHash(token)).execute();
  }
  reply.clearCookie(SESSION_COOKIE, { path: "/" });
}

function bearerToken(request: FastifyRequest) {
  const authorization = request.headers.authorization;
  if (!authorization?.startsWith("Bearer ")) return undefined;
  return authorization.slice("Bearer ".length).trim();
}

export async function authenticate(request: FastifyRequest, reply: FastifyReply) {
  const token = request.cookies[SESSION_COOKIE] ?? bearerToken(request);
  if (!token) {
    reply.code(401).send({ error: "Authentication required" });
    return;
  }

  const session = await db
    .selectFrom("sessions")
    .innerJoin("users", "users.id", "sessions.user_id")
    .select(["users.id as id", "users.email as email", "sessions.expires_at as expires_at"])
    .where("sessions.token_hash", "=", tokenHash(token))
    .where("sessions.expires_at", ">", sql<Date>`now()`)
    .executeTakeFirst();

  if (!session) {
    reply.code(401).send({ error: "Authentication required" });
    return;
  }

  request.user = { id: session.id, email: session.email };
}

export async function hasAnyUsers() {
  const row = await db.selectFrom("users").select((eb) => eb.fn.countAll<string>().as("count")).executeTakeFirst();
  return Number(row?.count ?? 0) > 0;
}
