import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { join } from "node:path";
// The launcher is deliberately JavaScript outside the collector TS project; importing
// the real implementation here is the contract test, not a copied fixture.
// @ts-expect-error no declaration file is shipped for the workstation .mjs module
import { buildJobManifest } from "../../../modules/workstation/claude/lib/k3s-remote-build.mjs";
import { BUILD_KEY_ANNOTATION, createKubernetesAdapter, projectCluster } from "./kubernetes";
import type { SessionRecord } from "../sessions/ledger";

const FIXTURE = JSON.parse(readFileSync(join(import.meta.dir, "../../test/fixtures/kubernetes/placement-cases.json"), "utf8"));
function session(id: string, extra: Partial<SessionRecord> = {}): SessionRecord {
  return { id, runtime: "codex", pid: null, cwd: "/repo", repo: "overdeck", repoRoot: "/repo", branch: "main", worktree: null,
    host: null, startedAt: FIXTURE.observedAt, lastHeartbeatAt: null, lastProgressAt: null, lastActiveAt: null, finishedAt: null,
    finishReason: null, transcriptPath: null, tmuxSession: null, tmuxSocket: null, mux: null, attached: null, evidence: null,
    parent: null, parentLedgerId: null, state: "ALIVE-WORKING", resumeId: null, launchedBy: null, title: null, activity: null,
    account: null, buildKey: null, runId: null, workloadUid: null, ...extra };
}
function snapshot(overrides: Record<string, unknown> = {}) {
  return projectCluster({ clusterId: "fixture", observedAt: FIXTURE.observedAt, nodes: FIXTURE.nodes,
    scopes: [{ namespace: "builds", pods: FIXTURE.pods, jobs: FIXTURE.jobs }, { namespace: "forbidden", error: "403 Forbidden" }],
    ledger: { dir: "/fixture", missing: false, unreadable: [], sessions: [session("session-exact", { buildKey: "build/exact" }), session("session-host", { host: "node-b" }), session("session-missing")] },
    builds: [{ buildKey: "build/exact", sessionId: "session-exact" }, { buildKey: "build/expired", sessionId: null }],
    prometheus: { status: "absent", error: "fixture outage" }, ...overrides } as any);
}

describe("kubernetes projection contracts", () => {
  test("covers exact, host-only, unplaced, conflicting, expired and partial-source cases without invented metrics", () => {
    const result = snapshot();
    expect(result.nodes.map((node) => node.name)).toEqual(["node-a", "node-b"]);
    expect(result.nodes.flatMap((node) => node.sessions).find((entry) => entry.sessionId === "session-exact")?.confidence).toBe("exact");
    expect(result.nodes.flatMap((node) => node.sessions).find((entry) => entry.sessionId === "session-host")?.confidence).toBe("host-only");
    expect(result.unplacedSessions.map((entry) => entry.sessionId)).toContain("session-missing");
    expect(result.unmatchedWorkloads.find((entry) => entry.uid === "pod-conflict")?.annotations.conflict).toBe(true);
    expect(result.unplacedBuilds.find((entry) => entry.buildKey === "build/expired")?.nodeName).toBeNull();
    expect(result.sources.kubernetes.status).toBe("partial");
    expect(result.sources.kubernetes.scopes?.incomplete).toEqual(["forbidden"]);
    expect(result.sources.prometheus.status).toBe("absent");
    expect(result.summary.usedCpuCores).toBeNull();
    expect(result.nodes[0]?.utilization.memoryBytes).toBeNull();
  });

  test("excludes terminal session history from operational placement", () => {
    const result = snapshot({
      ledger: {
        dir: "/fixture",
        missing: false,
        unreadable: [],
        sessions: [
          session("live"),
          session("finished", { state: "FINISHED", finishedAt: FIXTURE.observedAt }),
        ],
      },
    });

    expect(result.unplacedSessions.map((entry) => entry.sessionId)).toEqual(["live"]);
  });

  test("an absent ledger reports absent and fabricates no session placement", () => {
    const result = snapshot({ ledger: { dir: "/missing", missing: true, unreadable: [], sessions: [] } });
    expect(result.sources.ledger.status).toBe("absent");
    expect(result.nodes.flatMap((node) => node.sessions)).toEqual([]);
    expect(result.unplacedSessions).toEqual([]);
  });

  test("reads the canonical annotation from the real launcher Job and Pod template shape", () => {
    const manifest = buildJobManifest({ key: "literal/key with spaces", argv: ["bun", "test"],
      workingDir: "/var/lib/buildbox/builds/contract", nodeNames: ["fixture-node"], epoch: "1" },
    { name: "rb-contract", namespace: "builds", image: "fixture:test" });
    expect(manifest.metadata.annotations[BUILD_KEY_ANNOTATION]).toBe("literal/key with spaces");
    expect(manifest.spec.template.metadata.annotations[BUILD_KEY_ANNOTATION]).toBe("literal/key with spaces");
    expect(manifest.metadata.labels[BUILD_KEY_ANNOTATION]).not.toBe("literal/key with spaces");
  });

  test("adapter reports partial namespaces and reads only closed API paths", async () => {
    const calls: string[] = [];
    const kubeconfig = Buffer.from("ca").toString("base64");
    const adapter = createKubernetesAdapter({ kubeconfigPath: "/fixture/kubeconfig", namespaces: ["builds", "forbidden"], prometheusUrl: "http://127.0.0.1:9090",
      readFileImpl: () => `apiVersion: v1\ncurrent-context: fixture\nclusters:\n- name: fixture\n  cluster: {server: https://127.0.0.1:6443, certificate-authority-data: ${kubeconfig}}\nusers:\n- name: observer\n  user: {token: read-only}\ncontexts:\n- name: fixture\n  context: {cluster: fixture, user: observer}\n`,
      readLedgerImpl: () => ({ dir: "/fixture", missing: false, unreadable: [], sessions: [] }), readBuildsImpl: () => [], now: () => Date.parse(FIXTURE.observedAt),
      fetchImpl: async (input) => { const url = String(input); calls.push(url);
        if (url.includes("127.0.0.1:9090")) return Response.json({ status: "success", data: { result: [] } });
        if (url.endsWith("/api/v1/nodes")) return Response.json({ items: FIXTURE.nodes });
        if (url.includes("/namespaces/forbidden/")) return new Response("forbidden", { status: 403 });
        if (url.endsWith("/pods")) return Response.json({ items: FIXTURE.pods });
        if (url.endsWith("/jobs")) return Response.json({ items: FIXTURE.jobs });
        return new Response("not found", { status: 404 }); } });
    const result = await adapter.poll();
    expect((result.panels[0]?.data as any).sources.kubernetes.status).toBe("partial");
    expect(calls.some((url) => url.includes("labelSelector") || url.includes("fieldSelector"))).toBe(false);
  });
});
