const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const { sha256 } = require('./hash');

const ROOT = path.resolve(__dirname, '..', '..');
const BASE_DATA_DIR = path.resolve(process.env.MAZEX_DATA_DIR || path.join(ROOT, 'data'));
const PRIVATE_VAULT_ROOT = path.resolve(process.env.MAZEX_PRIVATE_VAULT_DIR || path.join(BASE_DATA_DIR, 'mazexchain_private'));
const PRIVATE_VAULT_VERSION = 'mazexchain-private-vault-1.0';
const PRIVATE_VAULT_ENABLED = !['0', 'false', 'off'].includes(
  String(process.env.MAZEX_PRIVATE_VAULT || '1').toLowerCase(),
);

function nowIso() {
  return new Date().toISOString();
}

function cloneJson(value) {
  return JSON.parse(JSON.stringify(value));
}

function ensureDir(dir) {
  fs.mkdirSync(dir, { recursive: true });
}

function atomicWriteJson(filePath, value) {
  ensureDir(path.dirname(filePath));
  const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
  fs.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
  fs.renameSync(tmp, filePath);
}

function stepKey(layerNo, stepNo) {
  return `${Number(layerNo)}:${Number(stepNo)}`;
}

function parseStepKey(key = '') {
  const [layer, step] = String(key).split(':').map((value) => Math.max(1, Math.trunc(Number(value || 1))));
  return { layer_no: layer || 1, step_no: step || 1 };
}

function pad(value) {
  return String(Math.max(1, Math.trunc(Number(value || 1)))).padStart(6, '0');
}

function stepFileName(key) {
  const parsed = parseStepKey(key);
  return `step_${pad(parsed.layer_no)}_${pad(parsed.step_no)}.json`;
}

function vaultPaths(root = PRIVATE_VAULT_ROOT) {
  return {
    root,
    steps: path.join(root, 'steps'),
    current: path.join(root, 'current_step.json'),
    index: path.join(root, 'index.json'),
  };
}

function encryptionKey() {
  const raw = process.env.MAZEX_PRIVATE_VAULT_KEY || '';
  if (!raw) return null;
  return crypto.createHash('sha256').update(raw, 'utf8').digest();
}

function encryptPayload(payload) {
  const key = encryptionKey();
  if (!key) {
    return {
      encrypted: false,
      payload,
    };
  }
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  const plaintext = Buffer.from(JSON.stringify(payload), 'utf8');
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
  const tag = cipher.getAuthTag();
  return {
    encrypted: true,
    algorithm: 'aes-256-gcm',
    iv: iv.toString('base64'),
    tag: tag.toString('base64'),
    ciphertext: ciphertext.toString('base64'),
  };
}

function decryptPayload(container) {
  if (!container?.encrypted) return container?.payload || container;
  const key = encryptionKey();
  if (!key) throw new Error('MAZEX_PRIVATE_VAULT_KEY is required to read encrypted private vault data');
  const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(container.iv, 'base64'));
  decipher.setAuthTag(Buffer.from(container.tag, 'base64'));
  const plaintext = Buffer.concat([
    decipher.update(Buffer.from(container.ciphertext, 'base64')),
    decipher.final(),
  ]);
  return JSON.parse(plaintext.toString('utf8'));
}

function readVaultFile(filePath) {
  if (!fs.existsSync(filePath)) return null;
  return decryptPayload(JSON.parse(fs.readFileSync(filePath, 'utf8')));
}

function writeVaultFile(filePath, payload) {
  atomicWriteJson(filePath, {
    type: 'mazex.private_vault_record',
    version: PRIVATE_VAULT_VERSION,
    written_at: nowIso(),
    sealed: encryptPayload(payload),
  });
}

function readVaultRecord(filePath) {
  if (!fs.existsSync(filePath)) return null;
  const outer = JSON.parse(fs.readFileSync(filePath, 'utf8'));
  if (outer.sealed) return decryptPayload(outer.sealed);
  return decryptPayload(outer);
}

function currentPrivateRecordFromState(state = {}) {
  const key = state.currentStepKey || stepKey(state.currentLayer, state.currentStep);
  const officialStep = state.officialStepsByKey?.[key] || null;
  const seedSource = state.seedSourcesByKey?.[key] || null;
  const commitment = state.commitmentsByKey?.[key] || state.currentStepCommitment || null;
  if (!key || !officialStep || !seedSource || !commitment) return null;
  return {
    type: 'mazex.base1.private_current_step',
    version: PRIVATE_VAULT_VERSION,
    chain_id: state.chainId || null,
    genesis_hash: state.chain?.genesis_hash || state.genesisHash || null,
    season_seed: state.seasonSeed || null,
    current_step_key: key,
    layer_no: officialStep.layer_no,
    step_no: officialStep.step_no,
    formula_hash: officialStep.formula_hash,
    seed_source_hash: seedSource.source_hash,
    step_commit: commitment.step_commit,
    official_step: officialStep,
    seed_source: seedSource,
    commitment,
    record_hash: sha256({
      type: 'mazex.private_step_record',
      chain_id: state.chainId || null,
      key,
      formula_hash: officialStep.formula_hash,
      seed_source_hash: seedSource.source_hash,
      step_commit: commitment.step_commit,
    }),
  };
}

