/**
 * MFA repository — CRUD for auth_mfa + auth_mfa_policies containers.
 * TOTP secrets are encrypted at rest with AES-256-GCM.
 */

import { createCipheriv, createDecipheriv, randomBytes, createHash } from 'node:crypto';
import { getCollection } from '../../../lib/datastore.js';
import { config } from '../../../lib/config.js';
import type { MfaDoc, MfaPolicyDoc } from './types.js';

function mfaCollection() {
  return getCollection<MfaDoc>('auth_mfa', '/userId');
}

function policyCollection() {
  return getCollection<MfaPolicyDoc>('auth_mfa_policies', '/productId');
}

// ── AES-256-GCM encryption ──────────────────────────────────

function getEncryptionKey(): Buffer {
  const keyHex = config.AUTH_TOTP_ENCRYPTION_KEY;
  if (!keyHex) throw new Error('AUTH_TOTP_ENCRYPTION_KEY is not configured');
  return Buffer.from(keyHex, 'hex');
}

export function encryptSecret(plaintext: string): {
  encrypted: string;
  iv: string;
  authTag: string;
} {
  const key = getEncryptionKey();
  const iv = randomBytes(12);
  const cipher = createCipheriv('aes-256-gcm', key, iv);
  let encrypted = cipher.update(plaintext, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  const authTag = cipher.getAuthTag().toString('hex');
  return { encrypted, iv: iv.toString('hex'), authTag };
}

export function decryptSecret(encrypted: string, ivHex: string, authTagHex: string): string {
  const key = getEncryptionKey();
  const iv = Buffer.from(ivHex, 'hex');
  const authTag = Buffer.from(authTagHex, 'hex');
  const decipher = createDecipheriv('aes-256-gcm', key, iv);
  decipher.setAuthTag(authTag);
  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  return decrypted;
}

// ── Recovery code helpers ───────────────────────────────────

export function generateRecoveryCodes(count = 8): string[] {
  const codes: string[] = [];
  for (let i = 0; i < count; i++) {
    codes.push(randomBytes(4).toString('hex')); // 8-char hex codes
  }
  return codes;
}

export function hashRecoveryCode(code: string): string {
  return createHash('sha256').update(code.toLowerCase()).digest('hex');
}

// ── MFA CRUD ────────────────────────────────────────────────

export async function getByUserId(userId: string): Promise<MfaDoc | null> {
  try {
    return await mfaCollection().findById(`mfa_${userId}`, userId);
  } catch {
    return null;
  }
}

export async function create(doc: MfaDoc): Promise<MfaDoc> {
  return mfaCollection().create(doc);
}

export async function update(userId: string, updates: Partial<MfaDoc>): Promise<MfaDoc | null> {
  try {
    return await mfaCollection().update(`mfa_${userId}`, userId, {
      ...updates,
      updatedAt: new Date().toISOString(),
    } as Partial<MfaDoc>);
  } catch {
    return null;
  }
}

export async function remove(userId: string): Promise<boolean> {
  try {
    await mfaCollection().delete(`mfa_${userId}`, userId);
    return true;
  } catch {
    return false;
  }
}

// ── MFA Policy CRUD ─────────────────────────────────────────

export async function getPolicy(productId: string): Promise<MfaPolicyDoc | null> {
  try {
    return await policyCollection().findById(`mfapol_${productId}`, productId);
  } catch {
    return null;
  }
}

export async function upsertPolicy(doc: MfaPolicyDoc): Promise<MfaPolicyDoc> {
  const existing = await getPolicy(doc.productId);
  if (existing) {
    return (await policyCollection().update(doc.id, doc.productId, {
      required: doc.required,
      methods: doc.methods,
      gracePeriodDays: doc.gracePeriodDays,
      updatedAt: new Date().toISOString(),
    } as Partial<MfaPolicyDoc>)) as MfaPolicyDoc;
  }
  return policyCollection().create(doc);
}
