"use strict";

const fs = require("node:fs");
const http = require("node:http");
const path = require("node:path");
const os = require("node:os");

const ROOT = __dirname;
const TYPES = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "application/javascript; charset=utf-8" };
const APP_VERSION = "2.0.1";

function json(response, status, value) {
  response.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" });
  response.end(JSON.stringify(value));
}

function proxyCodeChannel(request, response, options = {}) {
  const internalPath = request.url.replace(/^\/code(?=\/|$)/, "") || "/";
  const upstream = http.request({
    host: options.codeChannelHost || process.env.CODE_CHANNEL_HOST || "127.0.0.1",
    port: Number(options.codeChannelPort || process.env.CODE_CHANNEL_PORT || 3340),
    method: request.method,
    path: internalPath,
    headers: {
      ...request.headers,
      host: request.headers.host || "guanghulab.com",
      "x-forwarded-host": request.headers["x-forwarded-host"] || request.headers.host || "guanghulab.com",
      "x-forwarded-proto": request.headers["x-forwarded-proto"] || "https"
    },
    timeout: 300000
  }, upstreamResponse => {
    response.writeHead(upstreamResponse.statusCode || 502, upstreamResponse.headers);
    upstreamResponse.pipe(response);
  });
  upstream.on("timeout", () => upstream.destroy(new Error("upstream_timeout")));
  upstream.on("error", () => {
    if (!response.headersSent) return json(response, 502, { ok: false, error: "code_channel_unavailable" });
    response.destroy();
  });
  request.pipe(upstream);
}

function candidateFailureCategory(message) {
  const value = String(message || "").toLowerCase();
  if (/urlopen|timed out|http error|temporary failure|name or service/.test(value)) return "relay_download_failed";
  if (/no such file.*gpg|gpg.*no such file/.test(value)) return "signature_verifier_unavailable";
  if (/fingerprint|signature|gpg/.test(value)) return "signature_verification_failed";
  if (/candidate exited|candidate stopped/.test(value)) return "candidate_process_exited";
  if (/readiness timeout/.test(value)) return "candidate_readiness_timeout";
  return value ? "bootstrap_failed" : "";
}

function readCandidateStatus(response, options = {}) {
  const request = http.get({
    host: options.codeChannelHealthHost || "127.0.0.1",
    port: Number(options.codeChannelHealthPort || 3341),
    path: "/health",
    timeout: 3000
  }, upstreamResponse => {
    let raw = "";
    upstreamResponse.setEncoding("utf8");
    upstreamResponse.on("data", chunk => {
      raw += chunk;
      if (raw.length > 8192) upstreamResponse.destroy(new Error("status_too_large"));
    });
    upstreamResponse.on("end", () => {
      try {
        const source = JSON.parse(raw);
        return json(response, 200, {
          ok: source.ok === true,
          version: String(source.version || ""),
          code: String(source.code || ""),
          ready: source.ready === true,
          stage: String(source.stage || "unknown"),
          failure_category: source.ok === true ? "" : candidateFailureCategory(source.error)
        });
      } catch {
        return json(response, 502, { ok: false, ready: false, error: "invalid_candidate_status" });
      }
    });
  });
  request.on("timeout", () => request.destroy(new Error("status_timeout")));
  request.on("error", () => json(response, 502, { ok: false, ready: false, error: "candidate_status_unavailable" }));
}

function createApp(options = {}) {
  return http.createServer((request, response) => {
    const url = new URL(request.url, "http://localhost");
    if (url.pathname === "/code") {
      response.writeHead(308, { Location: "/code/", "Cache-Control": "no-store" });
      return response.end();
    }
    if (url.pathname.startsWith("/code/")) return proxyCodeChannel(request, response, options);
    if (request.method !== "GET" && request.method !== "HEAD") return json(response, 405, { ok: false, error: "method_not_allowed" });
    if (url.pathname === "/api/code-channel-status") return readCandidateStatus(response, options);
    if (url.pathname === "/api/status") {
      return json(response, 200, {
        ok: true,
        node_id: process.env.GUANGHU_NODE_ID || "JD-FD-PRIMARY",
        role: "fifth-domain-primary",
        service: "jd-app-hub",
        version: APP_VERSION,
        code_channel_proxy: "loopback-only",
        uptime_seconds: Math.floor(process.uptime()),
        load_1m: Number(os.loadavg()[0].toFixed(2)),
        memory_used_percent: Number((((os.totalmem() - os.freemem()) / os.totalmem()) * 100).toFixed(1)),
        timestamp: new Date().toISOString()
      });
    }
    const relative = url.pathname === "/" ? "index.html" : url.pathname.replace(/^\/+/, "");
    if (!/^(index\.html|styles\.css|app\.js)$/.test(relative)) return json(response, 404, { ok: false, error: "not_found" });
    const file = path.join(ROOT, relative);
    response.writeHead(200, { "Content-Type": TYPES[path.extname(file)] || "application/octet-stream", "Cache-Control": relative === "index.html" ? "no-cache" : "public, max-age=3600" });
    fs.createReadStream(file).pipe(response);
  });
}

if (require.main === module) {
  const host = process.env.HUB_HOST || "127.0.0.1";
  const port = Number(process.env.HUB_PORT || 8088);
  createApp().listen(port, host, () => console.log(`JD app hub listening on ${host}:${port}`));
}

module.exports = { createApp };
