'use strict';

const { execSync } = require('child_process');
const fs           = require('fs');
const crypto       = require('crypto');
const path         = require('path');

const IS_WINDOWS = process.platform === 'win32';
const AES_PREFIX = 'AESGCM:v1:';

function generateSecret(bytes = 64) {
  return crypto.randomBytes(bytes).toString('hex');
}

function encrypt(plainText) {
  if (!IS_WINDOWS) {
    const keyMaterial = process.env.ARKHON_SECRETS_KEY;
    if (keyMaterial) {
      const salt = crypto.randomBytes(16);
      const iv = crypto.randomBytes(12);
      const key = crypto.scryptSync(keyMaterial, salt, 32);
      const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
      const encrypted = Buffer.concat([cipher.update(String(plainText), 'utf8'), cipher.final()]);
      const tag = cipher.getAuthTag();
      return `${AES_PREFIX}${salt.toString('base64')}:${iv.toString('base64')}:${tag.toString('base64')}:${encrypted.toString('base64')}`;
    }
    if (process.env.NODE_ENV === 'production') {
      throw new Error('ARKHON_SECRETS_KEY requerido para cifrar credenciales fuera de Windows en produccion');
    }
    const b = Buffer.from(plainText, 'utf8');
    return `DEV:${b.toString('base64')}`;
  }

  // FIX CRITICO: no interpolar plainText en el comando de PowerShell.
  // Escribimos el texto plano a un archivo temporal, lo ciframos desde ahí
  // y limpiamos el temp. Elimina cualquier riesgo de PS injection.
  const os     = require('os');
  const tmpIn  = path.join(os.tmpdir(), `arkhon_enc_${crypto.randomBytes(8).toString('hex')}.tmp`);
  const tmpOut = path.join(os.tmpdir(), `arkhon_enc_${crypto.randomBytes(8).toString('hex')}.tmp`);
  try {
    fs.writeFileSync(tmpIn, plainText, { encoding: 'utf8', mode: 0o600 });
    const script = `
      Add-Type -AssemblyName System.Security;
      $bytes = [System.IO.File]::ReadAllBytes('${tmpIn.replace(/'/g, "''")}');
      $enc   = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $null, 'LocalMachine');
      [System.IO.File]::WriteAllText('${tmpOut.replace(/'/g, "''")}', [Convert]::ToBase64String($enc))
    `.trim().replace(/\n\s+/g, ' ');
    execSync(`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "${script}"`);
    return fs.readFileSync(tmpOut, 'utf8').trim();
  } finally {
    try { fs.unlinkSync(tmpIn);  } catch (_) {}
    try { fs.unlinkSync(tmpOut); } catch (_) {}
  }
}

function decrypt(base64) {
  if (!IS_WINDOWS && base64.startsWith(AES_PREFIX)) {
    const keyMaterial = process.env.ARKHON_SECRETS_KEY;
    if (!keyMaterial) throw new Error('ARKHON_SECRETS_KEY requerido para descifrar credenciales AES-GCM');
    const parts = base64.slice(AES_PREFIX.length).split(':');
    if (parts.length !== 4) throw new Error('credentialStore: formato AES-GCM invalido');
    const [saltB64, ivB64, tagB64, encryptedB64] = parts;
    const key = crypto.scryptSync(keyMaterial, Buffer.from(saltB64, 'base64'), 32);
    const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(ivB64, 'base64'));
    decipher.setAuthTag(Buffer.from(tagB64, 'base64'));
    return Buffer.concat([decipher.update(Buffer.from(encryptedB64, 'base64')), decipher.final()]).toString('utf8');
  }

  if (!IS_WINDOWS || base64.startsWith('DEV:')) {
    if (process.env.NODE_ENV === 'production' && base64.startsWith('DEV:')) {
      throw new Error('credentialStore: formato DEV no permitido en produccion');
    }
    const b64 = base64.startsWith('DEV:') ? base64.slice(4) : base64;
    return Buffer.from(b64, 'base64').toString('utf8');
  }

  const script = `
    Add-Type -AssemblyName System.Security;
    $bytes = [Convert]::FromBase64String('${base64}');
    $dec   = [System.Security.Cryptography.ProtectedData]::Unprotect($bytes, $null, 'LocalMachine');
    [System.Text.Encoding]::UTF8.GetString($dec)
  `.trim().replace(/\n\s+/g, ' ');

  return execSync(`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "${script}"`).toString().trim();
}

function saveCredentials(filePath, credentials) {
  const dir = path.dirname(filePath);
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
  const json = JSON.stringify(credentials);
  const enc  = encrypt(json);
  fs.writeFileSync(filePath, enc, 'utf8');
  if (IS_WINDOWS) {
    try {
      execSync(`icacls "${filePath}" /inheritance:r /grant:r "SYSTEM":F "Administrators":F "%USERNAME%":F`, { stdio: 'ignore' });
    } catch (_) {}
  }
}

function loadCredentials(filePath) {
  if (!fs.existsSync(filePath)) {
    throw new Error(`credentialStore: archivo no encontrado: ${filePath}`);
  }
  const enc  = fs.readFileSync(filePath, 'utf8').trim();
  const json = decrypt(enc);
  return JSON.parse(json);
}

function injectCredentials(filePath) {
  try {
    const creds = loadCredentials(filePath);
    for (const [key, value] of Object.entries(creds)) {
      if (!process.env[key]) process.env[key] = value;
    }
  } catch (err) {
    if (err.message.includes('no encontrado')) return;
    throw err;
  }
}

module.exports = { encrypt, decrypt, saveCredentials, loadCredentials, injectCredentials, generateSecret };
