const vm = require("vm");
const { spawn } = require("child_process");
const fs = require("fs");
const os = require("os");
const path = require("path");
const {
  prepareJavaScriptCommand,
  preparePythonCommand,
  resolveJavaScriptHandler
} = require("./command-code");

function buildCommandContext({ platform, user, message, args, argsText }) {
  return {
    platform,
    user,
    message,
    args: args || [],
    argsText: argsText || ""
  };
}

async function runAdvancedCommand({ code, language }, ctx) {
  if (language === "python") {
    return await runPythonCommand(code, ctx);
  }
  return await runJsCommand(code, ctx);
}

async function runJsCommand(code, ctx) {
  const logs = [];
  const safeConsole = {
    log: (...args) => logs.push(args.join(" "))
  };
  const sandbox = {
    ctx,
    console: safeConsole,
    module: { exports: {} },
    exports: {}
  };
  const context = vm.createContext(sandbox);
  const script = new vm.Script(prepareJavaScriptCommand(code), { filename: "command.js" });
  script.runInContext(context, { timeout: 1000 });

  const handler = resolveJavaScriptHandler(context);
  if (typeof handler !== "function") {
    throw new Error("Dynamic command did not create a runnable handler.");
  }
  const result = handler(ctx);
  if (result && typeof result.then === "function") {
    return await promiseWithTimeout(result, 1500);
  }
  return result;
}

async function runPythonCommand(code, ctx) {
  return await new Promise((resolve, reject) => {
    const payload = JSON.stringify(ctx);
    const encoded = Buffer.from(preparePythonCommand(code), "utf8").toString("base64");
    const script = `
import base64, json, sys, traceback
ctx = json.loads(sys.stdin.read() or "{}")
code = base64.b64decode("${encoded}").decode("utf-8")
globals_dict = {}
try:
    exec(code, globals_dict)
    if "run" not in globals_dict:
        raise Exception("Dynamic command did not create a runnable handler.")
    result = globals_dict["run"](ctx)
    if result is None:
        sys.exit(0)
    if isinstance(result, (dict, list)):
        print(json.dumps(result))
    else:
        print(str(result))
except Exception:
    traceback.print_exc()
    sys.exit(2)
`;

    const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-cmd-"));
    const filePath = path.join(tmpDir, "runner.py");
    fs.writeFileSync(filePath, script, "utf8");

    const child = spawn("python", ["-u", filePath], {
      stdio: ["pipe", "pipe", "pipe"]
    });
    let stdout = "";
    let stderr = "";
    const timeout = setTimeout(() => {
      child.kill("SIGKILL");
      reject(new Error("Python command timed out."));
    }, 2000);

    child.stdout.on("data", (chunk) => {
      stdout += chunk.toString();
    });
    child.stderr.on("data", (chunk) => {
      stderr += chunk.toString();
    });
    child.on("error", (error) => {
      clearTimeout(timeout);
      cleanupTemp(tmpDir);
      reject(error);
    });
    child.on("close", (code) => {
      clearTimeout(timeout);
      cleanupTemp(tmpDir);
      if (code && code !== 0) {
        reject(new Error(stderr || "Python command failed."));
        return;
      }
      resolve(stdout.trim());
    });

    child.stdin.write(payload);
    child.stdin.end();
  });
}

function cleanupTemp(dir) {
  try {
    fs.rmSync(dir, { recursive: true, force: true });
  } catch {
    // Ignore cleanup errors.
  }
}

function normalizeCommandResult(result) {
  if (result === null || result === undefined) {
    return "";
  }
  if (typeof result === "string") {
    return result;
  }
  if (typeof result === "number" || typeof result === "boolean") {
    return String(result);
  }
  if (typeof result === "object" && result.content) {
    return String(result.content);
  }
  try {
    return JSON.stringify(result);
  } catch {
    return String(result);
  }
}

function promiseWithTimeout(promise, timeoutMs) {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      reject(new Error("Command timed out."));
    }, timeoutMs);
    promise
      .then((value) => {
        clearTimeout(timeout);
        resolve(value);
      })
      .catch((error) => {
        clearTimeout(timeout);
        reject(error);
      });
  });
}

module.exports = {
  buildCommandContext,
  runAdvancedCommand,
  normalizeCommandResult
};
