import { describe, test, expect } from "bun:test";
import { sanitizeLogLineForApi } from "@/utils/log-line.js";

describe("sanitizeLogLineForApi", () => {
  test("plain text passes through unchanged", () => {
    expect(sanitizeLogLineForApi("hello world")).toBe("hello world");
  });

  test("strips ANSI escape sequences", () => {
    expect(sanitizeLogLineForApi("\u001b[31mred\u001b[0m text")).toBe("red text");
  });

  test("returns empty string for empty input", () => {
    expect(sanitizeLogLineForApi("")).toBe("");
  });

  test("returns empty string for whitespace-only input", () => {
    expect(sanitizeLogLineForApi("   ")).toBe("");
  });

  test("preserves Unicode characters", () => {
    expect(sanitizeLogLineForApi("日本語 café résumé")).toBe("日本語 café résumé");
  });

  test("truncates lines exceeding 64 KB with trailing ellipsis", () => {
    const longLine = "x".repeat(65537);
    const result = sanitizeLogLineForApi(longLine);
    // 64 * 1024 = 65536 chars + 1 ellipsis char
    expect(result.length).toBe(65537);
    expect(result.endsWith("…")).toBe(true);
    expect(result.startsWith("x")).toBe(true);
  });

  test("does not truncate a line exactly at the 64 KB limit", () => {
    const exactLine = "y".repeat(65536);
    const result = sanitizeLogLineForApi(exactLine);
    expect(result.length).toBe(65536);
    expect(result.endsWith("…")).toBe(false);
  });
});

describe("sanitizeLogLineForApi — Pino JSON extraction", () => {
  test("extracts msg from a Pino JSON line", () => {
    const pino = JSON.stringify({ level: 30, time: 1234567890, msg: "hello from pino" });
    expect(sanitizeLogLineForApi(pino)).toBe("hello from pino");
  });

  test("includes context fields (issueKey, jobKey, repo, branch) as prefix", () => {
    const pino = JSON.stringify({
      level: 30,
      time: 1234567890,
      msg: "hello",
      issueKey: "PROJ-123",
      jobKey: "fix",
      repo: "my-repo",
      branch: "main",
    });
    expect(sanitizeLogLineForApi(pino)).toBe("PROJ-123 · fix · my-repo · main — hello");
  });

  test("appends detail fields (step, remote, status) after msg", () => {
    const pino = JSON.stringify({
      level: 30,
      time: 1234567890,
      msg: "step done",
      step: "build",
      remote: "https://user:token@github.com/org/repo.git",
      status: 200,
    });
    expect(sanitizeLogLineForApi(pino)).toBe(
      "step done | step=build | remote=https://github.com/org/repo.git | status=200",
    );
  });

  test("redacts GitHub secrets in error field", () => {
    const pino = JSON.stringify({
      level: 50,
      time: 1234567890,
      msg: "auth fail",
      error: "token github_pat_abc123def456_more",
    });
    expect(sanitizeLogLineForApi(pino)).toBe("auth fail | error=token github_pat_<redacted>");
  });

  test("redacts x-access-token URLs in error field", () => {
    const pino = JSON.stringify({
      level: 50,
      time: 1234567890,
      msg: "clone fail",
      error: "https://x-access-token:ghs_abc123@github.com/org/repo.git",
    });
    expect(sanitizeLogLineForApi(pino)).toBe("clone fail | error=https://github.com/org/repo.git");
  });

  test("truncates error field at 500 chars with ellipsis", () => {
    const longError = "e".repeat(501);
    const pino = JSON.stringify({
      level: 50,
      time: 1234567890,
      msg: "failed",
      error: longError,
    });
    const result = sanitizeLogLineForApi(pino);
    expect(result).toBe(`failed | error=${"e".repeat(500)}…`);
  });

  test("includes cause field alongside error field", () => {
    const pino = JSON.stringify({
      level: 50,
      time: 1234567890,
      msg: "failed",
      cause: "connection reset",
    });
    expect(sanitizeLogLineForApi(pino)).toBe("failed | cause=connection reset");
  });

  test("ignores non-string err object (only msg is extracted)", () => {
    const pino = JSON.stringify({
      level: 50,
      time: 1234567890,
      msg: "boom",
      err: { message: "nested error" },
    });
    expect(sanitizeLogLineForApi(pino)).toBe("boom");
  });

  test("passes through invalid JSON starting with {", () => {
    expect(sanitizeLogLineForApi("{not valid json")).toBe("{not valid json");
  });
});
