import crypto from "node:crypto";
import QRCode from "qrcode";
import { generateSecret as otplibGenerateSecret, generateURI as otplibGenerateURI, verifySync } from "otplib";
import { AdminUser, db } from "./db";

/**
 * Enterprise TOTP 2FA Service
 * Scannable with Google Authenticator, Microsoft Authenticator, 1Password, Authy, etc.
 */

export const TOTP_ISSUER = "Quto AI";

/**
 * Generates a new base32 TOTP secret and scannable QR code.
 */
export async function generateTOTPEnrollment(email: string): Promise<{
  secret: string;
  otpauthUri: string;
  qrCodeDataUrl: string;
}> {
  const secret = otplibGenerateSecret();
  const otpauthUri = otplibGenerateURI({
    issuer: TOTP_ISSUER,
    label: email,
    secret,
  });

  const qrCodeDataUrl = await QRCode.toDataURL(otpauthUri, {
    errorCorrectionLevel: "M",
    margin: 2,
    scale: 6,
    color: {
      dark: "#000000",
      light: "#ffffff",
    },
  });

  return { secret, otpauthUri, qrCodeDataUrl };
}

/**
 * Verifies a 6-digit TOTP token against a base32 secret.
 */
export function verifyTOTP(token: string, secret: string): boolean {
  if (!token || !secret) return false;
  const cleanToken = token.trim().replace(/\s+/g, "");
  if (!/^\d{6}$/.test(cleanToken)) return false;

  try {
    const result = verifySync({ token: cleanToken, secret });
    return !!result && result.valid === true;
  } catch (err) {
    console.error("TOTP verification error:", err);
    return false;
  }
}

/**
 * Generates 10 single-use cryptographically secure backup recovery codes.
 * Format: 5 alphanumeric uppercase characters - 5 alphanumeric uppercase characters (e.g. 7K4L2-99BXM)
 */
export function generateBackupCodes(count = 10): string[] {
  const codes: string[] = [];
  const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // Base32-like without ambiguous 0/O, 1/I

  for (let i = 0; i < count; i++) {
    let part1 = "";
    let part2 = "";
    const bytes = crypto.randomBytes(10);
    for (let j = 0; j < 5; j++) {
      part1 += chars[bytes[j] % chars.length];
      part2 += chars[bytes[j + 5] % chars.length];
    }
    codes.push(`${part1}-${part2}`);
  }

  return codes;
}

/**
 * Hashes a backup code with SHA-256 for secure comparison.
 */
export function hashBackupCode(code: string): string {
  const normalized = code.trim().toUpperCase().replace(/[^A-Z0-9]/g, "");
  return crypto.createHash("sha256").update(normalized).digest("hex");
}

/**
 * Verifies and consumes a backup code. If valid, the code is removed from the user's account
 * so it cannot ever be reused.
 */
export async function verifyAndConsumeBackupCode(user: AdminUser, rawInputCode: string): Promise<boolean> {
  if (!user.twoFactorBackupCodes || user.twoFactorBackupCodes.length === 0) {
    return false;
  }

  const hashedInput = hashBackupCode(rawInputCode);
  const codeIndex = user.twoFactorBackupCodes.indexOf(hashedInput);

  if (codeIndex === -1) {
    return false;
  }

  // Code is valid! Consume it immediately (single-use)
  const remainingCodes = [...user.twoFactorBackupCodes];
  remainingCodes.splice(codeIndex, 1);

  await db.updateUser(user.id, {
    twoFactorBackupCodes: remainingCodes,
  });

  return true;
}

const TEMP_2FA_SECRET = process.env.ADMIN_SESSION_SECRET || "quto-ai-2fa-temp-secret-key-salt";

/**
 * Creates a signed 5-minute temporary token for 2FA login challenges.
 */
export function create2FATempToken(userId: string): string {
  const payload = Buffer.from(
    JSON.stringify({
      userId,
      exp: Date.now() + 5 * 60 * 1000, // 5 minutes
    })
  ).toString("base64url");

  const sig = crypto.createHmac("sha256", TEMP_2FA_SECRET).update(payload).digest("base64url");
  return `${payload}.${sig}`;
}

/**
 * Verifies a 2FA temporary token. Returns userId if valid, null otherwise.
 */
export function verify2FATempToken(token: string): string | null {
  if (!token || !token.includes(".")) return null;
  const [payload, sig] = token.split(".");
  if (!payload || !sig) return null;

  try {
    const expectedSig = crypto.createHmac("sha256", TEMP_2FA_SECRET).update(payload).digest("base64url");
    if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expectedSig))) {
      return null;
    }

    const data = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
    if (!data.userId || !data.exp || typeof data.exp !== "number") {
      return null;
    }

    if (Date.now() > data.exp) {
      return null; // Expired
    }

    return data.userId as string;
  } catch {
    return null;
  }
}
