import assert from "node:assert/strict";
import { createServer } from "node:http";
import test from "node:test";

import { upstreamRequestHeaders } from "./cloud-web-proxy.mjs";
import { cloudWebProxyRoutePolicy } from "./cloud-web-routes.mjs";
import { proxyHttpRequest } from "./http.mjs";

test("cloud web route policy proxies public Code Agent chat without gating other writes", () => {
  assert.deepEqual(cloudWebProxyRoutePolicy("POST", "/v1/agent/chat"), {
    proxy: true,
    authRequired: false,
    publicRoute: true,
    routeKey: "POST /v1/agent/chat"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("POST", "/v1/agent/chat/cancel"), {
    proxy: true,
    authRequired: false,
    publicRoute: true,
    routeKey: "POST /v1/agent/chat/cancel"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("POST", "/v1/agent/chat/steer"), {
    proxy: true,
    authRequired: false,
    publicRoute: true,
    routeKey: "POST /v1/agent/chat/steer"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("POST", "/v1/m3/io"), {
    proxy: true,
    authRequired: true,
    publicRoute: false,
    routeKey: "POST /v1/m3/io"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("GET", "/v1/agent/traces/trc_live"), {
    proxy: true,
    authRequired: false,
    publicRoute: true,
    routeKey: "GET /v1/agent/traces/trc_live"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("GET", "/v1/agent/chat/trace/trc_live"), {
    proxy: true,
    authRequired: true,
    publicRoute: false,
    routeKey: "GET /v1/agent/chat/trace/trc_live"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("GET", "/v1/agent/chat/result/trc_live"), {
    proxy: true,
    authRequired: false,
    publicRoute: true,
    routeKey: "GET /v1/agent/chat/result/trc_live"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("GET", "/v1/agent/turns/trc_live"), {
    proxy: true,
    authRequired: false,
    publicRoute: true,
    routeKey: "GET /v1/agent/turns/trc_live"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("GET", "/v1"), {
    proxy: true,
    authRequired: true,
    publicRoute: false,
    routeKey: "GET /v1"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("GET", "/v1/m3/status"), {
    proxy: true,
    authRequired: true,
    publicRoute: false,
    routeKey: "GET /v1/m3/status"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("GET", "/app.js"), {
    proxy: false,
    authRequired: false,
    publicRoute: false,
    routeKey: "GET /app.js"
  });
});

test("cloud web route policy delegates auth authority to cloud-api", () => {
  assert.deepEqual(cloudWebProxyRoutePolicy("GET", "/auth/session"), {
    proxy: true,
    authRequired: false,
    publicRoute: true,
    routeKey: "GET /auth/session"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("POST", "/auth/login"), {
    proxy: true,
    authRequired: false,
    publicRoute: true,
    routeKey: "POST /auth/login"
  });
  assert.deepEqual(cloudWebProxyRoutePolicy("POST", "/auth/logout"), {
    proxy: true,
    authRequired: false,
    publicRoute: true,
    routeKey: "POST /auth/logout"
  });
});

test("cloud web proxy forwards user credentials without forwarding session-token credentials", () => {
  const headers = upstreamRequestHeaders({
    headers: {
      accept: "application/json",
      authorization: "Bearer hwl_live_must-forward",
      cookie: "hwlab_session=session-a",
      "x-hwlab-session-token": "session-a-secret",
      "x-trace-id": "trc_hwpod_session_proxy"
    }
  });

  assert.equal(headers["x-hwlab-session-token"], undefined);
  assert.equal(headers.cookie, "hwlab_session=session-a");
  assert.equal(headers["x-trace-id"], "trc_hwpod_session_proxy");
  assert.equal(headers.authorization, "Bearer hwl_live_must-forward");
});

test("dev entrypoint proxy allows slow first response beyond legacy 4500ms", async () => {
  const upstream = createServer((request, response) => {
    request.resume();
    setTimeout(() => {
      response.writeHead(200, { "content-type": "application/json" });
      response.end(JSON.stringify({
        status: "completed",
        traceId: request.headers["x-trace-id"] ?? null
      }));
    }, 4700);
  });
  await listen(upstream);

  const proxy = createServer((request, response) => {
    proxyHttpRequest({
      request,
      response,
      upstream: serverUrl(upstream),
      timeoutMs: 6000
    });
  });
  await listen(proxy);

  try {
    const response = await fetch(`${serverUrl(proxy)}/v1/agent/chat`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-trace-id": "trc_dev-entrypoint-proxy-slow-success"
      },
      body: JSON.stringify({ message: "列出你能使用的所有skill" })
    });
    assert.equal(response.status, 200);
    const payload = await response.json();
    assert.equal(payload.status, "completed");
    assert.equal(payload.traceId, "trc_dev-entrypoint-proxy-slow-success");
  } finally {
    await close(proxy);
    await close(upstream);
  }
});

test("dev entrypoint proxy returns 502 when hard timeout expires", async () => {
  const upstream = createServer((request, response) => {
    request.resume();
    setTimeout(() => {
      response.writeHead(200, { "content-type": "application/json" });
      response.end(JSON.stringify({ status: "completed" }));
    }, 250);
  });
  await listen(upstream);

  const proxy = createServer((request, response) => {
    proxyHttpRequest({
      request,
      response,
      upstream: serverUrl(upstream),
      timeoutMs: 50
    });
  });
  await listen(proxy);

  try {
    const response = await fetch(`${serverUrl(proxy)}/v1/agent/chat`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-trace-id": "trc_dev-entrypoint-proxy-timeout"
      },
      body: JSON.stringify({ message: "slow" })
    });
    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.category, "timeout");
    assert.equal(payload.error.retryable, true);
    assert.equal(payload.error.timeoutMs, 50);
    assert.equal(payload.error.traceId, "trc_dev-entrypoint-proxy-timeout");
    assert.equal(payload.error.userMessage, "暂时无法连接上游。");
    assert.equal(payload.error.message, "暂时无法连接上游。");
    assert.equal(payload.error.blocker.code, "upstream_unavailable");
    assert.equal(payload.error.blocker.retryable, true);
    assert.equal(payload.traceId, "trc_dev-entrypoint-proxy-timeout");
    assert.equal(payload.message, "暂时无法连接上游。");
    assert.doesNotMatch(JSON.stringify(payload), /Code Agent|输入已保留|稍后重试/u);
  } finally {
    await close(proxy);
    await close(upstream);
  }
});

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 serverUrl(server) {
  const address = server.address();
  return `http://127.0.0.1:${address.port}`;
}
