import assert from "node:assert/strict";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test } from "bun:test";

import { createCloudApiServer } from "./server.ts";

test("cloud api aggregates live HWLAB build times from health and controlled deploy/catalog metadata", async () => {
  const healthByPath = new Map([
    ["/hwlab-cloud-web/health/live", {
      serviceId: "hwlab-cloud-web",
      status: "ok",
      revision: "webabcdef123456",
      commit: { id: "webabcdef123456", source: "test" },
      image: {
        reference: "127.0.0.1:5000/hwlab/hwlab-cloud-web:webabcd",
        tag: "webabcd",
        digest: "sha256:" + "1".repeat(64)
      },
      build: {
        createdAt: "2026-05-23T02:10:00.000Z",
        metadataSource: "runtime-env:HWLAB_BUILD_CREATED_AT"
      }
    }],
    ["/hwlab-gateway/health/live", {
      serviceId: "hwlab-gateway",
      status: "ok",
      revision: "48dfbf9",
      commit: { id: "48dfbf9", source: "test" },
      image: {
        reference: "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9",
        tag: "48dfbf9",
        digest: "sha256:" + "2".repeat(64)
      }
    }],
    ["/hwlab-agent-skills/health/live", {
      serviceId: "hwlab-agent-skills",
      status: "ok",
      revision: "skillsabcdef123456",
      commit: { id: "skillsabcdef123456", source: "test" },
      image: {
        reference: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd",
        tag: "skillsabcd",
        digest: "sha256:" + "5".repeat(64)
      }
    }],
    ["/external/health", {
      serviceId: "postgres",
      status: "ok",
      image: {
        reference: "postgres:16",
        tag: "16"
      }
    }]
  ]);
  const liveBuildMetadata = {
    deployManifest: {
      services: [
        {
          serviceId: "hwlab-cloud-web",
          image: "127.0.0.1:5000/hwlab/hwlab-cloud-web:webcat",
          namespace: "hwlab-dev",
          profile: "dev",
          healthPath: "/health/live",
          replicas: 1
        },
        {
          serviceId: "hwlab-gateway",
          image: "127.0.0.1:5000/hwlab/hwlab-gateway:3df89fe",
          namespace: "hwlab-dev",
          profile: "dev",
          healthPath: "/health/live",
          replicas: 1
        },
        {
          serviceId: "hwlab-agent-skills",
          image: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd",
          namespace: "hwlab-dev",
          profile: "dev",
          healthPath: "/health/live",
          replicas: 1,
          env: {
            HWLAB_COMMIT_ID: "skillsabcdef123456",
            HWLAB_IMAGE: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd",
            HWLAB_IMAGE_TAG: "skillsabcd",
            HWLAB_BUILD_CREATED_AT: "2026-05-23T05:30:00.000Z",
            HWLAB_BUILD_SOURCE: "deploy-env-test"
          }
        },
        {
          serviceId: "hwlab-edge-proxy",
          image: "127.0.0.1:5000/hwlab/hwlab-edge-proxy:edgecat",
          namespace: "hwlab-dev",
          profile: "dev",
          healthPath: "/health",
          replicas: 1
        },
        {
          serviceId: "hwlab-extra-lab",
          image: "127.0.0.1:5000/hwlab/hwlab-extra-lab:extracat",
          namespace: "hwlab-dev",
          profile: "dev",
          healthPath: "/health/live",
          replicas: 1
        }
      ]
    },
    artifactCatalog: {
      services: [
        {
          serviceId: "hwlab-gateway",
          commitId: "3df89fe",
          image: "127.0.0.1:5000/hwlab/hwlab-gateway:3df89fe",
          imageTag: "3df89fe",
          digest: "sha256:" + "3".repeat(64),
          namespace: "hwlab-dev",
          profile: "dev",
          healthPath: "/health/live",
          buildCreatedAt: "2026-05-23T04:20:00.000Z",
          buildSource: "artifact-catalog-test"
        },
        {
          serviceId: "hwlab-agent-skills",
          commitId: "skillsabcdef123456",
          image: "127.0.0.1:5000/hwlab/hwlab-agent-skills:skillsabcd",
          imageTag: "skillsabcd",
          digest: "sha256:" + "5".repeat(64),
          namespace: "hwlab-dev",
          profile: "dev",
          healthPath: "/health/live",
          buildCreatedAt: null,
          buildSource: null
        },
        {
          serviceId: "hwlab-extra-lab",
          commitId: "extracatabcdef123456",
          image: "127.0.0.1:5000/hwlab/hwlab-extra-lab:extracat",
          imageTag: "extracat",
          digest: "sha256:" + "4".repeat(64),
          namespace: "hwlab-dev",
          profile: "dev",
          healthPath: "/health/live",
          buildCreatedAt: "2026-05-23T03:00:00.000Z",
          buildSource: "artifact-catalog-test"
        }
      ]
    }
  };

  const server = createCloudApiServer({
    env: {
      PATH: "",
      HWLAB_COMMIT_ID: "apiabcdef123456",
      HWLAB_IMAGE: "127.0.0.1:5000/hwlab/hwlab-cloud-api:apiabcd",
      HWLAB_IMAGE_TAG: "apiabcd",
      HWLAB_BUILD_CREATED_AT: "2026-05-23T01:00:00.000Z",
      HOSTNAME: "hwlab-cloud-api-test-pod",
      POD_NAMESPACE: "hwlab-dev",
      HWLAB_RUNTIME_CONTAINER: "hwlab-cloud-api",
      HWLAB_CLOUD_WEB_SERVICE_URL: "http://live.test/hwlab-cloud-web",
      HWLAB_GATEWAY_URL: "http://live.test/hwlab-gateway",
      HWLAB_GATEWAY_SIMU_URL: "http://live.test/missing-gateway-simu",
      HWLAB_BOX_SIMU_URL: "http://live.test/missing-box-simu",
      HWLAB_PATCH_PANEL_URL: "http://live.test/missing-patch-panel",
      HWLAB_ROUTER_URL: "http://live.test/missing-router",
      HWLAB_TUNNEL_CLIENT_URL: "http://live.test/missing-tunnel-client",
      HWLAB_EDGE_PROXY_URL: "http://live.test/external",
      HWLAB_CLI_URL: "http://live.test/missing-cli",
      HWLAB_AGENT_SKILLS_URL: "http://live.test/hwlab-agent-skills",
      HWLAB_EXTRA_LAB_URL: "http://live.test/missing-extra-lab"
    },
    liveBuildMetadata,
    fetchImpl: async (url) => {
      const path = new URL(url).pathname;
      const payload = healthByPath.get(path);
      if (!payload) {
        return {
          ok: false,
          status: 503,
          async text() {
            return JSON.stringify({ error: "offline" });
          }
        };
      }
      return {
        ok: true,
        status: 200,
        async text() {
          return JSON.stringify(payload);
        }
      };
    }
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const response = await fetch(`http://127.0.0.1:${port}/v1/live-builds`);
    assert.equal(response.status, 200);
    const payload = await response.json();
    assert.equal(payload.contractVersion, "live-builds-v1");
    assert.equal(payload.latest.serviceId, "hwlab-agent-skills");
    assert.equal(payload.latest.build.createdAt, "2026-05-23T05:30:00.000Z");
    assert.equal(payload.latest.image.tag, "skillsabcd");
    assert.equal(payload.latest.commit.id, "skillsabcdef123456");
    assert.equal(payload.latest.build.metadataSource, "deploy-env:HWLAB_BUILD_CREATED_AT");
    assert.equal(payload.latest.build.liveMetadataMatch.metadata.imageTag, "skillsabcd");
    assert.match(payload.latest.build.liveHealthMissingReason, /匹配的 deploy metadata/u);
    const api = payload.services.find((service) => service.serviceId === "hwlab-cloud-api");
    assert.equal(api.runtime.pod.name, "hwlab-cloud-api-test-pod");
    assert.equal(api.runtime.unideskRoute, "G14:k3s:hwlab-dev:pod:hwlab-cloud-api-test-pod:hwlab-cloud-api");
    assert.equal(payload.counts.total, payload.services.length);
    assert.equal(payload.counts.external, 2);
    assert.equal(payload.counts.withBuildTime, 3);
    const gateway = payload.services.find((service) => service.serviceId === "hwlab-gateway");
    assert.equal(gateway.build.createdAt, null);
    assert.equal(gateway.image.tag, "48dfbf9");
    assert.equal(gateway.revision, "48dfbf9");
    assert.equal(gateway.build.metadataSource, "live-health:controlled-metadata-mismatch");
    assert.equal(gateway.build.unavailableReason, "构建时间不可用：live tag 与 catalog metadata 不匹配");
    assert.match(gateway.build.unavailableDetail, /live tag 48dfbf9/u);
    assert.match(gateway.build.unavailableDetail, /metadata tag 3df89fe/u);
    const extra = payload.services.find((service) => service.serviceId === "hwlab-extra-lab");
    assert.equal(extra.build.createdAt, null);
    assert.match(extra.build.unavailableReason, /当前 live health 不可用/u);
    assert.ok(payload.services.some((service) => service.kind === "external" && service.serviceId === "hwlab-edge-proxy" && service.healthUrl.endsWith("/health")));
    assert.ok(payload.services.some((service) => service.kind === "external" && service.serviceId === "hwlab-frpc" && service.image.tag === "v0.68.1"));
    assert.ok(payload.services.some((service) => service.serviceId === "hwlab-gateway"));
  } finally {
    await new Promise((resolve, reject) => {
      server.close((error) => (error ? reject(error) : resolve()));
    });
  }
});
test("cloud api ignores old repository reports and keeps missing buildCreatedAt unavailable", async () => {
  const tempRoot = await mkdtemp(path.join(os.tmpdir(), "hwlab-live-builds-"));
  const deployDir = path.join(tempRoot, "deploy");
  const oldReportDir = path.join(tempRoot, "reports", "dev-gate");
  await mkdir(deployDir, { recursive: true });
  await mkdir(oldReportDir, { recursive: true });
  await writeFile(path.join(deployDir, "deploy.yaml"), `${JSON.stringify({
    services: [
      {
        serviceId: "hwlab-gateway",
        image: "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9",
        namespace: "hwlab-dev",
        profile: "dev",
        healthPath: "/health/live",
        replicas: 1
      }
    ]
  }, null, 2)}\n`);
  await writeFile(path.join(deployDir, "artifact-catalog.dev.json"), `${JSON.stringify({
    services: [
      {
        serviceId: "hwlab-gateway",
        commitId: "48dfbf9abcdef",
        image: "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9",
        imageTag: "48dfbf9",
        digest: "sha256:" + "6".repeat(64),
        namespace: "hwlab-dev",
        profile: "dev",
        healthPath: "/health/live",
        buildCreatedAt: null,
        buildSource: null
      }
    ]
  }, null, 2)}\n`);
  await writeFile(path.join(oldReportDir, "dev-artifacts.json"), `${JSON.stringify({
    artifactPublish: {
      services: [
        {
          serviceId: "hwlab-gateway",
          sourceCommitId: "48dfbf9abcdef",
          image: "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9",
          imageTag: "48dfbf9",
          digest: "sha256:" + "6".repeat(64),
          buildCreatedAt: "2099-01-01T00:00:00.000Z",
          buildSource: "old-report-must-not-be-read"
        }
      ]
    }
  }, null, 2)}\n`);

  const observe = async () => {
    const server = createCloudApiServer({
      env: {
        PATH: "",
        HWLAB_GATEWAY_URL: "http://live.test/hwlab-gateway"
      },
      repoRoot: tempRoot,
      fetchImpl: async (url) => {
        if (new URL(url).pathname === "/hwlab-gateway/health/live") {
          return {
            ok: true,
            status: 200,
            async text() {
              return JSON.stringify({
                serviceId: "hwlab-gateway",
                status: "ok",
                revision: "48dfbf9abcdef",
                commit: { id: "48dfbf9abcdef", source: "test" },
                image: {
                  reference: "127.0.0.1:5000/hwlab/hwlab-gateway:48dfbf9",
                  tag: "48dfbf9",
                  digest: "sha256:" + "6".repeat(64)
                }
              });
            }
          };
        }
        return {
          ok: false,
          status: 503,
          async text() {
            return JSON.stringify({ error: "offline" });
          }
        };
      }
    });
    await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
    try {
      const { port } = server.address();
      const response = await fetch(`http://127.0.0.1:${port}/v1/live-builds`);
      assert.equal(response.status, 200);
      return response.json();
    } finally {
      await new Promise((resolve, reject) => {
        server.close((error) => (error ? reject(error) : resolve()));
      });
    }
  };

  try {
    const withOldReport = await observe();
    await rm(path.join(tempRoot, "reports"), { recursive: true, force: true });
    const withoutOldReport = await observe();
    const summarize = (payload) => {
      const manager = payload.services.find((service) => service.serviceId === "hwlab-gateway");
      return {
        latest: payload.latest,
        source: payload.source,
        createdAt: manager.build.createdAt,
        metadataSource: manager.build.metadataSource,
        unavailableReason: manager.build.unavailableReason,
        unavailableDetail: manager.build.unavailableDetail,
        liveMetadataMatch: manager.build.liveMetadataMatch,
        counts: payload.counts
      };
    };

    assert.deepEqual(summarize(withOldReport), summarize(withoutOldReport));
    const manager = withOldReport.services.find((service) => service.serviceId === "hwlab-gateway");
    assert.equal(withOldReport.latest, null);
    assert.equal(manager.build.createdAt, null);
    assert.equal(Object.hasOwn(withOldReport.source, "artifactReportSource"), false);
    assert.equal(JSON.stringify(withOldReport).includes("2099-01-01T00:00:00.000Z"), false);
    assert.equal(JSON.stringify(withOldReport).includes("artifact-report"), false);
  } finally {
    await rm(tempRoot, { recursive: true, force: true });
  }
});
