import { projectPublicDiffData } from "./public-diff.js";
import { sanitizePublicData, sanitizePublicText } from "./public-sanitizer.js";

export function publicErrorBody(error, config = {}) {
  const explicitCode = typeof error?.code === "string" ? error.code : "";
  const candidate = explicitCode || error?.message || "";
  const code = sanitizePublicText(candidate, config);
  const isPublicCode = Number(error?.status) < 500 && /^[a-z][a-z0-9_]*$/.test(code);
  return { error: isPublicCode ? code : "internal_error" };
}

export function sendJson(res, status, body) {
  res.writeHead(status, {
    "content-type": "application/json; charset=utf-8",
    "cache-control": "no-store",
    ...(res.corsHeaders ?? corsHeaders())
  });
  res.end(JSON.stringify(publicProjection(body, res.publicSanitizerConfig)));
}

export function notFound(res) {
  sendJson(res, 404, { error: "not_found" });
}

export function methodNotAllowed(res) {
  sendJson(res, 405, { error: "method_not_allowed" });
}

export async function readJson(req, maxBytes = 1024 * 1024) {
  let body = "";

  for await (const chunk of req) {
    body += chunk;
    if (body.length > maxBytes) {
      const error = new Error("request_too_large");
      error.status = 413;
      throw error;
    }
  }

  if (body.length === 0) {
    return {};
  }

  try {
    return JSON.parse(body);
  } catch {
    const error = new Error("invalid_json");
    error.status = 400;
    throw error;
  }
}

export function sendEvent(res, event, data) {
  res.write(`event: ${event}\n`);
  res.write(`data: ${JSON.stringify(publicProjection(data, res.publicSanitizerConfig))}\n\n`);
}

export function corsHeaders(origin, config = {}) {
  const allowed = isAllowedOrigin(origin, config);
  return {
    ...(allowed ? {
      "access-control-allow-origin": origin,
      "access-control-allow-credentials": "true",
      vary: "origin"
    } : {}),
    "access-control-allow-methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
    "access-control-allow-headers": "content-type,x-code-remote-csrf",
    "access-control-max-age": "600"
  };
}

export function assertAllowedOrigin(req, config = {}) {
  if (!["POST", "PUT", "PATCH", "DELETE"].includes(req.method)) return;
  const origin = req.headers?.origin;
  if (!origin) throw requestGuardError("origin_required");
  if (!isAllowedOrigin(origin, config)) throw requestGuardError("origin_not_allowed");
  if (req.headers?.["x-code-remote-csrf"] !== "1") throw requestGuardError("csrf_required");
}

export function assertEventStreamOrigin(req, config = {}) {
  const origin = req.headers?.origin;
  if (!origin) throw requestGuardError("origin_required");
  if (!isAllowedOrigin(origin, config)) throw requestGuardError("origin_not_allowed");
}

export function assertCorsPreflight(req, config = {}) {
  const origin = req.headers?.origin;
  if (!origin) throw requestGuardError("origin_required");
  if (!isAllowedOrigin(origin, config)) throw requestGuardError("origin_not_allowed");
}

export function prepareHttpRequest(req, res, config = {}) {
  const origin = req.headers?.origin;
  res.corsHeaders = corsHeaders(origin, config);
  res.publicSanitizerConfig = config;
  return origin;
}

function publicProjection(value, config = {}) {
  return sanitizePublicData(projectPublicDiffData(value), config);
}

function isAllowedOrigin(origin, config) {
  if (typeof origin !== "string") return false;
  const allowed = new Set([
    "http://127.0.0.1:43111",
    "http://localhost:43111",
    "https://localhost",
    ...(Array.isArray(config.allowedOrigins) ? config.allowedOrigins : [])
  ]);
  return allowed.has(origin);
}

export function sendOptions(res, origin, config) {
  res.writeHead(204, corsHeaders(origin, config));
  res.end();
}

function requestGuardError(code) {
  const error = new Error(code);
  error.status = 403;
  error.safeLogCode = code;
  return error;
}
