import type { Locator, Page } from "@playwright/test";

import { expect, fakeServerState, gotoWorkbench, saveScreenshot, test } from "../fixtures/test";
import { selectors, sessionTab } from "../fixtures/selectors";

type TracePagePayload = {
  events?: Array<{ seq?: number; projectedSeq?: number; terminal?: boolean; status?: string; label?: string }>;
  eventCount?: number;
  hasMore?: boolean;
  nextProjectedSeq?: number | null;
  nextCursor?: string | null;
  range?: Record<string, unknown>;
  [key: string]: unknown;
};

test.describe("Workbench golden e2e chain", () => {
  test.use({ scenarioId: "live-rest-backfill-without-terminal-sse" });

  test("new session reaches sealed final response, survives reload and archives by authority", async ({ page }, testInfo) => {
    await gotoWorkbench(page);

    await page.locator(selectors.sessionCreate).click();
    const activeTab = page.locator('.session-tab[data-active="true"]').first();
    await expect(activeTab).toHaveAttribute("data-session-id", /^ses_/u);
    const sessionId = await activeTab.getAttribute("data-session-id");
    expect(sessionId).toBeTruthy();
    if (!sessionId) throw new Error("expected active session id");

    await page.locator(selectors.commandInput).fill("hi");
    await expect(page.locator(selectors.commandSend)).toBeEnabled();
    await page.locator(selectors.commandSend).click();

    const agentCard = page.locator(`${selectors.messageCard}[data-role="agent"]`).last();
    await expect(page.locator(sessionTab(sessionId))).toHaveAttribute("data-running", "true");
    await expect(agentCard).toHaveAttribute("data-status", "running");
    await expect(agentCard.locator(selectors.traceTimeline)).toHaveAttribute("data-status", "running");
    await saveScreenshot(page, testInfo, "golden-running");

    await expect(agentCard).toHaveAttribute("data-status", "completed", { timeout: 12_000 });
    await expect(agentCard).toContainText("fake AgentRun completed after REST backfill without terminal SSE.");
    await expect(page.locator(sessionTab(sessionId))).toHaveAttribute("data-running", "false");

    const trace = agentCard.locator(selectors.traceTimeline);
    await expect(trace).toHaveAttribute("data-status", "completed");
    await openTrace(trace);
    await expect(trace.locator(`${selectors.traceRow}[data-terminal="true"]`)).toBeVisible();

    const tracePage = await fetchTracePage(page, "trc_live_backfill", "afterProjectedSeq=4&limit=10");
    expect(projectedSeqs(tracePage)).toEqual([5, 6, 7]);
    expect(tracePage.range).toMatchObject({ afterProjectedSeq: 4, fromProjectedSeq: 5, toProjectedSeq: 7, returned: 3, total: 7 });
    expect(tracePage.nextProjectedSeq).toBe(7);
    expectNoLegacyTraceCursorFields(tracePage);
    expect(tracePage.events?.at(-1)?.terminal).toBe(true);
    await saveScreenshot(page, testInfo, "golden-completed");

    await page.reload();
    await expect(page.locator(sessionTab(sessionId))).toHaveAttribute("data-active", "true");
    await expect(page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`).last()).toContainText("fake AgentRun completed after REST backfill without terminal SSE.");
    await saveScreenshot(page, testInfo, "golden-reloaded");

    await page.locator(sessionTab("ses_completed")).click();
    await expect(page.locator(sessionTab("ses_completed"))).toHaveAttribute("data-active", "true");
    await page.locator(sessionTab(sessionId)).click();
    await expect(page.locator(sessionTab(sessionId))).toHaveAttribute("data-active", "true");
    await expect(page.locator(`${selectors.messageCard}[data-role="agent"][data-status="completed"]`).last()).toContainText("fake AgentRun completed after REST backfill without terminal SSE.");

    await page.locator(selectors.sessionDelete).click();
    await expect(page.locator(sessionTab(sessionId))).toHaveCount(0);
    const detail = await page.request.get(`/v1/workbench/sessions/${encodeURIComponent(sessionId)}`);
    expect(detail.status()).toBe(200);
    const detailBody = await detail.json() as { session?: { status?: string } };
    expect(detailBody.session?.status).toBe("archived");

    const state = await fakeServerState(page) as { legacyRequestLedger?: unknown[]; chatRequests?: unknown[] };
    expect(state.legacyRequestLedger).toEqual([]);
    expect(state.chatRequests).toHaveLength(1);
  });
});

test.describe("long trace projected pagination", () => {
  test.use({ scenarioId: "scroll-follow-long-trace" });

  test("long trace fixture pages only by projected cursor", async ({ page }, testInfo) => {
    await gotoWorkbench(page, "/workbench/sessions/ses_scroll_follow");

    const firstPage = await fetchTracePage(page, "trc_scroll_follow", "afterProjectedSeq=0&limit=25");
    expect(projectedSeqs(firstPage)).toEqual(Array.from({ length: 25 }, (_, index) => index + 1));
    expect(firstPage.range).toMatchObject({ afterProjectedSeq: 0, fromProjectedSeq: 1, toProjectedSeq: 25, returned: 25, total: 90 });
    expect(firstPage.nextProjectedSeq).toBe(25);
    expect(firstPage.nextCursor).toBe("projected:25");
    expectNoLegacyTraceCursorFields(firstPage);

    const laterPage = await fetchTracePage(page, "trc_scroll_follow", "cursor=projected:70&limit=10");
    expect(projectedSeqs(laterPage)).toEqual(Array.from({ length: 10 }, (_, index) => index + 71));
    expect(laterPage.range).toMatchObject({ afterProjectedSeq: 70, fromProjectedSeq: 71, toProjectedSeq: 80, returned: 10, total: 90 });
    expect(laterPage.nextProjectedSeq).toBe(80);
    expect(laterPage.nextCursor).toBe("projected:80");
    expectNoLegacyTraceCursorFields(laterPage);

    await expect(page.locator(`${selectors.traceTimeline}[data-status="running"]`).last()).toBeVisible();
    await saveScreenshot(page, testInfo, "long-trace-projected-pagination");
  });
});

async function fetchTracePage(page: Page, traceId: string, query: string): Promise<TracePagePayload> {
  const response = await page.request.get(`/v1/workbench/traces/${encodeURIComponent(traceId)}/events?${query}`);
  expect(response.status()).toBe(200);
  return await response.json() as TracePagePayload;
}

function projectedSeqs(payload: TracePagePayload): number[] {
  return (payload.events ?? []).map((event) => Number(event.projectedSeq ?? event.seq));
}

function expectNoLegacyTraceCursorFields(payload: TracePagePayload): void {
  expect(payload).not.toHaveProperty("nextSeq");
  expect(payload).not.toHaveProperty("nextSinceSeq");
  expect(payload).not.toHaveProperty("sinceSeq");
  expect(payload.range).not.toHaveProperty("afterSeq");
  expect(payload.range).not.toHaveProperty("fromSeq");
  expect(payload.range).not.toHaveProperty("toSeq");
  expect(payload.range).not.toHaveProperty("sinceSeq");
}

async function openTrace(trace: Locator): Promise<void> {
  const details = trace.locator("details.trace-disclosure");
  const open = await details.evaluate((element) => (element as HTMLDetailsElement).open).catch(() => false);
  if (!open) await trace.locator("summary.trace-disclosure-summary").click();
}
