#!/usr/bin/env node
import { base64ToString, base64ToUint8Array } from 'uint8array-extras';
import { g as getGitenvs, a as getCwd, b as getPassphraseEnvName, c as getGitenvsVersion, l as latestGitenvsVersion, G as Gitenvs1, s as saveGitenvs, d as getIsLatestGitenvsVersion, e as GITENVS_STAGE_ENV_NAME } from '../gitenvs-nFXMnq5u.mjs';
import { readFile, writeFile, mkdir } from 'fs/promises';
import { join, dirname } from 'path';
import { execSync } from 'child_process';
import { Command } from 'commander';
import { randomBytes } from 'crypto';
import { fileURLToPath } from 'url';
import 'fs';
import 'zod';

const decryptEnvVar = async ({
  encrypted,
  encryptedPrivateKey: privateKeyData,
  passphrase
}) => {
  try {
    const { iv, encryptedPrivateKey } = JSON.parse(
      base64ToString(privateKeyData)
    );
    const passphraseKey = await globalThis.crypto.subtle.importKey(
      "raw",
      base64ToUint8Array(passphrase),
      {
        name: "AES-GCM",
        length: 256
      },
      false,
      ["unwrapKey"]
    );
    const privateKey = await globalThis.crypto.subtle.unwrapKey(
      "pkcs8",
      base64ToUint8Array(encryptedPrivateKey),
      passphraseKey,
      {
        name: "AES-GCM",
        iv: base64ToUint8Array(iv)
      },
      {
        name: "RSA-OAEP",
        hash: { name: "SHA-256" }
      },
      false,
      ["unwrapKey"]
    );
    const secretData = JSON.parse(base64ToString(encrypted));
    const symmetricKey = await globalThis.crypto.subtle.unwrapKey(
      "raw",
      base64ToUint8Array(secretData.encryptedSymmetricKey),
      privateKey,
      {
        name: "RSA-OAEP"
      },
      {
        name: "AES-GCM",
        length: 256
      },
      false,
      ["decrypt"]
    );
    const decrypted = await globalThis.crypto.subtle.decrypt(
      {
        name: "AES-GCM",
        iv: base64ToUint8Array(secretData.iv)
      },
      symmetricKey,
      base64ToUint8Array(secretData.encryptedValue)
    );
    const plaintext = new TextDecoder().decode(decrypted);
    return plaintext;
  } catch (error) {
    console.log("error", error);
  }
};

const getIsGitenvsExisting = async () => {
  try {
    await getGitenvs();
    return true;
  } catch (error) {
    if (process.env.NODE_ENV === "development") {
      console.error(error);
    }
    return false;
  }
};

const PASSPHRASE_FILE_NAME = "gitenvs.passphrases.json";
const getPassphrase = async ({
  stage,
  passphrase,
  passphrasePath = join(getCwd(), PASSPHRASE_FILE_NAME)
}) => {
  const getFromEnvVars = () => {
    const envName = getPassphraseEnvName({ stage });
    return process.env[envName];
  };
  const getFromFile = async () => {
    const currentFileContent = await readFile(passphrasePath, "utf-8").then((res) => JSON.parse(res)).catch(() => []);
    const requestedPassphrase = currentFileContent.find(
      (p) => p.stageName === stage
    )?.passphrase;
    return requestedPassphrase;
  };
  return passphrase ?? getFromEnvVars() ?? await getFromFile();
};

