/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
const { getAsset } = require('node:sea');
const process = require('node:process');
const nodeModule = require('node:module');
const path = require('node:path');
const { pathToFileURL } = require('node:url');
const fs = require('node:fs');
const os = require('node:os');
const crypto = require('node:crypto');

// --- Helper Functions ---

/**
 * Strips the "ghost" argument that Node SEA sometimes injects (argv[2] == argv[0]).
 * @param {string[]} argv
 * @param {string} execPath
 * @param {function} resolveFn
 * @returns {boolean} True if an argument was removed.
 */
function sanitizeArgv(argv, execPath, resolveFn = path.resolve) {
  if (argv.length > 2) {
    const binaryAbs = execPath;
    const arg2Abs = resolveFn(argv[2]);
    if (binaryAbs === arg2Abs) {
      argv.splice(2, 1);
      return true;
    }
  }
  return false;
}

/**
 * Sanitizes a string for use in file paths.
 * @param {string} name
 * @returns {string}
 */
function getSafeName(name) {
  return (name || 'unknown').toString().replace(/[^a-zA-Z0-9.-]/g, '_');
}

/**
 * Verifies the integrity of the runtime directory against the manifest.
 * @param {string} dir
 * @param {object} manifest
 * @param {object} fsMod
 * @param {object} cryptoMod
 * @returns {boolean}
 */
function verifyIntegrity(dir, manifest, fsMod = fs, cryptoMod = crypto) {
  try {
    const calculateHash = (filePath) => {
      const hash = cryptoMod.createHash('sha256');
      const fd = fsMod.openSync(filePath, 'r');
      const buffer = new Uint8Array(65536); // 64KB
      try {
        let bytesRead = 0;
        while (
          (bytesRead = fsMod.readSync(fd, buffer, 0, buffer.length, null)) !== 0
        ) {
          hash.update(buffer.subarray(0, bytesRead));
        }
      } finally {
        fsMod.closeSync(fd);
      }
      return hash.digest('hex');
    };

    if (calculateHash(path.join(dir, 'gemini.mjs')) !== manifest.mainHash)
      return false;
    if (manifest.files) {
      for (const file of manifest.files) {
        if (calculateHash(path.join(dir, file.path)) !== file.hash)
          return false;
      }
    }
    return true;
  } catch (_e) {
    return false;
  }
}

/**
 * Prepares the runtime directory, extracting assets if necessary.
 * @param {object} manifest
 * @param {function} getAssetFn
 * @param {object} deps Dependencies (fs, os, path, processEnv)
 * @returns {string} The path to the prepared runtime directory.
 */
