import { expect, test } from "bun:test";
import { mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { spawnSync } from "node:child_process";

import { resolveAgentRunLaneTarget } from "./agentrun-lanes";
import { renderAgentRunPipelineManifest } from "./agentrun-manifests";
import { renderPacFeatureConfigSchemaStage } from "./pac-feature-config-schema-stage";

test("shared stage injects owning YAML OTel values for all three PaC repositories", () => {
  for (const [repositoryId, consumer] of [
    ["hwlab-nc01-v03", "hwlab-nc01-v03"],
    ["agentrun-nc01-v02", "agentrun-nc01-v02"],
    ["sentinel-nc01-v03", "sentinel-nc01-v03"],
  ] as const) {
    const stage = renderPacFeatureConfigSchemaStage({
      repositoryId,
      consumer,
      repoDirExpression: "'/workspace/source'",
      renderedRootExpression: "'/workspace/rendered'",
    });
    expect(stage).toContain("feature-config-schema-validation");
    expect(stage).toContain("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT='http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces'");
    expect(stage).toContain("OTEL_TRACES_SAMPLER_ARG='1'");
    expect(stage).toContain("OTEL_EXPORTER_TIMEOUT_MS='300'");
    expect(stage).toContain("base64 -d | gzip -d");
    expect(stage).toContain("NODE_UNIDESK_FEATURE_CONFIG_SCHEMA");
    expect(stage).not.toContain("cicd.feature_config.schema");
    expect(stage).not.toContain("NODE_UNIDESK_AJV2020_BUNDLE");
    expect(stage).not.toContain("process.env.webProbeSentinel");
  }
});

test("HWLAB, AgentRun, and Sentinel normal stages validate without source workspace node_modules", () => {
  const root = mkdtempSync(join(tmpdir(), "feature-config-consumers-"));
  try {
    const source = join(root, "source");
    const rendered = join(root, "rendered");
    const temp = join(root, "tmp");
    mkdirSync(join(source, "config"), { recursive: true });
    mkdirSync(rendered, { recursive: true });
    mkdirSync(temp);
    writeFileSync(join(source, "config", "feature-config.schema.json"), JSON.stringify({
      $schema: "https://json-schema.org/draft/2020-12/schema",
      type: "object",
      properties: { webProbeSentinel: { type: "boolean", "x-unidesk-feature": "web-probe-sentinel" } },
      additionalProperties: false,
    }));
    writeFileSync(join(rendered, "deployment.json"), JSON.stringify({
      apiVersion: "apps/v1",
      kind: "Deployment",
      spec: { template: { spec: { containers: [{ name: "fixture", env: [{ name: "webProbeSentinel", value: "true" }] }] } } },
    }));
    const vendorBundle = resolve(import.meta.dir, "../vendor/ajv-dist/8.17.1/ajv2020.min.js");
    for (const consumer of [
      { repositoryId: "hwlab-nc01-v03", consumer: "hwlab-nc01-v03", ajvBundleExpression: JSON.stringify(vendorBundle) },
      { repositoryId: "agentrun-nc01-v02", consumer: "agentrun-nc01-v02" },
      { repositoryId: "sentinel-nc01-v03", consumer: "sentinel-nc01-v03" },
    ]) {
      const stage = renderPacFeatureConfigSchemaStage({
        ...consumer,
        repoDirExpression: JSON.stringify(source),
        renderedRootExpression: JSON.stringify(rendered),
      });
      const result = spawnSync("sh", [], {
        input: `set -eu\n${stage}\n`,
        encoding: "utf8",
        env: { ...process.env, TMPDIR: temp },
      });
      expect(result.status, consumer.consumer).toBe(0);
      expect(result.stderr, consumer.consumer).toContain('"code":"feature-config-schema-valid"');
      expect(result.stderr, consumer.consumer).not.toContain("feature-config-validator-unavailable");
      expect(result.stderr, consumer.consumer).not.toContain("feature-config-validator-process-failure");
      expect(readdirSync(temp), consumer.consumer).toEqual([]);
    }
  } finally {
    rmSync(root, { recursive: true, force: true });
  }
});

test("embedded node process failure remains a typed warning with zero shell exit", () => {
  const root = mkdtempSync(join(tmpdir(), "feature-config-stage-"));
  try {
    const bin = join(root, "bin");
    const temp = join(root, "tmp");
    mkdirSync(bin);
    mkdirSync(temp);
    writeFileSync(join(bin, "node"), "#!/bin/sh\nexit 71\n", { mode: 0o755 });
    const stage = renderPacFeatureConfigSchemaStage({
      repositoryId: "sentinel-nc01-v03",
      consumer: "sentinel-nc01-v03",
      repoDirExpression: "'/workspace/source'",
      renderedRootExpression: "'/workspace/rendered'",
    });
    const result = spawnSync("sh", [], {
      input: `set -eu\n${stage}\n`,
      env: { ...process.env, PATH: `${bin}:${process.env.PATH ?? ""}`, TMPDIR: temp },
      encoding: "utf8",
    });
    expect(result.status).toBe(0);
    expect(result.stderr).toContain("feature-config-validator-process-failure");
    expect(readdirSync(temp)).toEqual([]);
  } finally {
    rmSync(root, { recursive: true, force: true });
  }
});

test("AgentRun normal GitOps publish task contains the shared warning-only stage", () => {
  const { spec } = resolveAgentRunLaneTarget({ node: "NC01", lane: "nc01-v02" });
  const pipeline = renderAgentRunPipelineManifest(spec) as Record<string, any>;
  const scripts = pipeline.spec.tasks.flatMap((task: Record<string, any>) => task.taskSpec.steps.map((step: Record<string, any>) => String(step.script ?? ""))).join("\n");
  expect(scripts).toContain("UNIDESK_PAC_CONSUMER='agentrun-nc01-v02'");
  expect(scripts).toContain("feature-config-schema-validation");
  expect(scripts).toContain("feature-config-validator-process-failure");
  expect(scripts).toContain("OTEL_EXPORTER_TIMEOUT_MS='300'");
  expect(scripts).toContain("base64 -d | gzip -d");
  expect(scripts).toContain("NODE_UNIDESK_FEATURE_CONFIG_SCHEMA");
  expect(scripts).not.toContain("NODE_UNIDESK_AJV2020_BUNDLE");
});

test("repo-owned schema is Draft 2020-12 with one canonical feature property", () => {
  const schema = JSON.parse(readFileSync("config/feature-config.schema.json", "utf8"));
  expect(schema.$schema).toBe("https://json-schema.org/draft/2020-12/schema");
  expect(schema.properties.webProbeSentinel["x-unidesk-feature"]).toBe("web-probe-sentinel");
  expect(Object.keys(schema.properties)).toEqual(["webProbeSentinel"]);
});
