import { describe, expect, test } from "bun:test";

import {
  deepLinkToastMessage,
  emptyTransitionToastMessage,
  extractRequestId,
  normalizeRunFileScope,
} from "./run-files";
import {
  buildRunCommitOptions,
  fabroGeneratedCommitStage,
} from "./run-files/commit-options";

function buildRunFilesPayload({
  files = [],
  degraded = false,
}: {
  files?: string[];
  degraded?: boolean;
}) {
  return {
    data: files.map((name, index) => ({
      change_kind: "modified",
      old_file: { name, contents: degraded ? null : `old ${index}` },
      new_file: { name, contents: degraded ? null : `new ${index}` },
      ...(degraded ? { unified_patch: `diff --git a/${name} b/${name}` } : {}),
    })),
    meta: {
      degraded,
      total_changed: files.length,
      source: "sandbox",
      scope: "committed",
      stats: { additions: 0, deletions: 0 },
      truncated: false,
    },
  } as any;
}

describe("extractRequestId", () => {
  test("reads `request_id` from the top level of the error body", () => {
    expect(extractRequestId({ request_id: "abc-123" })).toBe("abc-123");
  });

  test("reads `request_id` from errors[0] under the uniform envelope", () => {
    expect(
      extractRequestId({
        errors: [
          { status: "500", title: "Internal", request_id: "evt_42" },
        ],
      }),
    ).toBe("evt_42");
  });

  test("parses `Request ID: xyz` out of errors[0].detail", () => {
    expect(
      extractRequestId({
        errors: [
          {
            status: "500",
            title:  "Internal Server Error",
            detail: "Run files failed. Request ID: req_999 on shard 2.",
          },
        ],
      }),
    ).toBe("req_999");
  });

  test("returns null for bodies without any request_id", () => {
    expect(extractRequestId(null)).toBe(null);
    expect(extractRequestId(undefined)).toBe(null);
    expect(extractRequestId("not an object")).toBe(null);
    expect(extractRequestId({ errors: [] })).toBe(null);
    expect(extractRequestId({ errors: [{ detail: "no id in here" }] })).toBe(
      null,
    );
  });

  test("handles request_id values with hyphens and underscores", () => {
    expect(
      extractRequestId({
        errors: [
          { detail: "Something failed. request_id: RX-1A_2B-3C4D" },
        ],
      }),
    ).toBe("RX-1A_2B-3C4D");
  });
});

describe("emptyTransitionToastMessage", () => {
  test("returns the no-changes toast when a populated diff becomes empty", () => {
    expect(emptyTransitionToastMessage(3, 0)).toBe("No changes in this run.");
  });

  test("returns null when the diff was already empty", () => {
    expect(emptyTransitionToastMessage(0, 0)).toBeNull();
    expect(emptyTransitionToastMessage(null, 0)).toBeNull();
    expect(emptyTransitionToastMessage(2, 1)).toBeNull();
  });
});

describe("normalizeRunFileScope", () => {
  test("defaults missing or invalid values to committed", () => {
    expect(normalizeRunFileScope(null)).toBe("committed");
    expect(normalizeRunFileScope("dirty")).toBe("committed");
  });

  test("accepts supported scope values", () => {
    expect(normalizeRunFileScope("committed")).toBe("committed");
    expect(normalizeRunFileScope("uncommitted")).toBe("uncommitted");
    expect(normalizeRunFileScope("all")).toBe("all");
  });
});

describe("buildRunCommitOptions", () => {
  test("shortens Fabro-generated subjects to stage visits", () => {
    const commits = [
      {
        sha:       "a".repeat(40),
        short_sha: "aaaaaaa",
        subject:   "fabro(run_1): implement (succeeded)",
        parents:   [{ sha: "1".repeat(40), short_sha: "1111111" }],
      },
      {
        sha:       "b".repeat(40),
        short_sha: "bbbbbbb",
        subject:   "fabro(run_1): implement (succeeded)",
        parents:   [{ sha: "a".repeat(40), short_sha: "aaaaaaa" }],
      },
    ];

    expect(buildRunCommitOptions(commits).map((commit) => commit.label)).toEqual([
      "implement@1",
      "implement@2",
    ]);
  });

  test("leaves externally generated commit subjects intact", () => {
    const [option] = buildRunCommitOptions([
      {
        sha:       "a".repeat(40),
        short_sha: "aaaaaaa",
        subject:   "Fix README typo",
        parents:   [{ sha: "1".repeat(40), short_sha: "1111111" }],
      },
    ]);

    expect(fabroGeneratedCommitStage("Fix README typo")).toBeNull();
    expect(option.label).toBe("Fix README typo");
  });
});

describe("deepLinkToastMessage", () => {
  test("returns the missing-file message when the requested file is absent", () => {
    expect(
      deepLinkToastMessage(
        "src/missing.ts",
        buildRunFilesPayload({ files: ["src/present.ts"] }),
      ),
    ).toBe("File src/missing.ts is not in this run.");
  });

  test("returns null when the deep-linked file exists", () => {
    expect(
      deepLinkToastMessage(
        "src/present.ts",
        buildRunFilesPayload({ files: ["src/present.ts"] }),
      ),
    ).toBeNull();
  });

  test("returns null for degraded file-shaped payloads when the file exists", () => {
    expect(
      deepLinkToastMessage(
        "src/present.ts",
        buildRunFilesPayload({
          degraded: true,
          files: ["src/present.ts"],
        }),
      ),
    ).toBeNull();
  });
});
