import { execFileSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

type RestoreEntry = { key: string; value: string | undefined };

function restoreEnv(entries: RestoreEntry[]): void {
  for (const { key, value } of entries) {
    if (value === undefined) {
      delete process.env[key];
    } else {
      process.env[key] = value;
    }
  }
}

function loadProfileEnv(): void {
  const profilePath = path.join(os.homedir(), ".profile");
  if (!fs.existsSync(profilePath)) {
    return;
  }
  try {
    const output = execFileSync(
      "/bin/bash",
      ["-lc", `set -a; source "${profilePath}" >/dev/null 2>&1; env -0`],
      { encoding: "utf8" },
    );
    const entries = output.split("\0");
    let applied = 0;
    for (const entry of entries) {
      if (!entry) {
        continue;
      }
      const idx = entry.indexOf("=");
      if (idx <= 0) {
        continue;
      }
      const key = entry.slice(0, idx);
      if (!key || (process.env[key] ?? "") !== "") {
        continue;
      }
      process.env[key] = entry.slice(idx + 1);
      applied += 1;
    }
    if (applied > 0) {
      console.log(`[live] loaded ${applied} env vars from ~/.profile`);
    }
  } catch {
    // ignore profile load failures
  }
}

export function installTestEnv(): { cleanup: () => void; tempHome: string } {
  const live =
    process.env.LIVE === "1" ||
    process.env.OPENCLAW_LIVE_TEST === "1" ||
    process.env.OPENCLAW_LIVE_GATEWAY === "1";

  // Live tests must use the real user environment (keys, profiles, config).
  // The default test env isolates HOME to avoid touching real state.
  if (live) {
    loadProfileEnv();
    return { cleanup: () => {}, tempHome: process.env.HOME ?? "" };
  }

  const restore: RestoreEntry[] = [
    { key: "OPENCLAW_TEST_FAST", value: process.env.OPENCLAW_TEST_FAST },
    { key: "HOME", value: process.env.HOME },
    { key: "USERPROFILE", value: process.env.USERPROFILE },
    { key: "XDG_CONFIG_HOME", value: process.env.XDG_CONFIG_HOME },
    { key: "XDG_DATA_HOME", value: process.env.XDG_DATA_HOME },
    { key: "XDG_STATE_HOME", value: process.env.XDG_STATE_HOME },
    { key: "XDG_CACHE_HOME", value: process.env.XDG_CACHE_HOME },
    { key: "OPENCLAW_STATE_DIR", value: process.env.OPENCLAW_STATE_DIR },
    { key: "OPENCLAW_CONFIG_PATH", value: process.env.OPENCLAW_CONFIG_PATH },
    { key: "OPENCLAW_GATEWAY_PORT", value: process.env.OPENCLAW_GATEWAY_PORT },
    { key: "OPENCLAW_BRIDGE_ENABLED", value: process.env.OPENCLAW_BRIDGE_ENABLED },
    { key: "OPENCLAW_BRIDGE_HOST", value: process.env.OPENCLAW_BRIDGE_HOST },
    { key: "OPENCLAW_BRIDGE_PORT", value: process.env.OPENCLAW_BRIDGE_PORT },
    { key: "OPENCLAW_CANVAS_HOST_PORT", value: process.env.OPENCLAW_CANVAS_HOST_PORT },
    { key: "OPENCLAW_TEST_HOME", value: process.env.OPENCLAW_TEST_HOME },
    { key: "TELEGRAM_BOT_TOKEN", value: process.env.TELEGRAM_BOT_TOKEN },
    { key: "DISCORD_BOT_TOKEN", value: process.env.DISCORD_BOT_TOKEN },
    { key: "SLACK_BOT_TOKEN", value: process.env.SLACK_BOT_TOKEN },
    { key: "SLACK_APP_TOKEN", value: process.env.SLACK_APP_TOKEN },
    { key: "SLACK_USER_TOKEN", value: process.env.SLACK_USER_TOKEN },
    { key: "COPILOT_GITHUB_TOKEN", value: process.env.COPILOT_GITHUB_TOKEN },
    { key: "GH_TOKEN", value: process.env.GH_TOKEN },
    { key: "GITHUB_TOKEN", value: process.env.GITHUB_TOKEN },
    { key: "NODE_OPTIONS", value: process.env.NODE_OPTIONS },
  ];

  const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-test-home-"));

  process.env.HOME = tempHome;
  process.env.USERPROFILE = tempHome;
  process.env.OPENCLAW_TEST_HOME = tempHome;
  process.env.OPENCLAW_TEST_FAST = "1";

  // Ensure test runs never touch the developer's real config/state, even if they have overrides set.
  delete process.env.OPENCLAW_CONFIG_PATH;
  // Prefer deriving state dir from HOME so nested tests that change HOME also isolate correctly.
  delete process.env.OPENCLAW_STATE_DIR;
  // Prefer test-controlled ports over developer overrides (avoid port collisions across tests/workers).
  delete process.env.OPENCLAW_GATEWAY_PORT;
  delete process.env.OPENCLAW_BRIDGE_ENABLED;
  delete process.env.OPENCLAW_BRIDGE_HOST;
  delete process.env.OPENCLAW_BRIDGE_PORT;
  delete process.env.OPENCLAW_CANVAS_HOST_PORT;
  // Avoid leaking real GitHub/Copilot tokens into non-live test runs.
  delete process.env.TELEGRAM_BOT_TOKEN;
  delete process.env.DISCORD_BOT_TOKEN;
  delete process.env.SLACK_BOT_TOKEN;
  delete process.env.SLACK_APP_TOKEN;
  delete process.env.SLACK_USER_TOKEN;
  delete process.env.COPILOT_GITHUB_TOKEN;
  delete process.env.GH_TOKEN;
  delete process.env.GITHUB_TOKEN;
  // Avoid leaking local dev tooling flags into tests (e.g. --inspect).
  delete process.env.NODE_OPTIONS;

  // Windows: prefer the default state dir so auth/profile tests match real paths.
  if (process.platform === "win32") {
    process.env.OPENCLAW_STATE_DIR = path.join(tempHome, ".openclaw");
  }

  process.env.XDG_CONFIG_HOME = path.join(tempHome, ".config");
  process.env.XDG_DATA_HOME = path.join(tempHome, ".local", "share");
  process.env.XDG_STATE_HOME = path.join(tempHome, ".local", "state");
  process.env.XDG_CACHE_HOME = path.join(tempHome, ".cache");

  const cleanup = () => {
    restoreEnv(restore);
    try {
      fs.rmSync(tempHome, { recursive: true, force: true });
    } catch {
      // ignore cleanup errors
    }
  };

  return { cleanup, tempHome };
}

export function withIsolatedTestHome(): { cleanup: () => void; tempHome: string } {
  return installTestEnv();
}
