'use strict';

const crypto = require('crypto');
const { generateSecret, generateURI, generateSync, verifySync } = require('otplib');
const QRCode = require('qrcode');
const { encryptSecret, decryptSecret } = require('./encryptionService');

const APP_NAME = 'Bill Tracker';
const RECOVERY_CODE_COUNT = 8;
const CHALLENGE_TTL_MS = 5 * 60 * 1000;

function newSecret() {
  return generateSecret(20);
}

async function generateQrCode(secret, username) {
  const uri = generateURI({ secret, account: username, issuer: APP_NAME, type: 'totp' });
  const qr_data_url = await QRCode.toDataURL(uri, { width: 200, margin: 2 });
  return { uri, qr_data_url };
}

function verifyToken(encryptedSecret, token) {
  if (!encryptedSecret || !token) return false;
  try {
    const secret = decryptSecret(encryptedSecret);
    const result = verifySync({ secret, token: String(token).replace(/\s/g, ''), type: 'totp' });
    return result?.valid === true;
  } catch {
    return false;
  }
}

function verifyTokenRaw(secret, token) {
  if (!secret || !token) return false;
  try {
    const result = verifySync({ secret, token: String(token).replace(/\s/g, ''), type: 'totp' });
    return result?.valid === true;
  } catch {
    return false;
  }
}

function makeRecoveryCodes() {
  return Array.from({ length: RECOVERY_CODE_COUNT }, () => {
    const bytes = crypto.randomBytes(5);
    const hex = bytes.toString('hex').toUpperCase();
    return `${hex.slice(0, 5)}-${hex.slice(5)}`;
  });
}

function hashRecoveryCode(code) {
  return crypto.createHash('sha256').update(code.replace(/-/g, '').toUpperCase()).digest('hex');
}

function consumeRecoveryCode(db, userId, code) {
  const normalized = code.replace(/[-\s]/g, '').toUpperCase();
  const user = db.prepare('SELECT totp_recovery_codes FROM users WHERE id = ?').get(userId);
  if (!user?.totp_recovery_codes) return { used: false };

  let stored;
  try {
    stored = JSON.parse(decryptSecret(user.totp_recovery_codes));
  } catch {
    return { used: false };
  }

  const incomingHash = crypto.createHash('sha256').update(normalized).digest('hex');
  const idx = stored.findIndex(h => h === incomingHash);
  if (idx === -1) return { used: false };

  stored.splice(idx, 1);
  db.prepare('UPDATE users SET totp_recovery_codes = ? WHERE id = ?')
    .run(encryptSecret(JSON.stringify(stored)), userId);

  return { used: true, remaining: stored.length };
}

function createChallenge(db, userId) {
  const id = crypto.randomUUID();
  const expiresAt = new Date(Date.now() + CHALLENGE_TTL_MS)
    .toISOString().slice(0, 19).replace('T', ' ');
  db.prepare('DELETE FROM totp_challenges WHERE user_id = ?').run(userId);
  db.prepare('INSERT INTO totp_challenges (id, user_id, expires_at) VALUES (?, ?, ?)').run(id, userId, expiresAt);
  return id;
}

function consumeChallenge(db, challengeId) {
  const row = db.prepare(
    "SELECT user_id FROM totp_challenges WHERE id = ? AND expires_at > datetime('now')"
  ).get(challengeId);
  if (!row) return null;
  db.prepare('DELETE FROM totp_challenges WHERE id = ?').run(challengeId);
  return row.user_id;
}

function pruneExpiredChallenges(db) {
  db.prepare("DELETE FROM totp_challenges WHERE expires_at <= datetime('now')").run();
}

module.exports = {
  generateSecret: newSecret,
  generateQrCode,
  verifyToken,
  verifyTokenRaw,
  generateRecoveryCodes: makeRecoveryCodes,
  hashRecoveryCode,
  consumeRecoveryCode,
  createChallenge,
  consumeChallenge,
  pruneExpiredChallenges,
};
