/**
 * Smoke tests for the `migrate-to-cf-observability.ts` codemod.
 *
 * Drives the script as a child process against tmp wrangler.jsonc fixtures.
 * Verifies the operationally important behaviors:
 *  - rewriting an existing observability block to the canonical CF-native
 *    shape (no destinations, master enabled flag set)
 *  - appending a new canonical block when none exists
 *  - second run is a no-op (idempotency / CI guard)
 *  - HyperDX-style destinations are stripped on the next run
 *  - opt-in destination forwarding via `--destination-logs` /
 *    `--destination-traces`
 *  - result is valid JSONC (parses after stripping comments)
 */
import * as cp from "node:child_process";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";

const SCRIPT = path.resolve(__dirname, "migrate-to-cf-observability.ts");

function runCodemod(args: string[]): { stdout: string; stderr: string; code: number } {
  const r = cp.spawnSync("npx", ["tsx", SCRIPT, ...args], { encoding: "utf8" });
  return { stdout: r.stdout || "", stderr: r.stderr || "", code: r.status ?? 0 };
}

function stripJsoncComments(s: string): string {
  return s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
}

describe("migrate-to-cf-observability codemod", () => {
  let tmpDir: string;
  let wranglerPath: string;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "cf-codemod-"));
    wranglerPath = path.join(tmpDir, "wrangler.jsonc");
  });

  afterEach(() => {
    fs.rmSync(tmpDir, { recursive: true, force: true });
  });

  it("rewrites a partial observability block to the canonical CF-native shape", () => {
    fs.writeFileSync(
      wranglerPath,
      `{
  "name": "lebiscuit-tanstack",
  "compatibility_date": "2026-02-14",
  "main": "./src/worker-entry.ts",
  "kv_namespaces": [{ "binding": "SITES_KV", "id": "abc" }],
  "version_metadata": { "binding": "CF_VERSION_METADATA" },
  "analytics_engine_datasets": [
    { "binding": "DECO_METRICS", "dataset": "deco_metrics_lebiscuit" }
  ],
  "observability": {
    "logs": {
      "enabled": true,
      "invocation_logs": true
    }
  }
}
`,
    );

    const r = runCodemod(["--source", tmpDir, "--write"]);
    expect(r.code).toBe(0);

    const result = fs.readFileSync(wranglerPath, "utf8");
    // Master switch present.
    expect(result).toContain('"enabled": true');
    // Both leaves present with rates and persist.
    expect(result).toContain('"head_sampling_rate": 1');
    expect(result).toContain('"head_sampling_rate": 0.1');
    expect(result.match(/"persist": true/g)?.length).toBe(2);
    // No destinations by default.
    expect(result).not.toContain('"destinations"');
    // Original keys preserved.
    expect(result).toContain('"name": "lebiscuit-tanstack"');
    expect(result).toContain('"binding": "DECO_METRICS"');
    // Result must be valid JSONC.
    expect(() => JSON.parse(stripJsoncComments(result))).not.toThrow();
  });

  it("strips stale HyperDX-style destinations from a config that already has them", () => {
    fs.writeFileSync(
      wranglerPath,
      `{
  "name": "lebiscuit-tanstack",
  "main": "./src/worker-entry.ts",
  "observability": {
    "enabled": true,
    "logs": {
      "enabled": true,
      "invocation_logs": true,
      "head_sampling_rate": 1,
      "persist": true,
      "destinations": ["hyperdx-logs"]
    },
    "traces": {
      "enabled": true,
      "head_sampling_rate": 0.1,
      "persist": true,
      "destinations": ["hyperdx-traces"]
    }
  }
}
`,
    );

    const r = runCodemod(["--source", tmpDir, "--write"]);
    expect(r.code).toBe(0);

    const result = fs.readFileSync(wranglerPath, "utf8");
    expect(result).not.toContain("hyperdx-logs");
    expect(result).not.toContain("hyperdx-traces");
    expect(result).not.toContain('"destinations"');
    expect(() => JSON.parse(stripJsoncComments(result))).not.toThrow();
  });

  it("appends a canonical observability block when none exists", () => {
    fs.writeFileSync(
      wranglerPath,
      `{
  "name": "fresh-site",
  "compatibility_date": "2026-02-14",
  "main": "./src/worker-entry.ts"
}
`,
    );

    const r = runCodemod(["--source", tmpDir, "--write"]);
    expect(r.code).toBe(0);

    const result = fs.readFileSync(wranglerPath, "utf8");
    expect(result).toContain('"observability"');
    expect(result).toContain('"enabled": true');
    expect(result).toContain('"head_sampling_rate": 1');
    expect(result).toContain('"head_sampling_rate": 0.1');
    expect(result).not.toContain('"destinations"');
    expect(() => JSON.parse(stripJsoncComments(result))).not.toThrow();
  });

  it("is idempotent: second run is a no-op", () => {
    fs.writeFileSync(
      wranglerPath,
      `{
  "name": "lebiscuit-tanstack",
  "main": "./src/worker-entry.ts",
  "observability": {
    "logs": { "enabled": true }
  }
}
`,
    );

    runCodemod(["--source", tmpDir, "--write"]);
    const after1 = fs.readFileSync(wranglerPath, "utf8");

    const r = runCodemod(["--source", tmpDir, "--write"]);
    expect(r.code).toBe(0);
    expect(r.stdout).toContain("already on the canonical CF observability block");

    const after2 = fs.readFileSync(wranglerPath, "utf8");
    expect(after2).toBe(after1);
  });

  it("dry-run exits 1 and does not modify the file (CI signal)", () => {
    const before = `{
  "name": "site",
  "main": "./src/worker-entry.ts"
}
`;
    fs.writeFileSync(wranglerPath, before);

    const r = runCodemod(["--source", tmpDir]);
    expect(r.code).toBe(1);
    expect(r.stdout).toContain("Dry-run");

    expect(fs.readFileSync(wranglerPath, "utf8")).toBe(before);
  });

  it("respects --destination-logs / --destination-traces / --traces-rate / --persist flags", () => {
    fs.writeFileSync(
      wranglerPath,
      `{
  "name": "site",
  "main": "./src/worker-entry.ts"
}
`,
    );

    runCodemod([
      "--source",
      tmpDir,
      "--destination-logs",
      "my-logs",
      "--destination-traces",
      "my-traces",
      "--traces-rate",
      "0.05",
      "--persist",
      "--write",
    ]);

    const result = fs.readFileSync(wranglerPath, "utf8");
    expect(result).toContain('"destinations": ["my-logs"]');
    expect(result).toContain('"destinations": ["my-traces"]');
    expect(result).toContain('"head_sampling_rate": 0.05');
    // Both blocks set persist:true.
    const persistTrueCount = (result.match(/"persist": true/g) ?? []).length;
    expect(persistTrueCount).toBe(2);
  });
}, 30_000);