function writeBase1PrivateVaultState(state, { root = PRIVATE_VAULT_ROOT } = {}) {
  if (!PRIVATE_VAULT_ENABLED) return { ok: true, skipped: true, reason: 'private_vault_disabled' };
  const record = currentPrivateRecordFromState(state);
  if (!record) return { ok: true, skipped: true, reason: 'no_private_step_in_state' };
  const paths = vaultPaths(root);
  ensureDir(paths.steps);
  const fileName = stepFileName(record.current_step_key);
  writeVaultFile(paths.current, record);
  writeVaultFile(path.join(paths.steps, fileName), record);
  const existing = fs.existsSync(paths.index) ? JSON.parse(fs.readFileSync(paths.index, 'utf8')) : {};
  const entries = Array.isArray(existing.entries) ? existing.entries : [];
  const filtered = entries.filter((entry) => entry.current_step_key !== record.current_step_key);
  filtered.push({
    current_step_key: record.current_step_key,
    layer_no: record.layer_no,
    step_no: record.step_no,
    formula_hash: record.formula_hash,
    seed_source_hash: record.seed_source_hash,
    step_commit: record.step_commit,
    record_hash: record.record_hash,
    file: `steps/${fileName}`,
    updated_at: nowIso(),
  });
  filtered.sort((a, b) => a.layer_no - b.layer_no || a.step_no - b.step_no);
  atomicWriteJson(paths.index, {
    version: PRIVATE_VAULT_VERSION,
    updated_at: nowIso(),
    encrypted_records: Boolean(encryptionKey()),
    entries: filtered,
  });
  return {
    ok: true,
    root,
    current_step_key: record.current_step_key,
    encrypted: Boolean(encryptionKey()),
  };
}

function hydrateBase1PrivateVaultState(state, { root = PRIVATE_VAULT_ROOT } = {}) {
  if (!state || typeof state !== 'object' || !PRIVATE_VAULT_ENABLED) return state;
  const key = state.currentStepKey || stepKey(state.currentLayer, state.currentStep);
  if (state.officialStepsByKey?.[key] && state.seedSourcesByKey?.[key]) return state;
  const paths = vaultPaths(root);
  let record = readVaultRecord(path.join(paths.steps, stepFileName(key)));
  if (!record) record = readVaultRecord(paths.current);
  if (!record || record.current_step_key !== key) return state;
  const next = cloneJson(state);
  next.seasonSeed ||= record.season_seed;
  next.officialStepsByKey ||= {};
  next.seedSourcesByKey ||= {};
  next.commitmentsByKey ||= {};
  next.officialStepsByKey[key] = record.official_step;
  next.seedSourcesByKey[key] = record.seed_source;
  next.commitmentsByKey[key] = record.commitment;
  next.currentFormulaHash ||= record.formula_hash;
  next.currentSeedSourceHash ||= record.seed_source_hash;
  next.currentStepCommitment ||= record.commitment;
  return next;
}

function sanitizeBase1PublicState(state) {
  const publicState = cloneJson(state);
  delete publicState.seasonSeed;
  publicState.officialStepsByKey = {};
  publicState.seedSourcesByKey = {};
  publicState.commitmentsByKey = {};
  publicState.privateVault = {
    required_for_local_verifier: true,
    public_state_sanitized: true,
  };
  return publicState;
}

function privateVaultStatus(root = PRIVATE_VAULT_ROOT) {
  const paths = vaultPaths(root);
  const index = fs.existsSync(paths.index) ? JSON.parse(fs.readFileSync(paths.index, 'utf8')) : null;
  return {
    ok: true,
    enabled: PRIVATE_VAULT_ENABLED,
    root,
    exists: fs.existsSync(root),
    encrypted: Boolean(encryptionKey()),
    current_exists: fs.existsSync(paths.current),
    step_record_count: index?.entries?.length || 0,
  };
}

module.exports = {
  PRIVATE_VAULT_ROOT,
  PRIVATE_VAULT_VERSION,
  hydrateBase1PrivateVaultState,
  privateVaultStatus,
  sanitizeBase1PublicState,
  vaultPaths,
  writeBase1PrivateVaultState,
};
