import { exec } from "../ssh";
import type { DeployStrategy } from "./types";
import { fmtExec } from "./types";

// PM2 — `ref` là pm2 process name. Hỗ trợ format "user:proc" để chạy pm2 dưới user khác
// (PM2 daemon thường chạy under non-root như "onepiece", "ubuntu", "node").
// Dùng `sudo -u <user> -i pm2 jlist` để load PATH + HOME chính chủ.

function parseRef(ref: string): { user: string | null; proc: string } {
  const i = ref.indexOf(":");
  if (i > 0) return { user: ref.slice(0, i), proc: ref.slice(i + 1) };
  return { user: null, proc: ref };
}

function pm2Cmd(user: string | null, cmd: string): string {
  // PM2 thường nằm ở ~/.npm-global/bin (npm prefix), ~/.nvm/versions/node/*/bin (nvm),
  // hoặc ~/.local/bin. .bashrc của user không nhất thiết export những path này.
  // Prepend explicit để pm2 chạy được dù shell config user có thiếu.
  const PATH_PREFIX =
    'export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$(ls -d $HOME/.nvm/versions/node/*/bin 2>/dev/null | head -1):/usr/local/bin:$PATH"';
  if (user) {
    // sudo -u <user> bash -c 'PATH_PREFIX; cmd'
    return `sudo -u ${user} bash -c ${JSON.stringify(`${PATH_PREFIX}; ${cmd}`)}`;
  }
  return `bash -c ${JSON.stringify(`${PATH_PREFIX}; ${cmd}`)}`;
}

const PARSE_SCRIPT =
  "python3 -c \"import sys,json,time; ps=json.load(sys.stdin); m=[p for p in ps if p.get('name')==sys.argv[1]]; " +
  "p=m[0] if m else None; " +
  "print(p['pm2_env']['status'] if p else 'missing'); " +
  "print(p.get('pid') or 0); " +
  "print(p['pm2_env'].get('version') or ''); " +
  "now=int(time.time()*1000); up=p['pm2_env'].get('pm_uptime') if p else 0; " +
  "print(int((now-up)/1000) if up else 0); " +
  "print(p['pm2_env'].get('restart_time') if p else 0)\" ";

function cleanVersion(out: string, ok: boolean): string | undefined {
  if (!ok) return undefined;
  const v = out.trim();
  if (!v || /command not found|No such file|not recognized/i.test(v)) return undefined;
  return v;
}

export const pm2Strategy: DeployStrategy = {
  async status({ hostId, ref, versionProbe }) {
    const { user, proc } = parseRef(ref);
    const j = await exec(
      hostId,
      `${pm2Cmd(user, "pm2 jlist")} 2>/dev/null | ${PARSE_SCRIPT} ${JSON.stringify(proc)}`,
    );
    if (!j.ok || !j.stdout.trim()) {
      const list = await exec(hostId, `${pm2Cmd(user, "pm2 ls 2>&1")}`);
      return {
        ok: false,
        log: `pm2 jlist failed (user=${user || "root"}, proc=${proc}):\n${j.stderr || j.stdout}\n--- pm2 ls ---\n${list.stdout || list.stderr}`,
        running: false,
      };
    }
    const lines = j.stdout.trim().split("\n");
    const state = lines[0]?.trim() || "unknown";
    const pid = parseInt(lines[1] || "0", 10) || undefined;
    const pmVersion = lines[2]?.trim() || "";
    const uptimeSec = parseInt(lines[3] || "0", 10) || 0;
    const restartCount = parseInt(lines[4] || "0", 10) || 0;
    // Probe binary version qua pm2Cmd để load PATH user (vd ~/.npm-global/bin)
    const v = await exec(hostId, pm2Cmd(user, versionProbe));
    const cleanV = cleanVersion(v.stdout, v.ok) || (pmVersion && pmVersion !== "N/A" ? pmVersion : undefined);
    return {
      ok: true,
      log: `pm2: ${proc} (user=${user || "root"})\nstate: ${state}\npid: ${pid ?? "-"}\nversion (binary): ${cleanV || "-"}\nversion (pm2 env): ${pmVersion || "-"}\nuptime: ${uptimeSec}s\nrestart_count: ${restartCount}`,
      version: cleanV,
      running: state === "online",
      pid: state === "online" ? pid : undefined,
      uptimeSec: state === "online" ? uptimeSec : undefined,
      restartCount,
    };
  },

  async start({ hostId, ref }) {
    const { user, proc } = parseRef(ref);
    const r = await exec(hostId, `${pm2Cmd(user, `pm2 restart ${proc} || pm2 start ${proc}`)}`);
    return { ok: r.ok, log: fmtExec(`pm2 restart ${proc} (user=${user || "root"})`, r) };
  },

  async stop({ hostId, ref }) {
    const { user, proc } = parseRef(ref);
    const r = await exec(hostId, `${pm2Cmd(user, `pm2 stop ${proc}`)}`);
    return { ok: r.ok, log: fmtExec(`pm2 stop ${proc} (user=${user || "root"})`, r) };
  },

  async restart({ hostId, ref }) {
    const { user, proc } = parseRef(ref);
    const r = await exec(hostId, `${pm2Cmd(user, `pm2 restart ${proc}`)}`);
    return { ok: r.ok, log: fmtExec(`pm2 restart ${proc} (user=${user || "root"})`, r) };
  },

  async logs({ hostId, ref }, lines = 200) {
    const { user, proc } = parseRef(ref);
    const r = await exec(
      hostId,
      `${pm2Cmd(user, `pm2 logs --lines ${lines} --nostream --raw ${proc} 2>&1`)}`,
      { timeoutMs: 15_000 },
    );
    return { ok: r.ok, log: r.stdout || r.stderr };
  },

  async uninstall({ hostId, ref }) {
    const { user, proc } = parseRef(ref);
    const r = await exec(hostId, `${pm2Cmd(user, `pm2 delete ${proc} && pm2 save 2>&1 || true`)}`);
    return { ok: true, log: fmtExec(`pm2 delete ${proc} (user=${user || "root"})`, r) };
  },
};
