import { describe, expect, it } from "vitest";
import { renderRequest } from "../../apps/web/src/app.js";
import { commandAcknowledgement } from "../../apps/web/src/interaction.js";
import { decideRealtimeUpdate } from "../../apps/web/src/realtime.js";
import { renderFailureFacts } from "../../apps/web/src/render-factory.js";

describe("FIRE projection honesty", () => {
  it("FIRE-D-RT023 marks a realtime sequence gap stale and requires authoritative refetch", () => {
    expect(decideRealtimeUpdate({ lastSequence: 100, observedSequence: 103 })).toEqual({
      kind: "stale-refetch",
      lastSequence: 100,
      observedSequence: 103,
      reason: "gap",
    });
    expect(
      decideRealtimeUpdate({ lastSequence: 103, observedSequence: 104, reconnected: true }),
    ).toMatchObject({
      kind: "stale-refetch",
      reason: "reconnect",
    });
  });

  it("FIRE-D-RT025 never promotes lifecycle from URL or client command acknowledgement", () => {
    const review = renderRequest("/changesets/CS-001?state=merged");
    expect(review).toContain("Changes requested");
    expect(review).not.toContain("Merged / completed");
    expect(commandAcknowledgement("merge")).toContain("only after authoritative reconciliation");
  });

  it("FIRE-D-RT026 preserves rich failure class/cause/retry/source and renders missing fields explicitly", () => {
    const recovery = renderRequest("/factory-runs/FR-001?fixture=recovery", { fixtureMode: true });
    expect(recovery).toContain("infrastructure");
    expect(recovery).toContain("workspace_heartbeat_expired");
    expect(recovery).toContain("retryable-new-attempt");
    expect(recovery).toContain("checkpoint:a7f3c9e");
    expect(renderFailureFacts({})).toContain("Unknown / not provided");
  });

  it("FIRE-D-RT021 keeps historical failure evidence visible after executor disappearance", () => {
    const history = renderRequest("/factory-runs/FR-001?fixture=history-no-executor", {
      fixtureMode: true,
    });
    expect(history).toContain("Historical durable record");
    expect(history).toContain("Attempt 1 failed — work preserved");
    expect(history).toContain("a7f3c9e");
  });

  it("FIRE-D-U3 exposes preserved-work truth before retry", () => {
    const recovery = renderRequest("/factory-runs/FR-001?fixture=recovery", { fixtureMode: true });
    expect(recovery).toContain("WIP is safely persisted");
    expect(recovery).toContain("14 modified · source + test state");
    expect(recovery).toContain("Attempt 1 remains immutable history");
    expect(recovery).toContain("Retry as new Attempt");
  });

  it("FIRE-D-STATE distinguishes running, waiting and failure/recovery", () => {
    const running = renderRequest("/factory-runs/FR-001", { fixtureMode: true });
    const waiting = renderRequest("/factory-runs/FR-001?fixture=waiting", { fixtureMode: true });
    const failed = renderRequest("/factory-runs/FR-001?fixture=recovery", { fixtureMode: true });
    expect(running).toContain("◉ Running");
    expect(waiting).toContain("FactoryRun is waiting — not failed, not complete");
    expect(failed).toContain("● Failure / recovery");
    expect(waiting).not.toContain("Merged / completed");
    expect(failed).not.toContain("Merged / completed");
  });

  it("FIRE-D-STALE labels stale state and disables protected retry", () => {
    const stale = renderRequest("/factory-runs/FR-001?fixture=recovery-stale", {
      fixtureMode: true,
    });
    expect(stale).toContain("Stale / disconnected");
    expect(stale).toMatch(/data-command="retry-run" disabled aria-disabled="true"/u);
  });
});
