const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const ssbKeys = require('../server/node_modules/ssb-keys');

const MAGIC = Buffer.from('OAES1\n', 'utf8');
const KEYLEN = 32;
const DEFAULT_SCRYPT = { N: 16384, r: 8, p: 1 };
const VERIFIER_PLAINTEXT = Buffer.from('oasis-keylock-v1', 'utf8');

let KEK = null; // SOLO en memoria

const maxmemFor = (s) => Math.max(64 * 1024 * 1024, 256 * s.N * s.r);

const deriveKEK = (password, salt, scrypt = DEFAULT_SCRYPT) =>
  crypto.scryptSync(Buffer.from(String(password), 'utf8'), salt, KEYLEN,
    { N: scrypt.N, r: scrypt.r, p: scrypt.p, maxmem: maxmemFor(scrypt) });

const isWrapped = (buf) =>
  Buffer.isBuffer(buf) && buf.length >= MAGIC.length && buf.subarray(0, MAGIC.length).equals(MAGIC);

const wrap = (buf, kek = KEK) => {
  if (!kek) throw new Error('keylock: locked (no KEK)');
  const iv = crypto.randomBytes(12);
  const c = crypto.createCipheriv('aes-256-gcm', kek, iv);
  const ct = Buffer.concat([c.update(buf), c.final()]);
  const tag = c.getAuthTag();
  return Buffer.concat([MAGIC, iv, tag, ct]);
};

const unwrap = (blob, kek = KEK) => {
  if (!kek) throw new Error('keylock: locked (no KEK)');
  if (!isWrapped(blob)) throw new Error('keylock: not a wrapped blob');
  let o = MAGIC.length;
  const iv = blob.subarray(o, o += 12);
  const tag = blob.subarray(o, o += 16);
  const ct = blob.subarray(o);
  const d = crypto.createDecipheriv('aes-256-gcm', kek, iv);
  d.setAuthTag(tag);
  return Buffer.concat([d.update(ct), d.final()]);
};

const lockPath = (configPath) => path.join(configPath, 'keylock.json');

const isConfigured = (configPath) => {
  try { return fs.existsSync(lockPath(configPath)); } catch (_) { return false; }
};

const setPassword = (configPath, password) => {
  const salt = crypto.randomBytes(16);
  const scrypt = DEFAULT_SCRYPT;
  const kek = deriveKEK(password, salt, scrypt);
  const verifier = wrap(VERIFIER_PLAINTEXT, kek);
  const data = {
    v: 1, kdf: 'scrypt', N: scrypt.N, r: scrypt.r, p: scrypt.p,
    salt: salt.toString('hex'), verifier: verifier.toString('base64'),
  };
  fs.mkdirSync(configPath, { recursive: true, mode: 0o700 });
  const tmp = lockPath(configPath) + '.tmp';
  fs.writeFileSync(tmp, JSON.stringify(data), { mode: 0o600 });
  fs.renameSync(tmp, lockPath(configPath));
  KEK = kek;
  return kek;
};

const verifyPassword = (configPath, password) => {
  let data;
  try { data = JSON.parse(fs.readFileSync(lockPath(configPath), 'utf8')); } catch (_) { return null; }
  const salt = Buffer.from(data.salt, 'hex');
  const scrypt = { N: data.N || DEFAULT_SCRYPT.N, r: data.r || DEFAULT_SCRYPT.r, p: data.p || DEFAULT_SCRYPT.p };
  const kek = deriveKEK(password, salt, scrypt);
  try {
    if (unwrap(Buffer.from(data.verifier, 'base64'), kek).equals(VERIFIER_PLAINTEXT)) {
      KEK = kek;
      return kek;
    }
  } catch (_) {}
  return null;
};

const getKEK = () => KEK;
const setKEK = (k) => { KEK = k; };
const isUnlocked = () => KEK != null;
const lock = () => { try { if (KEK) KEK.fill(0); } catch (_) {} KEK = null; };

const secretEncPath = (configPath) => path.join(configPath, 'secret.enc');
const legacySecretPath = (configPath) => path.join(configPath, 'secret');

const shred = (p) => {
  try { fs.writeFileSync(p, crypto.randomBytes(Math.max(1, fs.statSync(p).size))); } catch (_) {}
  try { fs.unlinkSync(p); } catch (_) {}
};

const writeSecret = (configPath, keys) => {
  const blob = wrap(Buffer.from(JSON.stringify(keys), 'utf8'));
  fs.mkdirSync(configPath, { recursive: true, mode: 0o700 });
  const tmp = secretEncPath(configPath) + '.tmp';
  fs.writeFileSync(tmp, blob, { mode: 0o600 });
  fs.renameSync(tmp, secretEncPath(configPath));
};

const loadOrCreateSecret = (configPath) => {
  if (!KEK) throw new Error('keylock: locked');
  fs.mkdirSync(configPath, { recursive: true, mode: 0o700 });
  const encP = secretEncPath(configPath);
  if (fs.existsSync(encP)) {
    return JSON.parse(unwrap(fs.readFileSync(encP)).toString('utf8'));
  }
  const legP = legacySecretPath(configPath);
  if (fs.existsSync(legP)) {
    const keys = ssbKeys.loadSync(legP);
    writeSecret(configPath, keys);
    shred(legP);
    return keys;
  }
  const keys = ssbKeys.generate('ed25519'); // primer arranque
  writeSecret(configPath, keys);
  return keys;
};

const hasIdentity = (configPath) =>
  fs.existsSync(secretEncPath(configPath)) || fs.existsSync(legacySecretPath(configPath));

module.exports = {
  MAGIC, deriveKEK, wrap, unwrap, isWrapped,
  isConfigured, setPassword, verifyPassword,
  getKEK, setKEK, isUnlocked, lock,
  loadOrCreateSecret, writeSecret, hasIdentity,
};
