import { createHmac, randomBytes } from "crypto";
import { prisma } from "@pmap/database";

const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

export type AdminSecuritySettings = {
  twoFactorEnabled: boolean;
  authenticatorConfigured: boolean;
  recoveryCodesGeneratedAt: string | null;
  twoFactorSecret: string | null;
};

type StoredSecuritySettings = {
  twoFactorEnabled?: unknown;
  authenticatorConfigured?: unknown;
  recoveryCodesGeneratedAt?: unknown;
  twoFactorSecret?: unknown;
};

export function adminSecuritySettingKey(userId: string) {
  return `admin_security:${userId}`;
}

export function normalizeAdminSecuritySettings(value: unknown): AdminSecuritySettings {
  const settings = value && typeof value === "object" ? value as StoredSecuritySettings : {};
  return {
    twoFactorEnabled: settings.twoFactorEnabled === true,
    authenticatorConfigured: settings.authenticatorConfigured === true,
    recoveryCodesGeneratedAt: typeof settings.recoveryCodesGeneratedAt === "string" ? settings.recoveryCodesGeneratedAt : null,
    twoFactorSecret: typeof settings.twoFactorSecret === "string" && settings.twoFactorSecret ? settings.twoFactorSecret : null
  };
}

export async function getAdminSecuritySettings(userId: string) {
  const setting = await prisma.systemSetting.findFirst({
    where: { conferenceId: null, key: adminSecuritySettingKey(userId) }
  });
  return normalizeAdminSecuritySettings(setting?.value);
}

export async function saveAdminSecuritySettings(userId: string, settings: AdminSecuritySettings) {
  const existing = await prisma.systemSetting.findFirst({
    where: { conferenceId: null, key: adminSecuritySettingKey(userId) }
  });
  const value = {
    twoFactorEnabled: settings.twoFactorEnabled,
    authenticatorConfigured: settings.authenticatorConfigured,
    recoveryCodesGeneratedAt: settings.recoveryCodesGeneratedAt,
    twoFactorSecret: settings.twoFactorSecret
  };

  if (existing) {
    await prisma.systemSetting.update({
      where: { id: existing.id },
      data: { value, updatedById: userId }
    });
  } else {
    await prisma.systemSetting.create({
      data: { key: adminSecuritySettingKey(userId), value, updatedById: userId }
    });
  }

  return value;
}

export function generateTotpSecret() {
  const bytes = randomBytes(20);
  let bits = "";
  for (const byte of bytes) bits += byte.toString(2).padStart(8, "0");
  let output = "";
  for (let index = 0; index < bits.length; index += 5) {
    const chunk = bits.slice(index, index + 5).padEnd(5, "0");
    output += alphabet[Number.parseInt(chunk, 2)] ?? "";
  }
  return output;
}

function decodeBase32(secret: string) {
  const clean = secret.toUpperCase().replace(/[^A-Z2-7]/g, "");
  let bits = "";
  for (const char of clean) {
    const value = alphabet.indexOf(char);
    if (value >= 0) bits += value.toString(2).padStart(5, "0");
  }
  const bytes: number[] = [];
  for (let index = 0; index + 8 <= bits.length; index += 8) {
    bytes.push(Number.parseInt(bits.slice(index, index + 8), 2));
  }
  return Buffer.from(bytes);
}

function totpCode(secret: string, step: number) {
  const counter = Buffer.alloc(8);
  counter.writeBigUInt64BE(BigInt(step));
  const hmac = createHmac("sha1", decodeBase32(secret)).update(counter).digest();
  const offset = (hmac[hmac.length - 1] ?? 0) & 0x0f;
  const value = (((hmac[offset] ?? 0) & 0x7f) << 24) |
    (((hmac[offset + 1] ?? 0) & 0xff) << 16) |
    (((hmac[offset + 2] ?? 0) & 0xff) << 8) |
    ((hmac[offset + 3] ?? 0) & 0xff);
  return String(value % 1_000_000).padStart(6, "0");
}

export function verifyTotpCode(secret: string, code: string) {
  const normalizedCode = code.trim().replace(/\s/g, "");
  if (!/^\d{6}$/.test(normalizedCode)) return false;
  const currentStep = Math.floor(Date.now() / 30_000);
  return [-1, 0, 1].some((offset) => totpCode(secret, currentStep + offset) === normalizedCode);
}

export function authenticatorSetupUrl(email: string, secret: string) {
  const issuer = "PMAP AC 2026";
  const label = `${issuer}:${email}`;
  const params = new URLSearchParams({
    secret,
    issuer,
    algorithm: "SHA1",
    digits: "6",
    period: "30"
  });
  return `otpauth://totp/${encodeURIComponent(label)}?${params.toString()}`;
}
