import "server-only";
import { createHash, randomBytes } from "node:crypto";
import { eq, lt } from "drizzle-orm";
import { db, schema } from "@/lib/db";
import { auth } from "@/lib/auth";

/**
 * Tokens de set / reset de mot de passe.
 *
 * On stocke dans la table `verification` de Better Auth :
 *  - identifier : "password_reset:{userId}"
 *  - value      : sha256 hex du token (jamais le token en clair)
 *  - expiresAt  : now + 24h
 *
 * Le token brut (envoyé par email) ne transite jamais par la DB.
 * On peut donc générer un nouveau token sans craindre d'interception
 * d'un ancien.
 */

const TOKEN_TTL_HOURS = 24;
const PREFIX = "password_reset:";

export function generateToken(): string {
  return randomBytes(32).toString("hex");
}

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

/**
 * Crée un token de reset pour un userId. Renvoie le token en clair
 * (à intégrer dans l'URL d'email). Invalide les éventuels tokens
 * précédents pour cet user.
 */
export async function createPasswordResetToken(
  userId: string
): Promise<string> {
  const token = generateToken();
  const value = hashToken(token);
  const expiresAt = new Date(Date.now() + TOKEN_TTL_HOURS * 60 * 60 * 1000);
  const identifier = `${PREFIX}${userId}`;

  // Supprime les anciens tokens pour ce user.
  await db
    .delete(schema.verification)
    .where(eq(schema.verification.identifier, identifier));

  await db.insert(schema.verification).values({
    id: crypto.randomUUID(),
    identifier,
    value,
    expiresAt,
  });

  return token;
}

/**
 * Vérifie un token. Si OK, renvoie userId. Sinon null.
 * Ne consomme PAS le token (utiliser consumePasswordResetToken pour ça).
 */
export async function verifyPasswordResetToken(
  userId: string,
  token: string
): Promise<boolean> {
  const identifier = `${PREFIX}${userId}`;
  const expected = hashToken(token);

  const row = (
    await db
      .select()
      .from(schema.verification)
      .where(eq(schema.verification.identifier, identifier))
      .limit(1)
  )[0];

  if (!row) return false;
  if (row.value !== expected) return false;
  if (new Date(row.expiresAt) < new Date()) return false;

  return true;
}

/**
 * Définit un nouveau mot de passe et consomme le token.
 * Toutes les sessions actives de l'user sont invalidées.
 */
export async function setPasswordWithToken(
  userId: string,
  token: string,
  newPassword: string
): Promise<{ ok: true } | { ok: false; error: string }> {
  if (newPassword.length < 10) {
    return { ok: false, error: "Mot de passe trop court (10 caractères min)." };
  }

  const valid = await verifyPasswordResetToken(userId, token);
  if (!valid) {
    return { ok: false, error: "Lien invalide ou expiré." };
  }

  const u = (
    await db
      .select()
      .from(schema.user)
      .where(eq(schema.user.id, userId))
      .limit(1)
  )[0];
  if (!u) return { ok: false, error: "Utilisateur introuvable." };

  const ctx = await auth.$context;
  const hashed = await ctx.password.hash(newPassword);

  // Upsert de la row account "credential".
  const accountRow = (
    await db
      .select()
      .from(schema.account)
      .where(eq(schema.account.userId, userId))
      .limit(1)
  )[0];

  if (accountRow) {
    await db
      .update(schema.account)
      .set({ password: hashed, updatedAt: new Date() })
      .where(eq(schema.account.id, accountRow.id));
  } else {
    await db.insert(schema.account).values({
      id: crypto.randomUUID(),
      userId,
      accountId: u.email,
      providerId: "credential",
      password: hashed,
    });
  }

  // Invalide les sessions ouvertes (par sécurité après reset).
  await db.delete(schema.session).where(eq(schema.session.userId, userId));

  // Consomme le token.
  await db
    .delete(schema.verification)
    .where(eq(schema.verification.identifier, `${PREFIX}${userId}`));

  // Cleanup opportuniste des tokens expirés (peu coûteux).
  await db
    .delete(schema.verification)
    .where(lt(schema.verification.expiresAt, new Date()));

  return { ok: true };
}