const [major] = process.versions.node.split(".").map(Number);
if (major && major < 20) {
  console.error("\u274C Gitenvs: Node version must be >= 20");
  process.exit(1);
}
const getGitenvsUiEnvVars = () => ({
  ...process.env,
  GITENVS_DIR: getCwd(),
  GITENVS_ENCRYPTION_TOKEN: randomBytes(32).toString("hex"),
  PORT: "1337"
});
const program = new Command();
program.name("gitenvs").description("Save your env variables in git \u2013 encrypted!");
program.command("migrate").action(async () => {
  let isLatestVersion = false;
  while (!isLatestVersion) {
    let currentVersion;
    try {
      currentVersion = await getGitenvsVersion();
    } catch (error) {
      console.error("\u274C Gitenvs: cannot read version");
      process.exit(1);
    }
    if (currentVersion === latestGitenvsVersion) {
      isLatestVersion = true;
      console.log(`\u2705 Gitenvs: On latest version v${latestGitenvsVersion}`);
      break;
    }
    console.log(
      `\u{1F504} Gitenvs: Migrating from v${currentVersion} to v${currentVersion + 1}`
    );
    const gitenvsContent = await readFile(
      join(getCwd(), "gitenvs.json"),
      "utf-8"
    );
    const jsonParsedGitenvsContent = JSON.parse(gitenvsContent);
    if (currentVersion === 1) {
      const gitenvs1 = Gitenvs1.parse(jsonParsedGitenvsContent);
      const migrated = {
        ...gitenvs1,
        version: "2",
        envVars: gitenvs1.envVars.map((envVar) => {
          const { fileId, id, key, values } = envVar;
          return {
            id,
            fileIds: [fileId],
            key,
            values
          };
        })
      };
      await saveGitenvs(migrated);
    }
  }
});
program.command("create").description("Creates env files").option(
  "--stage <stage>",
  "Example: production, staging, development",
  "development"
).option("--passphrase <passphrase>").option("--passphrasePath <passphrasePath>").action(
  async (options) => {
    const gitenvsExists = await getIsGitenvsExisting();
    if (!gitenvsExists) {
      console.error("\u274C Gitenvs: gitenvs.json not found");
      process.exit(1);
    }
    const isLatestGitenvsVersion = await getIsLatestGitenvsVersion();
    if (!isLatestGitenvsVersion) {
      console.error(
        `\u274C Gitenvs: Version is not latest. Please run \`gitenvs migrate\` to migrate to the latest version.`
      );
      process.exit(1);
    }
    const gitenvs = await getGitenvs();
    const stage = process.env[GITENVS_STAGE_ENV_NAME] ?? options.stage;
    if (!stage) {
      console.error(
        `Stage is required. Set it with --stage <stage> or with env var: ${GITENVS_STAGE_ENV_NAME}`
      );
      process.exit(1);
    }
    const envStage = gitenvs.envStages.find(
      (envStage2) => envStage2.name === stage
    );
    if (!envStage) {
      console.error(`Env stage ${stage} not found`);
      process.exit(1);
    }
    const passphrase = await getPassphrase({
      stage,
      passphrase: options.passphrase,
      passphrasePath: options.passphrasePath
    });
    if (!passphrase) {
      console.error(
        `Requested passphrase for stage ${stage} not found in ${options.passphrasePath ?? join(getCwd(), PASSPHRASE_FILE_NAME)}`
      );
      process.exit(1);
    }
    const promises = [];
    for (const envFile of gitenvs.envFiles) {
      const envVars = gitenvs.envVars.filter(
        (envVar) => envVar.fileIds.includes(envFile.id)
      );
      console.log(
        `\u{1F512} Gitenvs: Creating ${envFile.name} file for stage: ${stage}`
      );
      const dotenvVars = await Promise.all(
        envVars.map(async (envVar) => {
          const value = envVar.values[stage];
          if (!value) return { key: envVar.key, value: "" };
          const envVarValue2 = value.encrypted ? await decryptEnvVar({
            encrypted: value.value,
            encryptedPrivateKey: envStage.encryptedPrivateKey,
            passphrase
          }) ?? "" : value.value;
          return {
            key: envVar.key,
            value: envVarValue2,
            isFunction: value.isFunction
          };
        })
      );
      switch (envFile.type) {
        case "dotenv": {
          const dotenvContent = dotenvVars.map((dotenvVar) => {
            console.log(
              `\u{1F512} Gitenvs: Writing "${dotenvVar.key}" to ${envFile.filePath}`
            );
            let envVarValue = dotenvVar.value;
            if (dotenvVar.isFunction) {
              try {
                envVarValue = eval(envVarValue);
              } catch (error) {
                console.error(
                  `\u274C Gitenvs: Error evaluating function "${dotenvVar.key}"`
                );
                console.error(error);
                process.exit(1);
              }
            }
            const includesCommentCharacter = envVarValue.includes("#") ?? false;
            const includesDoubleQuote = envVarValue.includes('"') ?? false;
            const includesSingleQuote = envVarValue.includes("'") ?? false;
            let wrapWith = "";
            if (includesCommentCharacter) {
              if (includesSingleQuote && includesDoubleQuote) {
                throw new Error(
                  `\u274C Gitenvs: "${dotenvVar.key}" includes both single and double quotes and a comment character. This is not supported and will result in unexpected values.`
                );
              }
              if (includesDoubleQuote) {
                wrapWith = "'";
              } else {
                wrapWith = '"';
              }
              console.log(
                `\u26A0\uFE0F Gitenvs: wrapping "${dotenvVar.key}" with ${wrapWith} quotes because it includes a comment character`
              );
            }
            return `${dotenvVar.key}=${wrapWith}${envVarValue}${wrapWith}`;
          }).join("\n");
          promises.push(
            writeFile(
              join(getCwd(), envFile.filePath),
              dotenvContent,
              "utf-8"
            )
          );
          break;
        }
        case ".ts": {
          const tsEnvContent = dotenvVars.map((envVar) => {
            console.log(
              `\u{1F512} Gitenvs: Writing "${envVar.key}" to ${envFile.filePath}`
            );
            const includesDoubleQuote2 = envVar.value?.includes('"') ?? false;
            const includesSingleQuote2 = envVar.value?.includes("'") ?? false;
            const includesBacktick = envVar.value?.includes("`") ?? false;
            let wrapWith2 = "'";
            if (includesSingleQuote2 && includesDoubleQuote2 && includesBacktick) {
              throw new Error(
                `\u274C Gitenvs: "${envVar.key}" includes all string encasing characters. This is not supported and will result in unexpected values.`
              );
            }
            if (includesSingleQuote2) {
              if (!includesBacktick) {
                wrapWith2 = "`";
              } else {
                wrapWith2 = '"';
              }
            }
            return `export const ${envVar.key} = process.env.${envVar.key} ?? ${wrapWith2}${envVar.value}${wrapWith2}`;
          }).join("\n");
          promises.push(
            writeFile(
              join(getCwd(), envFile.filePath),
              tsEnvContent,
              "utf-8"
            )
          );
          break;
        }
      }
      await Promise.allSettled(promises);
    }
  }
);
program.command("dev-ui").description("Starts a browser UI to edit env vars").action(() => {
  execSync("pnpm run dev-next", {
    stdio: "inherit",
    env: getGitenvsUiEnvVars()
  });
});
program.command("init-playground").description("Initializes the playground").action(async () => {
  await mkdir("playground", { recursive: true });
  await writeFile(
    "playground/package.json",
    JSON.stringify(
      {
        name: "playground",
        version: "1.0.0",
        private: true
      },
      null,
      2
    ),
    "utf-8"
  );
});
program.command("ui", { isDefault: true }).description("Starts a browser UI to edit env vars").action(async () => {
  const gitenvsExists2 = await getIsGitenvsExisting();
  if (gitenvsExists2) {
    const isLatestGitenvsVersion2 = await getIsLatestGitenvsVersion();
    if (!isLatestGitenvsVersion2) {
      console.error(
        `\u274C Gitenvs: Version is not latest. Please run \`gitenvs migrate\` to migrate to the latest version.`
      );
      process.exit(1);
    }
  }
  const nodePath = process.argv0;
  const currentDir = dirname(fileURLToPath(import.meta.url));
  execSync(`${nodePath} ${currentDir}/next/server.js`, {
    stdio: "inherit",
    env: getGitenvsUiEnvVars()
  });
});
program.parse();
