/*
 * SPEC: PJ2026-010102 HWPOD 工具；PJ2026-010103 HWPOD 服务；
 * PJ2026-01010305 71FREQ 预装 draft-2026-06-26-71freq-v03-hwpod-preinstall。
 * 责任: 验证 runtime spec CRUD、重启持久化与 YAML-first 内置冻结合同。
 */

import { expect, test } from "bun:test";

import { buildHwpodTopologyFromRegistry, createHwpodHttpApp } from "./http.ts";
import { createHwpodSpecRegistry, type HwpodRuntimeSpecRecord, type HwpodRuntimeSpecStore } from "./spec-registry.ts";

test("runtime spec repository persists CRUD and freezes YAML-first built-ins", async () => {
  const store = memoryStore();
  const builtIn = spec("builtin-pod", "/builtin");
  const registry = createHwpodSpecRegistry({ store, builtIns: [{ configRef: "config/hwpod.yaml#spec", sha256: "source-sha", document: builtIn }] });

  const initial = await registry.list();
  expect(initial.specs).toHaveLength(1);
  expect(initial.specs[0]).toMatchObject({ hwpodId: "builtin-pod", authority: "yaml-first-builtin", mutable: false, frozen: true });

  const created = await registry.create(spec("runtime-pod", "/runtime-v1"));
  expect(created).toMatchObject({ status: "created", hwpodId: "runtime-pod", authority: "runtime", mutable: true, frozen: false, mutation: true });

  const restarted = createHwpodSpecRegistry({ store, builtIns: [{ configRef: "config/hwpod.yaml#spec", document: builtIn }] });
  expect((await restarted.get("runtime-pod")).document.spec.workspace.path).toBe("/runtime-v1");
  expect((await restarted.update("runtime-pod", spec("runtime-pod", "/runtime-v2"))).status).toBe("updated");
  expect((await restarted.get("runtime-pod")).document.spec.workspace.path).toBe("/runtime-v2");
  expect((await restarted.delete("runtime-pod")).status).toBe("deleted");
  await expect(restarted.get("runtime-pod")).rejects.toMatchObject({ code: "hwpod_spec_not_found" });

  await expect(restarted.create(spec("builtin-pod", "/shadow"))).rejects.toMatchObject({ code: "hwpod_spec_frozen", details: { mutation: false } });
  await expect(restarted.update("builtin-pod", builtIn)).rejects.toMatchObject({ code: "hwpod_spec_frozen" });
  await expect(restarted.delete("builtin-pod")).rejects.toMatchObject({ code: "hwpod_spec_frozen" });
  expect((await restarted.get("builtin-pod")).document.spec.workspace.path).toBe("/builtin");
});

test("L1 HWPOD HTTP exposes runtime CRUD and typed frozen errors", async () => {
  const registry = createHwpodSpecRegistry({ store: memoryStore(), builtIns: [{ configRef: "config/hwpod.yaml#spec", document: spec("builtin-pod", "/builtin") }] });
  const temporal = { close: async () => {}, start: async () => ({}), describe: async () => ({}), result: async () => ({}) };
  const app = createHwpodHttpApp({ nodeOpsApiUrl: "http://native-hwpod.test", temporal, specRegistry: registry, nodeRegistry: nodeRegistry() });

  expect(await request(app, "GET", "/health/ready")).toMatchObject({ status: 200, body: { ok: true, specRegistry: "ready", specCount: 1, nodeRegistry: "ready" } });

  const created = await request(app, "POST", "/v1/hwpod/specs", { document: spec("runtime-http", "/http-v1") });
  expect(created).toMatchObject({ status: 201, body: { ok: true, status: "created", hwpodId: "runtime-http" } });
  expect(await request(app, "GET", "/v1/hwpod/specs/runtime-http")).toMatchObject({ status: 200, body: { document: { spec: { workspace: { path: "/http-v1" } } } } });
  expect(await request(app, "PUT", "/v1/hwpod/specs/runtime-http", { document: spec("runtime-http", "/http-v2") })).toMatchObject({ status: 200, body: { status: "updated" } });
  expect(await request(app, "DELETE", "/v1/hwpod/specs/runtime-http")).toMatchObject({ status: 200, body: { status: "deleted", mutation: true } });

  const frozen = await request(app, "DELETE", "/v1/hwpod/specs/builtin-pod");
  expect(frozen).toMatchObject({ status: 409, body: { ok: false, error: { code: "hwpod_spec_frozen", authority: "yaml-first-builtin", mutation: false } } });
  expect((await request(app, "GET", "/v1/hwpod/specs/builtin-pod")).body.document.spec.workspace.path).toBe("/builtin");
  await app.close();
});

