#!/usr/bin/env node
/**
 * OmniRoute — Zero-Config Bootstrap
 *
 * Auto-generates required secrets (JWT_SECRET, STORAGE_ENCRYPTION_KEY) if
 * missing or empty, persists them to {DATA_DIR}/server.env so they survive
 * restarts, Docker volume remounts, and upgrades.
 *
 * Works across all deployment modes:
 *   - npm / app runners:  called from run-standalone.mjs and run-next.mjs
 *   - Docker:     same, secrets persisted in mounted volume
 *   - Electron:   called from main.js startup, persisted in DATA_DIR
 *
 * Priority (lowest → highest):
 *   1. Auto-generated defaults
 *   2. {DATA_DIR}/server.env  (persisted on first boot)
 *   3. Preferred config .env  (DATA_DIR/.env -> ~/.omniroute/.env -> ./.env)
 *   4. process.env            (shell / Docker -e flags, highest priority)
 */

import { randomBytes } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { homedir } from "node:os";
import { join, resolve } from "node:path";

const require = createRequire(import.meta.url);

// ── OAuth secrets that are optional but warn if missing ─────────────────────
const OPTIONAL_OAUTH_SECRETS = [
  { key: "ANTIGRAVITY_OAUTH_CLIENT_SECRET", label: "Antigravity OAuth" },
  { key: "IFLOW_OAUTH_CLIENT_SECRET", label: "iFlow OAuth" },
  { key: "GEMINI_OAUTH_CLIENT_SECRET", label: "Gemini OAuth" },
];

// ── Resolve DATA_DIR (mirrors dataPaths.ts logic) ───────────────────────────
function resolveDataDir(overridePath, env = process.env) {
  if (overridePath?.trim()) return resolve(overridePath);

  const configured = env.DATA_DIR?.trim();
  if (configured) return resolve(configured);

  if (process.platform === "win32") {
    const appData = env.APPDATA || join(homedir(), "AppData", "Roaming");
    return join(appData, "omniroute");
  }

  const xdg = env.XDG_CONFIG_HOME?.trim();
  if (xdg) return join(resolve(xdg), "omniroute");

  return join(homedir(), ".omniroute");
}

function getPreferredEnvFilePath(env = process.env) {
  const candidates = [];

  if (env.DATA_DIR?.trim()) {
    candidates.push(join(resolve(env.DATA_DIR.trim()), ".env"));
  }

  candidates.push(join(resolveDataDir(null, env), ".env"));
  candidates.push(join(process.cwd(), ".env"));

  return candidates.find((filePath) => existsSync(filePath)) ?? null;
}

function hasEncryptedCredentials(dataDir) {
  const dbPath = join(dataDir, "storage.sqlite");
  if (!existsSync(dbPath)) return false;

  try {
    const Database = require("better-sqlite3");
    const db = new Database(dbPath, { readonly: true, fileMustExist: true });
    try {
      const row = db
        .prepare(
          `SELECT 1
             FROM provider_connections
            WHERE access_token LIKE 'enc:v1:%'
               OR refresh_token LIKE 'enc:v1:%'
               OR api_key LIKE 'enc:v1:%'
               OR id_token LIKE 'enc:v1:%'
            LIMIT 1`
        )
        .get();
      return !!row;
    } finally {
      db.close();
    }
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    throw new Error(`Unable to inspect existing database at ${dbPath}: ${message}`);
  }
}

// ── Parse a simple KEY=VALUE env file ───────────────────────────────────────
function parseEnvFile(filePath) {
  if (!existsSync(filePath)) return {};
  const env = {};
  const lines = readFileSync(filePath, "utf8").split(/\r?\n/);
  for (const line of lines) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith("#")) continue;
    const eqIdx = trimmed.indexOf("=");
    if (eqIdx < 1) continue;
    const key = trimmed.slice(0, eqIdx).trim();
    const val = trimmed.slice(eqIdx + 1).trim();
    env[key] = val;
  }
  return env;
}

// ── Write a simple KEY=VALUE env file ───────────────────────────────────────
function writeEnvFile(filePath, env) {
  const lines = [
    "# Auto-generated by OmniRoute bootstrap — do not delete",
    `# Created: ${new Date().toISOString()}`,
    "",
    ...Object.entries(env).map(([k, v]) => `${k}=${v}`),
    "",
  ];
  writeFileSync(filePath, lines.join("\n"), "utf8");
}

// ── Main bootstrap function ──────────────────────────────────────────────────
/**
 * @param {{ dataDirOverride?: string; quiet?: boolean }} options
 * @returns {Record<string, string>} merged env to pass to child process
 */
