import crypto from "crypto";
import { config } from "../config/env";

const ALGORITHM = "aes-256-gcm";
const IV_LENGTH = 12;
const TAG_LENGTH = 16;

export function encryptWithKey(plaintext: string, keyHex: string): string {
  const key = Buffer.from(keyHex, "hex");
  const iv = crypto.randomBytes(IV_LENGTH);
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv);

  const encrypted = Buffer.concat([
    cipher.update(plaintext, "utf8"),
    cipher.final(),
  ]);
  const tag = cipher.getAuthTag();

  // Use PHP-compatible format: base64(nonce + ciphertext + tag)
  return Buffer.concat([iv, encrypted, tag]).toString("base64");
}

export function encrypt(plaintext: string): string {
  return encryptWithKey(plaintext, config.totp.encryptionKey);
}

/**
 * Decrypt a ciphertext using an explicit 32-byte (hex-encoded) key.
 *
 * Format detection note (audit finding addressed):
 * The format is inferred from `split(":").length === 3` — the TS wire format is
 * `hex(iv):hex(ciphertext):hex(tag)`, while the PHP-legacy wire format is a
 * single base64 blob of `nonce + ciphertext + tag` (no colons). This heuristic
 * is intentionally left UNCHANGED: altering it (e.g. adding a version prefix)
 * would change the on-the-wire shape and break every TOTP secret already stored
 * in the database. The crypto itself is sound (random IV per encrypt, GCM tag
 * verified on decrypt, raw 32-byte key) and is also left untouched. A malformed
 * TS value that happens not to contain exactly two colons falls through to the
 * base64 branch and fails authentication there — which is the safe outcome.
 */
export function decryptWithKey(ciphertext: string, keyHex: string): string {
  const key = Buffer.from(keyHex, "hex");

  // Detect format: PHP uses base64(nonce+ciphertext+tag), TS uses hex:hex:hex
  const parts = ciphertext.split(":");
  if (parts.length === 3) {
    // TS format: iv:encrypted:tag (hex)
    const iv = Buffer.from(parts[0], "hex");
    const encrypted = parts[1];
    const tag = Buffer.from(parts[2], "hex");

    const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
    decipher.setAuthTag(tag);

    let decrypted = decipher.update(encrypted, "hex", "utf8");
    decrypted += decipher.final("utf8");
    return decrypted;
  }

  // PHP format: base64(nonce + ciphertext + tag)
  const raw = Buffer.from(ciphertext, "base64");
  if (raw.length < IV_LENGTH + TAG_LENGTH + 1) {
    throw new Error("Invalid ciphertext format");
  }

  const iv = raw.subarray(0, IV_LENGTH);
  const tag = raw.subarray(raw.length - TAG_LENGTH);
  const encrypted = raw.subarray(IV_LENGTH, raw.length - TAG_LENGTH);

  const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
  decipher.setAuthTag(tag);

  const decrypted = decipher.update(encrypted);
  const final = decipher.final();
  return Buffer.concat([decrypted, final]).toString("utf8");
}

export function decrypt(ciphertext: string): string {
  return decryptWithKey(ciphertext, config.totp.encryptionKey);
}