test("L0 HWPOD topology is built from the local registry without Cloud API", async () => {
  const registry = createHwpodSpecRegistry({
    store: memoryStore(),
    builtIns: [{ configRef: "config/hwpod.yaml#spec", document: spec("builtin-pod", "/builtin") }],
  });
  await registry.create(spec("runtime-pod", "/runtime"));

  const topology = await buildHwpodTopologyFromRegistry(registry, { resource: "device", q: "runtime-pod" });
  expect(topology).toMatchObject({
    ok: true,
    contractVersion: "hwpod-topology-v1",
    resource: "device",
    service: { connectedNodeCount: 0, websocketMode: "hwpod-native-api-no-node-registry" },
    summary: { deviceCount: 2, onlineNodeCount: 0 },
    items: [{ hwpodId: "runtime-pod", nodeId: "node-test", status: "offline", source: { authority: "runtime" } }],
  });
  expect(topology.items[0].blockers).toContainEqual(expect.objectContaining({ code: "hwpod_node_offline" }));

  const temporal = { close: async () => {}, start: async () => ({}), describe: async () => ({}), result: async () => ({}) };
  const app = createHwpodHttpApp({ nodeOpsApiUrl: "http://native-hwpod.test", temporal, specRegistry: registry, nodeRegistry: nodeRegistry() });
  const response = await request(app, "GET", "/v1/hwpod/topology?resource=node");
  expect(response).toMatchObject({ status: 200, body: { ok: true, resource: "node", items: [{ nodeId: "node-test", status: "offline" }] } });
  await app.close();
});

test("L1 Web operation compiles a registry spec and submits native node-ops through Temporal", async () => {
  const registry = createHwpodSpecRegistry({ store: memoryStore(), builtIns: [{ configRef: "config/hwpod.yaml#spec", document: spec("web-pod", "/workspace") }] });
  let submitted: any = null;
  const temporal = {
    close: async () => {},
    start: async (input: unknown) => { submitted = input; return { workflowId: "hwpod-operation-test", workflowRunId: "run-test" }; },
    describe: async () => ({}),
    result: async () => ({}),
  };
  const app = createHwpodHttpApp({ nodeOpsApiUrl: "http://127.0.0.1:6681", temporal, specRegistry: registry, nodeRegistry: nodeRegistry() });
  const response = await request(app, "POST", "/v1/hwpod/operations", { hwpodId: "web-pod", intent: "workspace.ls", args: { path: "." } });
  expect(response).toMatchObject({ status: 202, body: { ok: true, status: "accepted", hwpodId: "web-pod", authority: "hwpod-temporal" } });
  expect(submitted).toMatchObject({ runtimeApiUrl: "http://127.0.0.1:6681", plan: { hwpodId: "web-pod", nodeId: "node-test", intent: "workspace.ls", ops: [{ op: "workspace.ls", args: { path: ".", workspacePath: "/workspace" } }] } });
  await app.close();
});

async function request(app: any, method: string, pathname: string, body?: unknown) {
  const response = await app.fetch(new Request(`http://hwpod.test${pathname}`, { method, headers: { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }) }));
  return { status: response.status, body: await response.json() };
}

function memoryStore(): HwpodRuntimeSpecStore {
  const records = new Map<string, HwpodRuntimeSpecRecord>();
  return {
    list: async () => [...records.values()],
    get: async (id) => records.get(id) ?? null,
    create: async (record) => records.has(record.hwpodId) ? false : (records.set(record.hwpodId, structuredClone(record)), true),
    update: async (id, document, updatedAt) => {
      const existing = records.get(id);
      if (!existing) return null;
      const record = { ...existing, document: structuredClone(document), updatedAt };
      records.set(id, record);
      return record;
    },
    delete: async (id) => { const record = records.get(id) ?? null; records.delete(id); return record; },
  };
}
function nodeRegistry() {
  return {
    describe: () => ({ mode: "hwpod-native-api-websocket", connectedCount: 0, nodes: [] }),
    lookup: async () => null,
    dispatch: async () => ({ ok: true, status: "completed", results: [] }),
  };
}
function spec(name: string, workspacePath: string) { return { apiVersion: "hwlab.dev/v0alpha1", kind: "Hwpod", metadata: { name }, spec: { targetDevice: { board: "test" }, workspace: { path: workspacePath }, debugProbe: { type: "test" }, ioProbe: { type: "test" }, nodeBinding: { nodeId: "node-test" } } }; }
