import { afterEach, expect, test } from "bun:test";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { rootPath } from "../../src/config";
import { exportCicdOtelPayload, loadCicdOtelRuntimeConfig, selectCicdOtelRepository } from "./otel-runtime-config";

const roots: string[] = [];

afterEach(() => {
  while (roots.length > 0) rmSync(roots.pop()!, { recursive: true, force: true });
});

test("unexpanded Repository inputs fall back to materialized owning YAML", () => {
  const config = loadCicdOtelRuntimeConfig("sentinel-nc01-v03", {
    OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "{{otel_traces_endpoint}}",
    OTEL_SERVICE_NAME: "{{otel_service_name}}",
    OTEL_TRACES_SAMPLER_ARG: "{{otel_sampling_ratio}}",
    OTEL_EXPORTER_TIMEOUT_MS: "{{otel_exporter_timeout_ms}}",
  });
  expect(config).toMatchObject({
    endpoint: "http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces",
    endpointHostname: "otel-collector.platform-infra.svc.cluster.local",
    serviceName: "unidesk-cicd-sentinel-nc01",
    samplingRatio: 1,
    timeoutMs: 300,
  });
  expect(config.fallbackCodes).toEqual([
    "INVALID_ENDPOINT_INPUT",
    "INVALID_SERVICE_INPUT",
    "INVALID_SAMPLING_INPUT",
    "INVALID_TIMEOUT_INPUT",
  ]);
});

test("valid but mismatched runtime inputs cannot override owning YAML", () => {
  const config = loadCicdOtelRuntimeConfig("sentinel-nc01-v03", {
    OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://collector.example.invalid:4318/v1/traces",
    OTEL_SERVICE_NAME: "different-service",
    OTEL_TRACES_SAMPLER_ARG: "0.5",
    OTEL_EXPORTER_TIMEOUT_MS: "600",
  });
  expect(config.endpointHostname).toBe("otel-collector.platform-infra.svc.cluster.local");
  expect(config.serviceName).toBe("unidesk-cicd-sentinel-nc01");
  expect(config.samplingRatio).toBe(1);
  expect(config.timeoutMs).toBe(300);
  expect(config.fallbackCodes).toEqual([
    "MISMATCH_ENDPOINT_INPUT",
    "MISMATCH_SERVICE_INPUT",
    "MISMATCH_SAMPLING_INPUT",
    "MISMATCH_TIMEOUT_INPUT",
  ]);
});

test("legal owning endpoint and non-blocking exporter failure share the bounded helper", async () => {
  const config = loadCicdOtelRuntimeConfig("sentinel-nc01-v03", {
    OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces",
    OTEL_SERVICE_NAME: "unidesk-cicd-sentinel-nc01",
    OTEL_TRACES_SAMPLER_ARG: "1",
    OTEL_EXPORTER_TIMEOUT_MS: "300",
  });
  expect(config.fallbackCodes).toEqual([]);
  const warning = await exportCicdOtelPayload(
    config,
    "00-11111111111111111111111111111111-2222222222222222-01",
    "11111111111111111111111111111111",
    { resourceSpans: [] },
    async () => { throw Object.assign(new TypeError("fetch failed"), { cause: { code: "ENOTFOUND" } }); },
  );
  expect(warning).toEqual({
    event: "cicd.otel.export",
    warning: true,
    blocking: false,
    causeCode: "ENOTFOUND",
    endpointHostname: "otel-collector.platform-infra.svc.cluster.local",
    timedOut: false,
    errorType: "TypeError",
    timeoutMs: 300,
    traceId: "11111111111111111111111111111111",
    valuesRedacted: true,
  });
});

test("repository identity selection fails closed on zero or multiple exact matches", () => {
  expect(() => selectCicdOtelRepository([], "sentinel-nc01-v03")).toThrow("matched 0");
  expect(() => selectCicdOtelRepository([
    { id: "sentinel-nc01-v03", params: {} },
    { id: "sentinel-nc01-v03", params: {} },
  ], "sentinel-nc01-v03")).toThrow("matched 2");
});

test("actual renderer replaces unexpanded inputs without changing business exit", async () => {
  const result = await render({ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "{{otel_traces_endpoint}}" }, false);
  expect(result.exitCode).toBe(0);
  expect(result.stderr).toContain('"event":"cicd.otel.config"');
  expect(result.stderr).toContain('"blocking":false');
  expect(result.stderr).toContain('"endpointHostname":"otel-collector.platform-infra.svc.cluster.local"');
  expect(result.stderr).not.toContain("{{otel_traces_endpoint}}");
  expect(readFileSync(join(result.outputDir, "source.sh"), "utf8")).toContain("http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces");
  const publish = readFileSync(join(result.outputDir, "publish.sh"), "utf8");
  expect(publish).toContain("UNIDESK_PAC_CONSUMER='sentinel-nc01-v03'");
  expect(publish).toContain("feature-config-schema-validation");
  expect(publish).toContain("cicd.feature_config.schema");
  expect(publish).toContain("OTEL_EXPORTER_TIMEOUT_MS='300'");
  expect(publish).toContain("feature-config-validator-process-failure");
});

async function render(env: Record<string, string>, traced = true) {
  const outputDir = mkdtempSync(join(tmpdir(), "unidesk-render-sentinel-publish-"));
  roots.push(outputDir);
  const child = Bun.spawn({
    cmd: [
      process.execPath,
      "scripts/native/cicd/render-sentinel-publish-task.ts",
      "--node", "NC01",
      "--lane", "v03",
      "--repository-id", "sentinel-nc01-v03",
      "--sentinel", "nc01-web-probe-sentinel",
      "--pipeline-run", "hwlab-web-probe-sentinel-nc01-test",
      "--source-commit", "a".repeat(40),
      "--source-stage-ref", `refs/unidesk/snapshots/test/${"a".repeat(40)}`,
      "--source-authority", "gitea-snapshot",
      "--output-dir", outputDir,
    ],
    cwd: rootPath(),
    env: {
      ...process.env,
      TRACEPARENT: traced ? "00-11111111111111111111111111111111-2222222222222222-01" : "",
      OTEL_SERVICE_NAME: "unidesk-cicd-test",
      OTEL_TRACES_SAMPLER_ARG: "1",
      OTEL_EXPORTER_TIMEOUT_MS: "300",
      ...env,
    },
    stdout: "pipe",
    stderr: "pipe",
  });
  const [exitCode, stdout, stderr] = await Promise.all([
    child.exited,
    new Response(child.stdout).text(),
    new Response(child.stderr).text(),
  ]);
  return {
    exitCode,
    stdout,
    stderr,
    outputDir,
  };
}
