import { find } from 'lodash-es';
import { base64ToString, uint8ArrayToBase64, stringToBase64 } from 'uint8array-extras';
import { g as getGitenvs, s as saveGitenvs } from '../gitenvs-nFXMnq5u.mjs';
import { webcrypto } from 'node:crypto';
import 'fs';
import 'fs/promises';
import 'path';
import 'zod';

const encryptEnvVar = async ({
  plaintext,
  publicKey
}) => {
  const symmetricEncrypted = await encryptSymmetric({ plaintext });
  const jsonWebKey = JSON.parse(base64ToString(publicKey));
  const pubKey = await globalThis.crypto.subtle.importKey(
    "jwk",
    // TODO: zod this
    jsonWebKey,
    {
      name: "RSA-OAEP",
      hash: { name: "SHA-256" }
    },
    false,
    ["wrapKey"]
  );
  const encryptedSymmetricKey = await crypto.subtle.wrapKey(
    "raw",
    symmetricEncrypted.symmetricKey,
    pubKey,
    {
      name: pubKey.algorithm.name
    }
  );
  const secretData = {
    encryptedValue: symmetricEncrypted.encryptedValue,
    iv: symmetricEncrypted.iv,
    encryptedSymmetricKey: uint8ArrayToBase64(
      new Uint8Array(encryptedSymmetricKey)
    )
  };
  const stringified = JSON.stringify(secretData);
  return stringToBase64(stringified);
};
const encryptSymmetric = async ({ plaintext }) => {
  const encoded = new TextEncoder().encode(plaintext);
  const symmetricKey = await globalThis.crypto.subtle.generateKey(
    {
      name: "AES-GCM",
      length: 256
    },
    true,
    ["encrypt"]
  );
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encrypted = await crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv
    },
    symmetricKey,
    encoded
  );
  return {
    iv: uint8ArrayToBase64(iv),
    encryptedValue: uint8ArrayToBase64(new Uint8Array(encrypted)),
    symmetricKey
  };
};

const POOL_SIZE_MULTIPLIER = 128;
let pool, poolOffset;
function fillPool(bytes) {
  if (!pool || pool.length < bytes) {
    pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
    webcrypto.getRandomValues(pool);
    poolOffset = 0;
  } else if (poolOffset + bytes > pool.length) {
    webcrypto.getRandomValues(pool);
    poolOffset = 0;
  }
  poolOffset += bytes;
}
function random(bytes) {
  fillPool((bytes -= 0));
  return pool.subarray(poolOffset - bytes, poolOffset)
}
function customRandom(alphabet, defaultSize, getRandom) {
  let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1;
  let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length);
  return (size = defaultSize) => {
    let id = '';
    while (true) {
      let bytes = getRandom(step);
      let i = step;
      while (i--) {
        id += alphabet[bytes[i] & mask] || '';
        if (id.length === size) return id
      }
    }
  }
}
function customAlphabet(alphabet, size = 21) {
  return customRandom(alphabet, size, random)
}

const nanoid = customAlphabet(
  "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
  22
);
const getNewEnvFileId = () => {
  return `envFile_${nanoid()}`;
};

const upsertEnvVarValue = async ({
  fileId,
  stage,
  key,
  value,
  encrypt
}) => {
  const gitenvs = await getGitenvs();
  const envStage = find(gitenvs.envStages, (s) => s.name === stage);
  if (!envStage) {
    throw new Error(`Env stage not found: ${stage}`);
  }
  const file = find(gitenvs.envFiles, (f) => f.id === fileId);
  if (!file) {
    throw new Error(`File not found: ${fileId}`);
  }
  let envVar = find(
    gitenvs.envVars,
    (v) => v.fileIds.includes(file.id) && v.key === key
  );
  if (!envVar) {
    envVar = {
      id: getNewEnvFileId(),
      fileIds: [file.id],
      key,
      values: {}
    };
    gitenvs.envVars.push(envVar);
  }
  const valueToSave = encrypt ? await encryptEnvVar({
    plaintext: value,
    publicKey: envStage.publicKey
  }) : value;
  envVar.values[stage] = {
    value: valueToSave,
    encrypted: encrypt
  };
  await saveGitenvs(gitenvs);
};

export { upsertEnvVarValue };
