import { describe, expect, test } from "bun:test";
import { actionLogFailureExcerpt, isActionLogNotReady, redactAndTailActionLog } from "./gh/actions-runs";
import { benchmarkDataUrl, parseBenchmarkData } from "./gh/actions-benchmark";
import { withGhDefaultRendered } from "./gh/default-render";

describe("GitHub Actions run diagnostics", () => {
  test("parses github-action-benchmark Pages exports without executing JavaScript", () => {
    const parsed = parseBenchmarkData(`window.BENCHMARK_DATA = {
      "lastUpdate": 123,
      "repoUrl": "https://github.com/owner/repo",
      "entries": {
        "Benchmark": [{
          "commit": {"id": "abcdef", "message": "perf: faster", "timestamp": "2026-01-01T00:00:00Z", "url": "https://github.com/owner/repo/commit/abcdef"},
          "date": 123,
          "tool": "googlecpp",
          "benches": [{"name": "loop", "value": 1.25, "unit": "ms/iter", "extra": "iterations: 10"}]
        }]
      }
    };`);
    expect(parsed.entries).toHaveLength(1);
    expect(parsed.entries[0]?.benches[0]?.value).toBe(1.25);
    expect(parsed.suites).toEqual(["Benchmark"]);
  });

  test("resolves a benchmark page to data.js and rejects a mismatched Pages owner", () => {
    expect(benchmarkDataUrl("owner/repo", "https://owner.github.io/repo/dev/bench/"))
      .toBe("https://owner.github.io/repo/dev/bench/data.js");
    expect(() => benchmarkDataUrl("owner/repo", "https://other.github.io/repo/dev/bench/"))
      .toThrow("host must be owner.github.io");
  });

  test("redacts credentials and keeps a bounded tail", () => {
    const lines = Array.from({ length: 200 }, (_, index) => `line-${index}`);
    lines.push("TOKEN=secret-value");
    lines.push("Authorization: Bearer abc.def.ghi");
    lines.push("clone https://user:password@example.com/repo.git failed");

    const tail = redactAndTailActionLog(lines.join("\n"), 5, 1000);

    expect(tail).not.toContain("secret-value");
    expect(tail).not.toContain("abc.def.ghi");
    expect(tail).not.toContain("user:password");
    expect(tail).toContain("<redacted>");
    expect(tail.split("\n")).toHaveLength(5);
  });

  test("applies the character bound after line selection", () => {
    expect(redactAndTailActionLog("a".repeat(100), 10, 12)).toBe("a".repeat(12));
  });

  test("classifies a pending job log without claiming the repository is missing", () => {
    expect(isActionLogNotReady({ status: 404, message: "The specified blob does not exist." })).toBe(true);
    expect(isActionLogNotReady({ status: 404, message: "Not Found" })).toBe(false);
  });

  test("returns the first failure context separately from the terminal tail", () => {
    const log = ["setup", "before", "Expected equality of these values:", "  actual", "  expected", "after", "cleanup"].join("\n");
    const excerpt = actionLogFailureExcerpt(log);
    expect(excerpt).toContain("before");
    expect(excerpt).toContain("Expected equality");
    expect(excerpt).toContain("after");
  });

  test("renders startup failures and bounded logs without hiding evidence", () => {
    const startup = withGhDefaultRendered(["run", "view", "42"], {
      ok: true,
      command: "run view",
      repo: "owner/repo",
      run: { id: 42, workflow: "code scan", status: "completed", conclusion: "failure", commit: "abcdef", title: "push" },
      jobs: [],
      startupFailure: true,
      diagnosis: "workflow failed before any job was created",
      next: [],
    }) as { renderedText: string };
    expect(startup.renderedText).toContain("startupFailure=true");
    expect(startup.renderedText).toContain("workflow failed before any job was created");

    const logs = withGhDefaultRendered(["run", "logs", "42"], {
      ok: true,
      command: "run logs",
      repo: "owner/repo",
      runId: 42,
      job: { id: 7, name: "test", status: "completed", conclusion: "failure" },
      failureExcerpt: "first failed assertion",
      logTail: "compile failed at source.c:10",
      lineLimit: 60,
      charLimit: 5000,
      truncated: true,
    }) as { renderedText: string };
    expect(logs.renderedText).toContain("compile failed at source.c:10");
    expect(logs.renderedText).toContain("first failed assertion");
    expect(logs.renderedText).toContain("valuesPrinted=false");

    const benchmark = withGhDefaultRendered(["run", "benchmark"], {
      ok: true,
      command: "run benchmark",
      repo: "owner/repo",
      source: { dataUrl: "https://owner.github.io/repo/dev/bench/data.js", bytes: 1024, lastUpdate: 123 },
      suites: ["Benchmark"],
      totalEntries: 2,
      matchingEntries: 2,
      returnedEntries: 1,
      entries: [{
        commit: { id: "abcdef", message: "perf: faster" },
        date: 123,
        measurementCount: 2,
      }],
      measurements: [
        { commit: "abcdef", name: "Performance Points", value: 18000, unit: "Point/iter" },
        { commit: "abcdef", name: "loop", value: 1.25, unit: "ms/iter" },
      ],
      next: { commit: "gh run benchmark --commit <sha>" },
    }) as { renderedText: string };
    expect(benchmark.renderedText).toContain("18000.00");
    expect(benchmark.renderedText).toContain("loop");
    expect(benchmark.renderedText).toContain("never the full data.js source");
  });
});
