import { describe, expect, test } from "bun:test";
import type { Run, RunStatus as ApiRunStatus } from "@qltysh/fabro-api-client";
import {
  columnForStatus,
  columnStatusDisplay,
  isRunStatus,
  mapRunListItem,
  mapRunToRunItem,
  runStatusDisplay,
} from "./runs";
import { TEST_PRINCIPAL } from "../lib/test-fixtures";

function makeRun(overrides: Partial<Run> = {}): Run {
  return {
    id:               "01ABC",
    goal:             "Fix the build",
    title:            "Fix the build",
    workflow:         { slug: "fix_build", name: "Fix Build", graph_name: "FixBuild", node_count: 0, edge_count: 0 },
    automation:       null,
    repository:       { name: "myrepo", origin_url: null, provider: "unknown" },
    created_by:       TEST_PRINCIPAL,
    origin:           { kind: "api" },
    labels:           {},
    lifecycle:        {
      status:          { kind: "running" },
      approval:        null,
      pending_control: null,
      queue_position:  null,
      error:           null,
      archived:        false,
      archived_at:     null,
    },
    sandbox:          null,
    models:           [],
    source_directory: "/home/user/myrepo",
    timestamps:       {
      created_at:     "2026-04-08T12:00:00Z",
      started_at:     "2026-04-08T12:00:00Z",
      last_event_at:  null,
      completed_at:   null,
    },
    timing:           {
      wall_time_ms:      65000,
      inference_time_ms: 0,
      tool_time_ms:      0,
      active_time_ms:    0,
    },
    billing:          { total_usd_micros: 500000 },
    size:             "XS",
    diff:             null,
    pull_request:     null,
    current_question: null,
    superseded_by:    null,
    retried_from:     null,
    links:            { web: null },
    ...overrides,
  };
}

function withStatus(status: ApiRunStatus): Pick<Run, "lifecycle"> {
  return {
    lifecycle: {
      status,
      approval: null,
      pending_control: null,
      queue_position:  null,
      error:           null,
      archived:        false,
      archived_at:     null,
    },
  };
}

describe("mapRunListItem", () => {
  test("trusts shared server fields for board items", () => {
    const summary = makeRun({
      title:         "Server supplied title",
      ...withStatus({ kind: "paused", prior_block: null }),
      pull_request: {
        owner: "fabro-sh",
        repo: "fabro",
        number: 123,
        html_url: "https://github.com/fabro-sh/fabro/pull/123",
      },
    });
    summary.lifecycle.pending_control = "cancel";
    const item = mapRunListItem(summary);
    expect(item.id).toBe("01ABC");
    expect(item.title).toBe("Server supplied title");
    expect(item.workflow).toBe("Fix Build");
    expect(item.repo).toBe("myrepo");
    expect(item.sourceDirectory).toBe("/home/user/myrepo");
    expect(item.elapsed).toBeDefined();
    expect(item.column).toBe("running");
    expect(item.lifecycleStatus).toBe("paused");
    expect(item.number).toBe(123);
    expect(item.pullRequestUrl).toBe("https://github.com/fabro-sh/fabro/pull/123");
    expect(item.pendingControl).toBe("cancel");
  });

  test("uses a fallback title when the server title is blank", () => {
    const summary = makeRun({ id: "01EMPTY", goal: "", title: "" });

    expect(mapRunListItem(summary).title).toBe("Untitled run");
  });

  test("carries the billed total so the size chip can show it on hover", () => {
    expect(mapRunListItem(makeRun()).totalUsdMicros).toBe(500000);
  });

  test("leaves the billed total undefined for runs without terminal billing", () => {
    expect(mapRunListItem(makeRun({ billing: null })).totalUsdMicros).toBeUndefined();
    expect(
      mapRunListItem(makeRun({ billing: { total_usd_micros: null } })).totalUsdMicros,
    ).toBeUndefined();
  });
});

describe("mapRunToRunItem", () => {
  test("maps canonical run summary to RunItem", () => {
    const summary = makeRun({
      pull_request: {
        owner: "fabro-sh",
        repo: "fabro",
        number: 456,
        html_url: "https://github.com/fabro-sh/fabro/pull/456",
      },
    });
    const item = mapRunToRunItem(summary);
    expect(item.id).toBe("01ABC");
    expect(item.title).toBe("Fix the build");
    expect(item.workflow).toBe("Fix Build");
    expect(item.repo).toBe("myrepo");
    expect(item.sourceDirectory).toBe("/home/user/myrepo");
    expect(item.elapsed).toBeDefined();
    expect(item.lifecycleStatus).toBe("running");
    expect(item.number).toBe(456);
    expect(item.pullRequestUrl).toBe("https://github.com/fabro-sh/fabro/pull/456");
  });

  test("handles missing optional fields", () => {
    const summary = makeRun({
      id:               "01DEF",
      goal:             "",
      title:            "",
      workflow:         { slug: null, name: null, graph_name: null, node_count: 0, edge_count: 0 },
      source_directory: null,
      repository:       { name: "unknown", origin_url: null, provider: "unknown" },
      ...withStatus({ kind: "submitted" }),
      timestamps:       {
        created_at:     "2026-04-08T12:00:00Z",
        started_at:     null,
        last_event_at:  null,
        completed_at:   null,
      },
      timing:           null,
      billing:          null,
    });
    const item = mapRunToRunItem(summary);
    expect(item.id).toBe("01DEF");
    expect(item.title).toBe("Untitled run");
    expect(item.workflow).toBe("unknown");
    expect(item.repo).toBe("unknown");
    expect(item.sourceDirectory).toBeUndefined();
  });

  test("falls back to graph name and slug for workflow labels", () => {
    const graphFallback = mapRunToRunItem(
       makeRun({ workflow: { slug: "fix_build", name: null, graph_name: "FixBuild", node_count: 0, edge_count: 0 } }),
    );
    const slugFallback = mapRunToRunItem(
       makeRun({ workflow: { slug: "fix_build", name: null, graph_name: null, node_count: 0, edge_count: 0 } }),
    );

    expect(graphFallback.workflow).toBe("FixBuild");
    expect(slugFallback.workflow).toBe("fix_build");
  });

  test("recognizes canonical blocked, pending, and runnable run statuses", () => {
    expect(isRunStatus("pending")).toBe(true);
    expect(isRunStatus("runnable")).toBe(true);
    expect(isRunStatus("blocked")).toBe(true);
    expect(runStatusDisplay).toHaveProperty("pending");
    expect(runStatusDisplay).toHaveProperty("runnable");
    expect(runStatusDisplay).toHaveProperty("blocked");
  });

  test("recognizes archived as a terminal run status", () => {
    expect(isRunStatus("archived")).toBe(true);
    expect(runStatusDisplay).toHaveProperty("archived");
  });

  test("uses blocked board column instead of waiting", () => {
    expect(columnStatusDisplay).toHaveProperty("blocked");
    expect(columnStatusDisplay).not.toHaveProperty("waiting");
  });
});

describe("columnForStatus", () => {
  test("returns null for lifecycle states that do not map to a board column", () => {
    expect(columnForStatus("removing")).toBeNull();
  });
});
