// server/__tests__/run.test.js
/**
 * @file run.test.js
 * @description Route tests for the terminal-run feature: same-origin guard,
 * laneId/cwd validation, spawn/kill/list against a mocked tmux backend.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */
const { describe, it, before, after, beforeEach } = require("node:test");
const assert = require("node:assert/strict");
const path = require("node:path");
const fs = require("node:fs");
const os = require("node:os");
const http = require("node:http");

const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "run-route-test-"));
process.env.DASHBOARD_DB_PATH = path.join(TMP, "dashboard.db");

const { createApp } = require("../index");
const tmux = require("../lib/tmux");

let server;
let BASE;

function fetchJson(p, opts = {}) {
  return new Promise((resolve, reject) => {
    const url = new URL(p, BASE);
    const headers = { ...(opts.headers || {}) };
    let body;
    if (opts.body !== undefined) {
      body = Buffer.from(JSON.stringify(opts.body));
      headers["Content-Type"] = "application/json";
      headers["Content-Length"] = body.length;
    }
    const req = http.request(
      {
        hostname: url.hostname,
        port: url.port,
        path: url.pathname + url.search,
        method: opts.method || "GET",
        headers,
      },
      (res) => {
        const chunks = [];
        res.on("data", (c) => chunks.push(c));
        res.on("end", () => {
          const raw = Buffer.concat(chunks).toString("utf8");
          let parsed;
          try {
            parsed = JSON.parse(raw);
          } catch {
            parsed = raw;
          }
          resolve({ status: res.statusCode, body: parsed });
        });
      }
    );
    req.on("error", reject);
    if (body) req.write(body);
    req.end();
  });
}

describe("/api/run", () => {
  before(async () => {
    const app = createApp();
    server = http.createServer(app);
    await new Promise((r) => server.listen(0, r));
    BASE = `http://127.0.0.1:${server.address().port}`;
  });

  after(async () => {
    await new Promise((r) => server.close(r));
    try {
      fs.rmSync(TMP, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
    } catch {
      /* best-effort */
    }
  });

  beforeEach(() => {
    tmux.__reset();
  });

  it("rejects cross-origin browser requests", async () => {
    const { status, body } = await fetchJson("/api/run", {
      headers: { Origin: "http://evil.example.com" },
    });
    assert.equal(status, 403);
    assert.equal(body.error.code, "EBADORIGIN");
  });

  it("allows requests with no Origin (CLI/curl)", async () => {
    tmux.__setExecImpl((args) => {
      if (args[0] === "list-sessions") {
        const e = new Error("no server running");
        e.status = 1;
        throw e;
      }
      return "";
    });
    const { status, body } = await fetchJson("/api/run");
    assert.equal(status, 200);
    assert.deepEqual(body.items, []);
  });

  it("POST / rejects a missing laneId", async () => {
    const { status, body } = await fetchJson("/api/run", { method: "POST", body: { cwd: TMP } });
    assert.equal(status, 400);
    assert.equal(body.error.code, "EBADLANE");
  });

  it("POST / rejects a non-existent cwd", async () => {
    const { status, body } = await fetchJson("/api/run", {
      method: "POST",
      body: { laneId: 1, cwd: "/definitely/not/a/real/path" },
    });
    assert.equal(status, 400);
    assert.equal(body.error.code, "EBADCWD");
  });

  it("POST / spawns a tmux session and GET /:id finds it", async () => {
    let hasSessionCalls = 0;
    tmux.__setExecImpl((args) => {
      if (args[0] === "has-session") {
        hasSessionCalls++;
        // First call (inside spawnRun): not yet running. Every call after
        // (GET /:id) sees it as running.
        if (hasSessionCalls === 1) {
          const e = new Error("gone");
          e.status = 1;
          throw e;
        }
        return "";
      }
      return "";
    });
    const spawned = await fetchJson("/api/run", { method: "POST", body: { laneId: 9, cwd: TMP } });
    assert.equal(spawned.status, 201);
    assert.equal(spawned.body.id, "ccam-lane-9");

    const fetched = await fetchJson(`/api/run/${spawned.body.id}`);
    assert.equal(fetched.status, 200);
    assert.equal(fetched.body.status, "running");
  });

  it("DELETE /:id kills a live tmux session", async () => {
    tmux.__setExecImpl(() => ""); // has-session succeeds; kill-session succeeds
    const { status, body } = await fetchJson("/api/run/ccam-lane-9", { method: "DELETE" });
    assert.equal(status, 200);
    assert.deepEqual(body, { ok: true });
  });

  it("DELETE /:id returns 404 for a session that doesn't exist", async () => {
    tmux.__setExecImpl((args) => {
      if (args[0] === "has-session") {
        const e = new Error("gone");
        e.status = 1;
        throw e;
      }
      return "";
    });
    const { status } = await fetchJson("/api/run/ccam-lane-999", { method: "DELETE" });
    assert.equal(status, 404);
  });

  it("GET /tmux reports availability from the tmux wrapper", async () => {
    tmux.__setExecImpl(() => "tmux 3.4");
    const { body } = await fetchJson("/api/run/tmux");
    assert.equal(body.available, true);
  });

  it("GET /cwds still returns suggested directories (unchanged behavior)", async () => {
    const { status, body } = await fetchJson("/api/run/cwds");
    assert.equal(status, 200);
    assert.ok(Array.isArray(body.items));
  });
});
