import assert from "node:assert/strict";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { createServer } from "node:http";
import { connect } from "node:net";
import os from "node:os";
import path from "node:path";
import test from "node:test";

import { createCloudWebServer, isClientDisconnectError } from "./cloud-web-runtime.mjs";

test("cloud web classifies Bun client disconnect ECONNRESET as non-fatal", () => {
  const error = new Error("aborted");
  error.code = "ECONNRESET";
  assert.equal(isClientDisconnectError(error), true);
  assert.equal(isClientDisconnectError(new Error("database migration failed")), false);
});

test("cloud web auth requests are proxied once", async () => {
  let upstreamRequests = 0;
  const upstream = createServer((request, response) => {
    upstreamRequests += 1;
    request.resume();
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ authenticated: true, path: request.url }));
  });
  await listen(upstream);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    cloudApiBaseUrl: serverUrl(upstream),
    cloudApiProxyTimeoutMs: 1000,
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/auth/session`, {
      headers: { cookie: "hwlab_session=token" }
    });
    assert.equal(response.status, 200);
    assert.deepEqual(await response.json(), { authenticated: true, path: "/auth/session" });
    assert.equal(upstreamRequests, 1);
  } finally {
    await close(cloudWeb);
    await close(upstream);
  }
});

test("cloud web proxy reports a unified upstream unavailable error", async () => {
  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    cloudApiBaseUrl: "http://127.0.0.1:1",
    cloudApiProxyTimeoutMs: 1000,
    healthPayload: () => ({ status: "ok" }),
    roots: [],
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/v1/web-performance/summary?window=15m`, {
      headers: { accept: "application/json", "x-trace-id": "trc_proxy_upstream_unavailable" }
    });
    assert.equal(response.status, 502);
    const payload = await response.json();
    assert.equal(payload.status, "failed");
    assert.equal(payload.error.code, "upstream_unavailable");
    assert.equal(payload.error.layer, "proxy");
    assert.equal(payload.error.retryable, true);
    assert.equal(payload.error.userMessage, "暂时无法连接上游。");
    assert.equal(payload.error.message, "暂时无法连接上游。");
    assert.equal(payload.error.blocker.code, "upstream_unavailable");
    assert.equal(payload.error.blocker.summary, "暂时无法连接上游。");
    assert.equal(payload.reason, "暂时无法连接上游。");
    assert.doesNotMatch(JSON.stringify(payload), /Code Agent|输入已保留|稍后重试/u);
  } finally {
    await close(cloudWeb);
  }
});

test("cloud web proxies Admin Access write routes", async () => {
  const upstreamRequests = [];
  const upstream = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    upstreamRequests.push({
      method: request.method,
      url: request.url,
      authorization: request.headers.authorization,
      cookie: request.headers.cookie,
      body
    });
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ ok: true, path: request.url }));
  });
  await listen(upstream);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    cloudApiBaseUrl: serverUrl(upstream),
    cloudApiProxyTimeoutMs: 1000,
    healthPayload: () => ({ status: "ok" }),
    roots: [],
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const checkBody = JSON.stringify({ user: "usr_alice", relation: "can_use", object: "tool:hwpod" });
    const checkResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/access/check`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer hwl_live_admin",
        cookie: "hwlab_session=session-a"
      },
      body: checkBody
    });
    assert.equal(checkResponse.status, 200);
    assert.deepEqual(await checkResponse.json(), { ok: true, path: "/v1/admin/access/check" });

    const grantResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/access/users/usr_alice/tools/hwpod/can-use`, {
      method: "PUT",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer hwl_live_admin"
      },
      body: "{}"
    });
    assert.equal(grantResponse.status, 200);

    assert.equal(upstreamRequests.length, 2);
    assert.deepEqual(upstreamRequests[0], {
      method: "POST",
      url: "/v1/admin/access/check",
      authorization: "Bearer hwl_live_admin",
      cookie: "hwlab_session=session-a",
      body: checkBody
    });
    assert.deepEqual(upstreamRequests[1], {
      method: "PUT",
      url: "/v1/admin/access/users/usr_alice/tools/hwpod/can-use",
      authorization: "Bearer hwl_live_admin",
      cookie: undefined,
      body: "{}"
    });
  } finally {
    await close(cloudWeb);
    await close(upstream);
  }
});

