import { describe, expect, test } from "bun:test";
import type { ActivityEvent, ActivitySourceCoverage } from "@overdeck/activity-contract";
import type { ObservabilityReportQuery } from "@overdeck/report-contract";
import type { Incident } from "../incidents/incident-service";
import type { GithubCheckRow } from "../requests/requests-store";
import { buildReliabilityReportSection } from "./reliability-report";

const QUERY: ObservabilityReportQuery = {
  from: "2026-08-16T00:00:00.000Z",
  to: "2026-08-17T00:00:00.000Z",
  timezone: "UTC",
};

function incident(overrides: Partial<Incident> = {}): Incident {
  return {
    id: "incident-1",
    kanboardTaskId: 1,
    title: "Buildbox failed",
    description: "",
    priority: "P1",
    incidentType: "buildbox",
    dispatchBrief: null,
    dispatchBriefProvenance: null,
    state: "resolved",
    active: false,
    createdAt: "2026-08-16T01:00:00.000Z",
    updatedAt: "2026-08-16T03:00:00.000Z",
    resolvedAt: "2026-08-16T03:00:00.000Z",
    dispatch: {
      state: "resolved",
      dispatchId: "dispatch-1",
      cli: null,
      model: null,
      wrapperModel: null,
      reasoningEffort: null,
      account: null,
      requestSha256: null,
      statusRevision: 2,
      startedAt: "2026-08-16T01:00:00.000Z",
      heartbeatAt: null,
      completedAt: "2026-08-16T03:00:00.000Z",
      exitCode: 0,
      failureClass: "host-unreachable",
      resultSummary: null,
    },
    activity: [],
    coverage: { stale: false },
    ...overrides,
  };
}

const SOURCE: ActivitySourceCoverage = {
  id: "reaper",
  label: "Agent reaper",
  category: "service",
  path: "/redacted",
  storage: "recorded state",
  queryBounds: "requested range",
  status: "ok",
  records: 2,
  totalRecords: 2,
  skipped: 0,
};

function failure(id: string, dedupeKey?: string): ActivityEvent {
  return {
    id,
    ts: `2026-08-16T0${id === "event-1" ? "4" : "5"}:00:00.000Z`,
    category: "service",
    source: "reaper",
    actor: "system",
    severity: "error",
    title: "Recorded failure prose must not become identity",
    result: "failure",
    project: "overdeck",
    host: "debian1",
    runtime: "agent-reaper",
    ...(dedupeKey ? { dedupeKey } : {}),
  };
}

const FAILED_CHECK: GithubCheckRow = {
  repo: "owner/overdeck",
  run_id: 42,
  name: "collector-tests",
  status: "completed",
  conclusion: "failure",
  sha: "abc",
  branch: "main",
  started_at: "2026-08-16T06:00:00.000Z",
  completed_at: "2026-08-16T06:05:00.000Z",
  work_key: null,
  observed_at: "2026-08-16T06:05:01.000Z",
};

describe("buildReliabilityReportSection", () => {
  test("uses explicit incident lifecycle timestamps for recovery and unresolved age", () => {
    const section = buildReliabilityReportSection(QUERY, {
      incidents: [
        incident(),
        incident({ id: "incident-open", title: "Still broken", state: "running", active: true, resolvedAt: null, updatedAt: "2026-08-16T04:00:00.000Z" }),
      ],
      incidentsComplete: true,
      ciChecks: [],
      activityEvents: [],
      activitySources: [],
    });

    expect(section.metrics.map((metric) => [metric.id, metric.value])).toEqual([
      ["incidents", 2],
      ["unresolved-incidents", 1],
      ["resolved-incidents", 1],
      ["recorded-failures", 2],
    ]);
    expect(section.incidents.find((row) => row.id === "incident-1")).toMatchObject({ durationMs: 7_200_000, ageMs: null });
    expect(section.incidents.find((row) => row.id === "incident-open")).toMatchObject({ durationMs: null, ageMs: 82_800_000 });
    expect(section.resolvedDurationSeries).toEqual([{ ts: "2026-08-16T03:00:00.000Z", value: 7_200_000, incidentId: "incident-1" }]);
  });

  test("groups recurrence only by stable recorded keys and leaves isolated errors separate", () => {
    const section = buildReliabilityReportSection(QUERY, {
      incidents: [],
      incidentsComplete: true,
      ciChecks: [FAILED_CHECK, { ...FAILED_CHECK, run_id: 43 }],
      activityEvents: [failure("event-1", "reaper-kill"), failure("event-2", "reaper-kill"), failure("event-3")],
      activitySources: [SOURCE],
    });

    expect(section.failureGroups.find((group) => group.key === "activity:reaper:reaper-kill")).toMatchObject({ count: 2, source: "Agent reaper" });
    expect(section.failureGroups.find((group) => group.key === "activity-record:event-3")).toMatchObject({ count: 1 });
    expect(section.failureGroups.find((group) => group.key.includes("collector-tests"))).toMatchObject({ count: 2 });
    expect(section.failureGroups.some((group) => group.key.includes("Recorded failure prose"))).toBe(false);
  });

  test("does not infer recovery from the absence of a later failure", () => {
    const section = buildReliabilityReportSection(QUERY, {
      incidents: [incident({ state: "running", active: true, resolvedAt: null })],
      incidentsComplete: true,
      ciChecks: [],
      activityEvents: [],
      activitySources: [],
    });
    expect(section.resolvedDurationSeries).toEqual([]);
    expect(section.incidents[0]).toMatchObject({ durationMs: null });
  });

  test("keeps every missing authority visible", () => {
    const section = buildReliabilityReportSection(QUERY, {});
    expect(section.status).toBe("unavailable");
    expect(section.metrics.every((metric) => metric.value === null)).toBe(true);
    expect(section.gaps).toHaveLength(3);
  });
});
