#!/usr/bin/env node
import http from "node:http";
import path from "node:path";
import { archivePage, defaultArchivePath } from "./archiver.mjs";
import { archiveIdForUrl, normalizeArchiveUrl } from "./archive-catalog.mjs";

const PORT = Number(process.env.PORT || process.env.ARCHIVE_WORKER_PORT || 5733);
const ARCHIVE_PATH = path.resolve(process.env.ARCHIVE_PATH || defaultArchivePath());
const MAX_BODY_BYTES = 64 * 1024;

let queue = Promise.resolve();

const server = http.createServer(async (req, res) => {
  try {
    await route(req, res);
  } catch (error) {
    sendJson(res, error.statusCode || 500, {
      ok: false,
      error: error.message || "Unexpected error"
    });
  }
});

server.listen(PORT, () => {
  console.log(`archive worker listening on ${PORT}`);
  console.log(`archive path: ${ARCHIVE_PATH}`);
});

async function route(req, res) {
  const requestUrl = new URL(req.url, `http://${req.headers.host || "localhost"}`);

  if (req.method === "GET" && requestUrl.pathname === "/healthz") {
    sendJson(res, 200, { ok: true });
    return;
  }

  if (req.method === "POST" && requestUrl.pathname === "/archive") {
    const body = await readJsonBody(req);
    const sourceUrl = normalizeArchiveUrl(body.url);
    const id = typeof body.id === "string" && body.id.trim() ? body.id.trim() : archiveIdForUrl(sourceUrl);
    const result = await enqueueArchive(sourceUrl, id);
    sendJson(res, 200, {
      ok: true,
      result
    });
    return;
  }

  sendJson(res, 404, { ok: false, error: "Not found" });
}

function enqueueArchive(sourceUrl, id) {
  const run = () => archivePage(sourceUrl, {
    archivePath: ARCHIVE_PATH,
    id
  });
  queue = queue.then(run, run);
  return queue;
}

async function readJsonBody(req) {
  const text = await readRequestBody(req, MAX_BODY_BYTES);
  if (!text.trim()) {
    throw httpError(400, "Request body is required");
  }
  try {
    return JSON.parse(text);
  } catch {
    throw httpError(400, "Request body must be JSON");
  }
}

function readRequestBody(req, maxBytes) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    let total = 0;
    req.on("data", (chunk) => {
      total += chunk.length;
      if (total > maxBytes) {
        reject(httpError(413, "Request body is too large"));
        req.destroy();
        return;
      }
      chunks.push(chunk);
    });
    req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
    req.on("error", reject);
  });
}

function sendJson(res, statusCode, payload) {
  res.writeHead(statusCode, {
    "content-type": "application/json; charset=utf-8",
    "cache-control": "no-store"
  });
  res.end(`${JSON.stringify(payload)}\n`);
}

function httpError(statusCode, message) {
  const error = new Error(message);
  error.statusCode = statusCode;
  return error;
}