function prepareRuntime(manifest, getAssetFn, deps = {}) {
  const fsMod = deps.fs || fs;
  const osMod = deps.os || os;
  const pathMod = deps.path || path;
  const processEnv = deps.processEnv || process.env;
  const processPid = deps.processPid || process.pid;
  const processUid =
    deps.processUid || (process.getuid ? process.getuid() : 'unknown');

  const version = manifest.version || '0.0.0';
  const safeVersion = getSafeName(version);
  const userInfo = osMod.userInfo();
  const username =
    userInfo.username || processEnv.USER || processUid || 'unknown';
  const safeUsername = getSafeName(username);

  let tempBase = osMod.tmpdir();

  if (process.platform === 'win32' && processEnv.LOCALAPPDATA) {
    const appDir = pathMod.join(processEnv.LOCALAPPDATA, 'Google', 'GeminiCLI');
    try {
      if (!fsMod.existsSync(appDir)) {
        fsMod.mkdirSync(appDir, { recursive: true, mode: 0o700 });
      }
      tempBase = appDir;
    } catch (_) {
      // Fallback to tmpdir
    }
  }

  const finalRuntimeDir = pathMod.join(
    tempBase,
    `gemini-runtime-${safeVersion}-${safeUsername}`,
  );

  let runtimeDir;
  let useExisting = false;

  const isSecure = (dir) => {
    try {
      const stat = fsMod.lstatSync(dir);
      if (!stat.isDirectory()) return false;
      if (processUid !== 'unknown' && stat.uid !== processUid) return false;
      // Skip strict permission check on Windows as it's unreliable with standard fs.stat
      if (process.platform !== 'win32' && (stat.mode & 0o777) !== 0o700)
        return false;
      return true;
    } catch (_) {
      return false;
    }
  };
  if (fsMod.existsSync(finalRuntimeDir)) {
    if (isSecure(finalRuntimeDir)) {
      if (
        verifyIntegrity(finalRuntimeDir, manifest, fsMod, deps.crypto || crypto)
      ) {
        runtimeDir = finalRuntimeDir;
        useExisting = true;
      } else {
        try {
          fsMod.rmSync(finalRuntimeDir, { recursive: true, force: true });
        } catch (_) {}
      }
    } else {
      try {
        fsMod.rmSync(finalRuntimeDir, { recursive: true, force: true });
      } catch (_) {}
    }
  }

  if (!useExisting) {
    const setupDir = pathMod.join(
      tempBase,
      `gemini-setup-${processPid}-${Date.now()}`,
    );

    try {
      fsMod.mkdirSync(setupDir, { recursive: true, mode: 0o700 });
      const writeToSetup = (assetKey, relPath) => {
        const content = getAssetFn(assetKey);
        if (!content) return;
        const destPath = pathMod.join(setupDir, relPath);
        const destDir = pathMod.dirname(destPath);
        if (!fsMod.existsSync(destDir))
          fsMod.mkdirSync(destDir, { recursive: true, mode: 0o700 });
        fsMod.writeFileSync(destPath, new Uint8Array(content), {
          mode: 0o755,
        });
      };
      writeToSetup('gemini.mjs', 'gemini.mjs');
      if (manifest.files) {
        for (const file of manifest.files) {
          writeToSetup(file.key, file.path);
        }
      }
      try {
        fsMod.renameSync(setupDir, finalRuntimeDir);
        runtimeDir = finalRuntimeDir;
      } catch (renameErr) {
        if (
          fsMod.existsSync(finalRuntimeDir) &&
          isSecure(finalRuntimeDir) &&
          verifyIntegrity(
            finalRuntimeDir,
            manifest,
            fsMod,
            deps.crypto || crypto,
          )
        ) {
          runtimeDir = finalRuntimeDir;
          try {
            fsMod.rmSync(setupDir, { recursive: true, force: true });
          } catch (_) {}
        } else {
          throw renameErr;
        }
      }
    } catch (e) {
      console.error(
        'Fatal Error: Failed to setup secure runtime. Please try running again and if error persists please reinstall.',
        e,
      );
      try {
        fsMod.rmSync(setupDir, { recursive: true, force: true });
      } catch (_) {}
      process.exit(1);
    }
  }

  return runtimeDir;
}

// --- Main Execution ---

async function main(getAssetFn = getAsset) {
  process.env.IS_BINARY = 'true';

  if (nodeModule.enableCompileCache) {
    nodeModule.enableCompileCache();
  }

  process.noDeprecation = true;

  sanitizeArgv(process.argv, process.execPath);

  const manifestJson = getAssetFn('manifest.json', 'utf8');
  if (!manifestJson) {
    console.error('Fatal Error: Corrupted binary. Please reinstall.');
    process.exit(1);
  }

  const manifest = JSON.parse(manifestJson);

  const runtimeDir = prepareRuntime(manifest, getAssetFn, {
    fs,
    os,
    path,
    processEnv: process.env,
    crypto,
  });

  const mainPath = path.join(runtimeDir, 'gemini.mjs');

  await import(pathToFileURL(mainPath).href).catch((err) => {
    console.error('Fatal Error: Failed to launch. Please reinstall.', err);
    console.error(err);
    process.exit(1);
  });
}

// Only execute if this is the main module (standard Node behavior)
// or if explicitly running as the SEA entry point (heuristic).
if (require.main === module) {
  main().catch((err) => {
    console.error('Unhandled error in sea-launch:', err);
    process.exit(1);
  });
}

module.exports = {
  sanitizeArgv,
  getSafeName,
  verifyIntegrity,
  prepareRuntime,
  main,
};
