import crypto from "node:crypto";
import argon2 from "argon2";
import * as bcrypt from "bcryptjs";

export const clampInt = (value: number, min = 0, max = 9999999) => {
  if (!Number.isFinite(value)) {
    return min;
  }
  return Math.floor(Math.max(min, Math.min(max, value)));
};

export const hashToken = (token: string) =>
  crypto.createHash("sha256").update(token).digest("hex");

export const parseDurationMs = (value: string): number => {
  const match = /^(\d+)([smhd])$/i.exec(value.trim());
  if (!match) {
    return 0;
  }
  const amount = Number(match[1]);
  const unit = match[2].toLowerCase();
  const multipliers: Record<string, number> = {
    s: 1000,
    m: 60 * 1000,
    h: 60 * 60 * 1000,
    d: 24 * 60 * 60 * 1000,
  };
  return amount * (multipliers[unit] ?? 0);
};

export const expiresAtFrom = (ttl: string) => {
  const ms = parseDurationMs(ttl);
  return new Date(Date.now() + ms);
};

export const getDayOfYear = (date: Date): number => {
  const start = new Date(date.getFullYear(), 0, 1);
  const diff =
    Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) -
    Date.UTC(start.getFullYear(), start.getMonth(), start.getDate());
  return Math.floor(diff / (1000 * 60 * 60 * 24));
};

export const hashPassword = async (plain: string) =>
  await argon2.hash(plain, {
    type: argon2.argon2id,
    timeCost: 3,
    memoryCost: 2 ** 16,
    parallelism: 1,
  });

export const verifyPassword = async (plain: string, hash: string) => {
  if (hash.startsWith("$2")) {
    return await new Promise<boolean>((resolve, reject) => {
      bcrypt.compare(plain, hash, (err: Error | null, same: boolean) => {
        if (err) {
          reject(err);
          return;
        }
        resolve(same);
      });
    });
  }
  return await argon2.verify(hash, plain);
};

export const isLegacyBcrypt = (hash?: string | null) =>
  Boolean(hash?.startsWith("$2"));

export const hashResetToken = (token: string) =>
  crypto.createHash("sha256").update(token).digest("hex");

export const randomId = () =>
  typeof crypto.randomUUID === "function"
    ? crypto.randomUUID().slice(0, 8)
    : crypto.randomBytes(4).toString("hex");

export const randomTokenBase64Url = (size = 32) =>
  crypto.randomBytes(size).toString("base64url");

export const isLocked = (lockedUntil: Date | string | null) => {
  if (!lockedUntil) {
    return false;
  }
  const date =
    lockedUntil instanceof Date ? lockedUntil : new Date(lockedUntil);
  return date.getTime() > Date.now();
};

/**
 * Vérifie si un utilisateur est actuellement banni
 * @param bannedAt - Date du ban (null = pas banni)
 * @param banUntil - Date de fin du ban (null = permanent)
 * @returns true si banni, false sinon (ou si le ban a expiré)
 */
export const isBanned = (
  bannedAt: Date | string | null,
  banUntil: Date | string | null,
): boolean => {
  // Si pas de date de ban, l'utilisateur n'est pas banni
  if (!bannedAt) {
    return false;
  }
  // Si pas de date de fin, c'est un ban permanent
  if (!banUntil) {
    return true;
  }
  // Sinon, vérifier si le ban est encore actif
  const untilDate = banUntil instanceof Date ? banUntil : new Date(banUntil);
  return untilDate.getTime() > Date.now();
};
