/**
 * Services d'authentification
 * Gestion des tokens, login failures, reset password
 */
import type { Pool } from "mysql2/promise";
import { hashResetToken, hashToken } from "./crypto-utils.js";
import {
  EMAIL_VERIFY_TOKEN_TTL_MS,
  LOCK_TIME_MS,
  MAX_FAILED_LOGINS,
  RESET_TOKEN_TTL_MS,
} from "./constants.js";
import type { RefreshTokenRow, UserRow, UserRecord } from "./types.js";
import { mapUserRow } from "./user-service.js";

/**
 * Nettoie les refresh tokens expirés ou révoqués
 */
export const cleanupRefreshTokens = async (db: Pool): Promise<void> => {
  await db.execute(
    "DELETE FROM refresh_tokens WHERE revoked_at IS NOT NULL OR expires_at < NOW()",
  );
};

/**
 * Révoque tous les refresh tokens d'un utilisateur
 */
export const revokeRefreshTokensForUser = async (
  db: Pool,
  userId: number,
): Promise<void> => {
  await db.execute(
    "UPDATE refresh_tokens SET revoked_at = NOW() WHERE user_id = ? AND revoked_at IS NULL",
    [userId],
  );
};

/**
 * Stocke un nouveau refresh token
 */
export const storeRefreshToken = async (
  db: Pool,
  userId: number,
  token: string,
  expiresAt: Date,
): Promise<void> => {
  const tokenHash = hashToken(token);
  await db.execute(
    "INSERT INTO refresh_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)",
    [userId, tokenHash, expiresAt],
  );
};

/**
 * Vérifie la validité d'un refresh token
 */
export const verifyRefreshToken = async (
  db: Pool,
  userId: number,
  token: string,
): Promise<boolean> => {
  const tokenHash = hashToken(token);
  const [rows] = await db.execute(
    "SELECT user_id, token_hash, expires_at, revoked_at FROM refresh_tokens WHERE token_hash = ? LIMIT 1",
    [tokenHash],
  );
  const row = Array.isArray(rows)
    ? (rows[0] as RefreshTokenRow | undefined)
    : undefined;
  if (!row || row.user_id !== userId || row.revoked_at) {
    return false;
  }
  const expiresAt =
    row.expires_at instanceof Date ? row.expires_at : new Date(row.expires_at);
  return expiresAt.getTime() > Date.now();
};

/**
 * Révoque un refresh token spécifique
 */
export const revokeRefreshToken = async (
  db: Pool,
  token: string,
): Promise<void> => {
  const tokenHash = hashToken(token);
  await db.execute(
    "UPDATE refresh_tokens SET revoked_at = NOW() WHERE token_hash = ?",
    [tokenHash],
  );
};

/**
 * Efface les échecs de connexion d'un utilisateur
 */
export const clearLoginFailures = async (
  db: Pool,
  userId: number,
): Promise<void> => {
  await db.execute(
    "UPDATE users SET failed_login_attempts = 0, locked_until = NULL WHERE id = ?",
    [userId],
  );
};

/**
 * Enregistre un échec de connexion
 */
export const recordFailedLogin = async (
  db: Pool,
  userId: number,
  currentAttempts: number,
): Promise<{ nextAttempts: number; lockedUntil: Date | null }> => {
  const nextAttempts = currentAttempts + 1;
  const lockedUntil =
    nextAttempts >= MAX_FAILED_LOGINS
      ? new Date(Date.now() + LOCK_TIME_MS)
      : null;
  await db.execute(
    "UPDATE users SET failed_login_attempts = ?, locked_until = ? WHERE id = ?",
    [nextAttempts, lockedUntil, userId],
  );
  return { nextAttempts, lockedUntil };
};

/**
 * Stocke un token de réinitialisation de mot de passe
 */
export const storeResetToken = async (
  db: Pool,
  userId: number,
  token: string,
): Promise<Date> => {
  const tokenHash = hashResetToken(token);
  const expiresAt = new Date(Date.now() + RESET_TOKEN_TTL_MS);
  await db.execute(
    "UPDATE users SET reset_token_hash = ?, reset_token_expires = ? WHERE id = ?",
    [tokenHash, expiresAt, userId],
  );
  return expiresAt;
};

/**
 * Consomme un token de réinitialisation et retourne l'utilisateur
 */
export const consumeResetToken = async (
  db: Pool,
  token: string,
): Promise<UserRecord | null> => {
  const tokenHash = hashResetToken(token);
  const [rows] = await db.execute(
    "SELECT id, email, password_hash as passwordHash, failed_login_attempts as failedAttempts, locked_until as lockedUntil, reset_token_hash as resetTokenHash, reset_token_expires as resetTokenExpires, email_verified as emailVerified, email_verified_at as emailVerifiedAt, email_verify_token_hash as emailVerifyTokenHash, email_verify_token_expires as emailVerifyTokenExpires, display_name as displayName, avatar, accent, title, motto, guest, is_admin as isAdmin FROM users WHERE reset_token_hash = ? AND reset_token_expires > NOW() LIMIT 1",
    [tokenHash],
  );
  const row = Array.isArray(rows)
    ? (rows[0] as UserRow | undefined)
    : undefined;
  if (!row) {
    return null;
  }
  const user = mapUserRow(row);
  await db.execute(
    "UPDATE users SET reset_token_hash = NULL, reset_token_expires = NULL WHERE id = ?",
    [user.id],
  );
  return {
    ...user,
    resetTokenHash: null,
    resetTokenExpires: null,
  };
};

/**
 * Stocke un token de vérification d'email
 */
export const storeEmailVerifyToken = async (
  db: Pool,
  userId: number,
  token: string,
): Promise<Date> => {
  const tokenHash = hashToken(token);
  const expiresAt = new Date(Date.now() + EMAIL_VERIFY_TOKEN_TTL_MS);
  await db.execute(
    "UPDATE users SET email_verify_token_hash = ?, email_verify_token_expires = ? WHERE id = ?",
    [tokenHash, expiresAt, userId],
  );
  return expiresAt;
};

/**
 * Consomme un token de vérification d'email et valide le compte
 */
export const consumeEmailVerifyToken = async (
  db: Pool,
  token: string,
): Promise<UserRecord | null> => {
  const tokenHash = hashToken(token);
  const [rows] = await db.execute(
    "SELECT id, email, password_hash as passwordHash, failed_login_attempts as failedAttempts, locked_until as lockedUntil, reset_token_hash as resetTokenHash, reset_token_expires as resetTokenExpires, email_verified as emailVerified, email_verified_at as emailVerifiedAt, email_verify_token_hash as emailVerifyTokenHash, email_verify_token_expires as emailVerifyTokenExpires, display_name as displayName, avatar, accent, title, motto, guest, is_admin as isAdmin FROM users WHERE email_verify_token_hash = ? AND email_verify_token_expires > NOW() LIMIT 1",
    [tokenHash],
  );
  const row = Array.isArray(rows)
    ? (rows[0] as UserRow | undefined)
    : undefined;
  if (!row) {
    return null;
  }
  const user = mapUserRow(row);
  await db.execute(
    "UPDATE users SET email_verified = TRUE, email_verified_at = NOW(), email_verify_token_hash = NULL, email_verify_token_expires = NULL WHERE id = ?",
    [user.id],
  );
  return {
    ...user,
    emailVerified: true,
    emailVerifiedAt: new Date(),
    emailVerifyTokenHash: null,
    emailVerifyTokenExpires: null,
  };
};
