/**
 * @file Tests for dashboard self-update HTTP endpoints.
 * @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
 */

const { describe, it, before, after } = require("node:test");
const assert = require("node:assert/strict");
const path = require("path");
const fs = require("fs");
const os = require("os");
const http = require("http");

const TEST_DB = path.join(os.tmpdir(), `dashboard-updates-${Date.now()}-${process.pid}.db`);
process.env.DASHBOARD_DB_PATH = TEST_DB;

const { createApp, startServer } = require("../index");
const { db } = require("../db");

let server;
let BASE;

function httpFetch(urlPath, options = {}) {
  return new Promise((resolve, reject) => {
    const url = new URL(urlPath, BASE);
    const opts = {
      hostname: url.hostname,
      port: url.port,
      path: url.pathname + url.search,
      method: options.method || "GET",
      headers: { "Content-Type": "application/json", ...options.headers },
    };
    const req = http.request(opts, (res) => {
      let body = "";
      res.on("data", (chunk) => {
        body += chunk;
      });
      res.on("end", () => {
        let parsed;
        try {
          parsed = JSON.parse(body);
        } catch {
          parsed = body;
        }
        resolve({ status: res.statusCode, body: parsed });
      });
    });
    req.on("error", reject);
    if (options.body) req.write(options.body);
    req.end();
  });
}

before(async () => {
  const app = createApp();
  server = await startServer(app, 0);
  const addr = server.address();
  BASE = `http://127.0.0.1:${addr.port}`;
});

after(() => {
  if (server) server.close();
  if (db) db.close();
  try {
    fs.unlinkSync(TEST_DB);
    fs.unlinkSync(`${TEST_DB}-wal`);
    fs.unlinkSync(`${TEST_DB}-shm`);
  } catch {
    // ignore
  }
});

describe("GET /api/updates/status", () => {
  it("returns update check payload", async () => {
    const res = await httpFetch("/api/updates/status");
    assert.equal(res.status, 200);
    assert.equal(typeof res.body.git_repo, "boolean");
    assert.equal(typeof res.body.update_available, "boolean");
    if (res.body.git_repo) {
      assert.ok(typeof res.body.repo_root === "string");
    }
  });
});

describe("POST /api/updates/check", () => {
  it("returns a fresh update status payload", async () => {
    const res = await httpFetch("/api/updates/check", { method: "POST", body: "{}" });
    assert.equal(res.status, 200);
    assert.equal(typeof res.body.git_repo, "boolean");
    assert.equal(typeof res.body.update_available, "boolean");
  });
});

describe("POST /api/updates/apply", () => {
  it("declines with a reason instead of pulling/restarting when there is nothing to apply", async () => {
    // The test repo checkout has no update pending (or isn't fast-forwardable
    // from a bare test run), so this exercises the decline path only — it must
    // never touch the working tree or process in a test run.
    const res = await httpFetch("/api/updates/apply", {
      method: "POST",
      body: "{}",
    });
    assert.equal(res.status, 409);
    assert.equal(res.body.applied, false);
    assert.ok(["up_to_date", "not_fast_forwardable"].includes(res.body.reason));
  });
});