export function bootstrapEnv({ dataDirOverride, quiet = false } = {}) {
  const log = quiet ? () => {} : (msg) => process.stderr.write(`[bootstrap] ${msg}\n`);

  const preferredEnvPath = getPreferredEnvFilePath(process.env);
  const preferredEnv = preferredEnvPath ? parseEnvFile(preferredEnvPath) : {};
  const dataDir = resolveDataDir(dataDirOverride, { ...preferredEnv, ...process.env });
  const serverEnvPath = join(dataDir, "server.env");

  // ── Layer 1: Load persisted server.env ────────────────────────────────────
  let persisted = parseEnvFile(serverEnvPath);

  // ── Layer 2: Load the same preferred .env that the CLI wrapper uses ───────
  // This keeps run-next / run-standalone consistent with `bin/omniroute.mjs`.
  const merged = { ...persisted, ...preferredEnv, ...process.env };

  // ── Auto-generate required secrets ────────────────────────────────────────
  let needsPersist = false;

  if (!merged.JWT_SECRET?.trim()) {
    persisted.JWT_SECRET = randomBytes(64).toString("hex");
    merged.JWT_SECRET = persisted.JWT_SECRET;
    needsPersist = true;
    log("✨ JWT_SECRET auto-generated (first run)");
  }

  if (!merged.STORAGE_ENCRYPTION_KEY?.trim()) {
    if (hasEncryptedCredentials(dataDir)) {
      throw new Error(
        `Refusing to auto-generate STORAGE_ENCRYPTION_KEY: encrypted credentials already exist in ${join(
          dataDir,
          "storage.sqlite"
        )}. Restore the key via ${preferredEnvPath ?? "an appropriate .env file"}, ${serverEnvPath}, or process.env.`
      );
    }
    persisted.STORAGE_ENCRYPTION_KEY = randomBytes(32).toString("hex");
    merged.STORAGE_ENCRYPTION_KEY = persisted.STORAGE_ENCRYPTION_KEY;
    needsPersist = true;
    log("✨ STORAGE_ENCRYPTION_KEY auto-generated (first run)");
  }

  if (!merged.STORAGE_ENCRYPTION_KEY_VERSION?.trim()) {
    persisted.STORAGE_ENCRYPTION_KEY_VERSION = "v1";
    merged.STORAGE_ENCRYPTION_KEY_VERSION = persisted.STORAGE_ENCRYPTION_KEY_VERSION;
    needsPersist = true;
  }

  if (!merged.API_KEY_SECRET?.trim()) {
    persisted.API_KEY_SECRET = randomBytes(32).toString("hex");
    merged.API_KEY_SECRET = persisted.API_KEY_SECRET;
    needsPersist = true;
    log("✨ API_KEY_SECRET auto-generated (first run)");
  }

  // ── Persist new secrets ────────────────────────────────────────────────────
  if (needsPersist) {
    try {
      mkdirSync(dataDir, { recursive: true });
      // Only persist keys that we auto-generated (not .env or process.env vals)
      writeEnvFile(serverEnvPath, persisted);
      log(`📁 Secrets persisted to: ${serverEnvPath}`);
    } catch (e) {
      log(`⚠️  Could not persist secrets to ${serverEnvPath}: ${e.message}`);
    }
  }

  // ── Mark as bootstrapped ───────────────────────────────────────────────────
  if (needsPersist) {
    merged.OMNIROUTE_BOOTSTRAPPED = "true";
  }

  // ── Warn about missing optional OAuth secrets ──────────────────────────────
  const missingOauth = OPTIONAL_OAUTH_SECRETS.filter(({ key }) => !merged[key]?.trim());
  if (missingOauth.length > 0) {
    log("ℹ️  The following OAuth integrations are not configured:");
    for (const { key, label } of missingOauth) {
      log(`   • ${label} (${key}) — set in .env or ${serverEnvPath}`);
    }
    log("   These providers will not work until configured.");
  }

  // ── Warn about default password ────────────────────────────────────────────
  if (merged.INITIAL_PASSWORD === "CHANGEME" || !merged.INITIAL_PASSWORD?.trim()) {
    log("⚠️  INITIAL_PASSWORD is not set — using default 'CHANGEME'. Change it in Settings!");
  }

  return merged;
}

// ── CLI usage: node scripts/bootstrap-env.mjs ──────────────────────────────
if (process.argv[1] && process.argv[1].endsWith("bootstrap-env.mjs")) {
  const env = bootstrapEnv();
  process.stderr.write(`[bootstrap] Done. DATA_DIR resolved to: ${resolveDataDir()}\n`);
  process.stderr.write(`[bootstrap] JWT_SECRET length: ${env.JWT_SECRET?.length ?? 0}\n`);
  process.stderr.write(
    `[bootstrap] STORAGE_ENCRYPTION_KEY length: ${env.STORAGE_ENCRYPTION_KEY?.length ?? 0}\n`
  );
}