test("cloud web proxies provider profile management write routes", async () => {
  const upstreamRequests = [];
  const upstream = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    upstreamRequests.push({
      method: request.method,
      url: request.url,
      authorization: request.headers.authorization,
      body
    });
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ ok: true, path: request.url }));
  });
  await listen(upstream);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    cloudApiBaseUrl: serverUrl(upstream),
    cloudApiProxyTimeoutMs: 1000,
    healthPayload: () => ({ status: "ok" }),
    roots: [],
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const credentialBody = JSON.stringify({ apiKey: "sk-test-redacted" });
    const credentialResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/provider-profiles/deepseek/credential`, {
      method: "PUT",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer hwl_live_admin"
      },
      body: credentialBody
    });
    assert.equal(credentialResponse.status, 200);

    const validateResponse = await fetch(`${serverUrl(cloudWeb)}/v1/admin/provider-profiles/deepseek/validate`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer hwl_live_admin"
      },
      body: "{}"
    });
    assert.equal(validateResponse.status, 200);

    assert.equal(upstreamRequests.length, 2);
    assert.deepEqual(upstreamRequests[0], {
      method: "PUT",
      url: "/v1/admin/provider-profiles/deepseek/credential",
      authorization: "Bearer hwl_live_admin",
      body: credentialBody
    });
    assert.deepEqual(upstreamRequests[1], {
      method: "POST",
      url: "/v1/admin/provider-profiles/deepseek/validate",
      authorization: "Bearer hwl_live_admin",
      body: "{}"
    });
  } finally {
    await close(cloudWeb);
    await close(upstream);
  }
});

test("cloud web forwards hwpod node-ops plans to cloud-api", async () => {
  const upstreamRequests = [];
  const upstream = createServer(async (request, response) => {
    let body = "";
    for await (const chunk of request) body += chunk;
    upstreamRequests.push({
      method: request.method,
      url: request.url,
      authorization: request.headers.authorization,
      body
    });
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ ok: false, status: "blocked", path: request.url, blocker: { code: "hwpod_node_unavailable" } }));
  });
  await listen(upstream);

  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    cloudApiBaseUrl: serverUrl(upstream),
    cloudApiProxyTimeoutMs: 1000,
    healthPayload: () => ({ status: "ok" }),
    roots: [],
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const plan = {
      contractVersion: "hwpod-node-ops-v1",
      planId: "hwpod_plan_proxy_test",
      hwpodId: "hwpod-test",
      nodeId: "node-test",
      ops: [{ opId: "op_01", op: "node.health", args: {} }]
    };
    const response = await fetch(`${serverUrl(cloudWeb)}/v1/hwpod-node-ops`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        authorization: "Bearer hwl_live_operator"
      },
      body: JSON.stringify(plan)
    });
    assert.equal(response.status, 200);
    assert.deepEqual(await response.json(), { ok: false, status: "blocked", path: "/v1/hwpod-node-ops", blocker: { code: "hwpod_node_unavailable" } });
    assert.equal(upstreamRequests.length, 1);
    assert.deepEqual(upstreamRequests[0], {
      method: "POST",
      url: "/v1/hwpod-node-ops",
      authorization: "Bearer hwl_live_operator",
      body: JSON.stringify(plan)
    });
  } finally {
    await close(cloudWeb);
    await close(upstream);
  }
});

test("cloud web remains healthy after a client disconnects during static response", async () => {
  const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-cloud-web-runtime-"));
  await writeFile(path.join(root, "large.txt"), "x".repeat(512 * 1024), "utf8");
  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [root],
    cloudApiBaseUrl: "http://127.0.0.1:1",
    healthPayload: () => ({ status: "ok", process: { uptimeSeconds: 1 } }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    await disconnectDuringResponse(serverUrl(cloudWeb), "/large.txt");
    const response = await fetch(`${serverUrl(cloudWeb)}/health/live`);
    assert.equal(response.status, 200);
    assert.equal((await response.json()).status, "ok");
  } finally {
    await close(cloudWeb);
    await rm(root, { recursive: true, force: true });
  }
});

test("cloud web serves client deep links through the Vue shell", async () => {
  const restoreEnv = withEnv({
    HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间"
  });
  const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-cloud-web-runtime-"));
  await writeFile(path.join(root, "index.html"), "<div id=\"root\"></div>\n", "utf8");
  await writeFile(path.join(root, "asset.txt"), "asset body\n", "utf8");
  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [root],
    cloudApiBaseUrl: "http://127.0.0.1:1",
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    for (const route of ["/access", "/register", "/api-keys", "/admin/users"]) {
      const response = await fetch(`${serverUrl(cloudWeb)}${route}`, {
        headers: { accept: "text/html" }
      });
      assert.equal(response.status, 200, route);
      assert.equal(response.headers.get("content-type"), "text/html; charset=utf-8", route);
      const html = await response.text();
      assert.match(html, /<div id="root"><\/div>/u, route);
      assert.match(html, /HWLAB_CLOUD_WEB_CONFIG/u, route);
    }

    const assetResponse = await fetch(`${serverUrl(cloudWeb)}/asset.txt`, {
      headers: { accept: "text/plain" }
    });
    assert.equal(assetResponse.status, 200);
    assert.equal(await assetResponse.text(), "asset body\n");

    const missingAssetResponse = await fetch(`${serverUrl(cloudWeb)}/missing.css`, {
      headers: { accept: "text/css,*/*" }
    });
    assert.equal(missingAssetResponse.status, 404);
  } finally {
    restoreEnv();
    await close(cloudWeb);
    await rm(root, { recursive: true, force: true });
  }
});

test("cloud web injects trace explorer runtime config from env", async () => {
  const restoreEnv = withEnv({
    HWLAB_CLOUD_WEB_DISPLAY_TIME_ZONE: "Asia/Shanghai",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LOCALE: "zh-CN",
    HWLAB_CLOUD_WEB_DISPLAY_TIME_LABEL: "北京时间",
    HWLAB_WORKBENCH_TRACE_EXPLORER_URL_TEMPLATE: "/v1/workbench/traces/{trace_id}/events"
  });
  const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-cloud-web-runtime-"));
  await writeFile(path.join(root, "index.html"), "<html><head></head><body><div id=\"root\"></div></body></html>\n", "utf8");
  const cloudWeb = createCloudWebServer({
    serviceId: "hwlab-cloud-web",
    roots: [root],
    cloudApiBaseUrl: "http://127.0.0.1:1",
    healthPayload: () => ({ status: "ok" }),
    sendJson(response, statusCode, body) {
      const payload = JSON.stringify(body);
      response.writeHead(statusCode, {
        "content-type": "application/json",
        "content-length": Buffer.byteLength(payload)
      });
      response.end(payload);
    }
  });
  await listen(cloudWeb);

  try {
    const response = await fetch(`${serverUrl(cloudWeb)}/workbench`, { headers: { accept: "text/html" } });
    assert.equal(response.status, 200);
    const html = await response.text();
    assert.match(html, /traceExplorerUrlTemplate":"\/v1\/workbench\/traces\/\{trace_id\}\/events/u);
    assert.doesNotMatch(html, /api key|secret|password/iu);
  } finally {
    restoreEnv();
    await close(cloudWeb);
    await rm(root, { recursive: true, force: true });
  }
});

function withEnv(values) {
  const previous = new Map();
  for (const [key, value] of Object.entries(values)) {
    previous.set(key, process.env[key]);
    process.env[key] = value;
  }
  return () => {
    for (const [key, value] of previous.entries()) {
      if (value === undefined) delete process.env[key];
      else process.env[key] = value;
    }
  };
}

function listen(server) {
  return new Promise((resolve, reject) => {
    server.once("error", reject);
    server.listen(0, "127.0.0.1", resolve);
  });
}

function close(server) {
  return new Promise((resolve, reject) => {
    server.close((error) => (error ? reject(error) : resolve()));
  });
}

function disconnectDuringResponse(baseUrl, pathname) {
  const url = new URL(pathname, baseUrl);
  return new Promise((resolve, reject) => {
    const socket = connect(Number(url.port), url.hostname);
    const timeout = setTimeout(() => {
      socket.destroy();
      reject(new Error("client disconnect probe timed out"));
    }, 2000);
    socket.once("connect", () => {
      socket.write(`GET ${url.pathname} HTTP/1.1\r\nHost: ${url.host}\r\nConnection: close\r\n\r\n`);
    });
    socket.once("data", () => {
      clearTimeout(timeout);
      socket.destroy();
      setTimeout(resolve, 50);
    });
    socket.once("error", (error) => {
      clearTimeout(timeout);
      reject(error);
    });
  });
}

function serverUrl(server) {
  const address = server.address();
  return `http://127.0.0.1:${address.port}`;
}
