// @vitest-environment jsdom

import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import HistoryPage from "./page";
import { mockHistoryApi } from "./test-helpers";

function makePR(overrides: Partial<{ number: number; title: string; createdAt: string }> = {}) {
  return {
    number: overrides.number ?? 1,
    title: overrides.title ?? `release(acs-bump): bump ${overrides.number ?? 1}`,
    author: "delivery-user",
    state: "OPEN" as const,
    url: `https://github.com/example/repo/pull/${overrides.number ?? 1}`,
    createdAt: overrides.createdAt ?? "2026-01-01T00:00:00.000Z",
    mergedAt: null,
    regions: [`r-dev-${overrides.number ?? 1}`],
  };
}

describe("HistoryPage", () => {
  beforeEach(() => {
    cleanup();
    vi.restoreAllMocks();

    mockHistoryApi({
      ok: true,
      data: {
        prs: [
          {
            number: 123,
            title: "release(acs-bump): prod nextgen abc123",
            author: "delivery-user",
            state: "OPEN",
            url: "https://github.com/example/repo/pull/123",
            createdAt: "2026-01-01T00:00:00.000Z",
            mergedAt: null,
            regions: ["r-dev-1", "r-dev-2"],
          },
          {
            number: 124,
            title: "release(acs-bump): old PR without region metadata",
            author: "delivery-user",
            state: "MERGED",
            url: "https://github.com/example/repo/pull/124",
            createdAt: "2026-01-02T00:00:00.000Z",
            mergedAt: "2026-01-02T01:00:00.000Z",
          },
        ],
      },
    });
  });

  it("links history rows with parsed regions to observe", async () => {
    render(<HistoryPage />);

    const observeLink = await screen.findByRole("link", { name: "Observe" });
    const agentsLink = screen.getByRole("link", { name: "Agents" });

    expect(observeLink).toHaveAttribute(
      "href",
      `/observe?regions=${encodeURIComponent("r-dev-1,r-dev-2")}&component=acs`
    );
    expect(agentsLink).toHaveAttribute(
      "href",
      `/agents?regions=${encodeURIComponent("r-dev-1,r-dev-2")}`
    );
    expect(screen.getByRole("link", { name: "#123" })).toHaveAttribute(
      "href",
      "https://github.com/example/repo/pull/123"
    );
    expect(screen.getByRole("link", { name: "#124" })).toBeInTheDocument();
    expect(screen.getAllByText("-").length).toBeGreaterThanOrEqual(1);
  });

  it("renders title after created in the table columns", async () => {
    const { container } = render(<HistoryPage />);

    await screen.findByRole("link", { name: "#123" });
    const table = container.querySelector("table");

    expect(screen.getAllByRole("columnheader").map((header) => header.textContent)).toEqual([
      "PR",
      "Author",
      "State",
      "Created",
      "Title",
      "Actions",
    ]);
    expect(table).not.toHaveClass("table-fixed");
    expect(container.querySelector("colgroup")).not.toBeInTheDocument();
    expect(screen.getAllByRole("columnheader")).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          className: expect.stringContaining("px-5 py-2.5"),
        }),
      ])
    );
    expect(screen.getByRole("columnheader", { name: "PR" })).toHaveClass("px-5", "py-2.5");
    expect(screen.getByRole("columnheader", { name: "Author" })).toHaveClass(
      "px-5",
      "py-2.5"
    );
    expect(screen.getByRole("columnheader", { name: "State" })).toHaveClass(
      "px-5",
      "py-2.5"
    );
    expect(screen.getByRole("columnheader", { name: "Created" })).toHaveClass(
      "pl-5",
      "pr-1",
      "py-2.5"
    );
    expect(screen.getByRole("columnheader", { name: "Title" })).toHaveClass(
      "pl-0.5",
      "pr-4",
      "py-2.5"
    );
    expect(screen.getByRole("columnheader", { name: "Actions" })).toHaveClass(
      "pl-2",
      "pr-4",
      "py-2.5"
    );
    expect(screen.getByRole("columnheader", { name: "Created" })).not.toHaveClass("pr-4");
    expect(screen.getByRole("columnheader", { name: "Title" })).not.toHaveClass("pl-2");
    expect(screen.getByRole("columnheader", { name: "Actions" })).not.toHaveClass("pl-4");
  });

  it("renders visually consistent table rows", async () => {
    render(<HistoryPage />);

    const prLink = await screen.findByRole("link", { name: "#123" });
    const row = prLink.closest("tr");
    const table = prLink.closest("table");
    const observeLink = screen.getByRole("link", { name: "Observe" });
    const agentsLink = screen.getByRole("link", { name: "Agents" });
    const actionGroup = observeLink.parentElement;
    const createdTime = screen.getByText("2026-01-01 08:00 UTC+8");
    const createdCell = createdTime.closest("td");
    const titleCell = screen.getByText("release(acs-bump): prod nextgen abc123").closest("td");
    const openBadge = screen.getByText("OPEN");

    expect(table).toHaveClass("text-sm");
    expect(table).not.toHaveClass("text-xs");
    expect(row).toHaveClass("hover:bg-surface-1");
    expect(prLink.closest("td")).toHaveClass("px-5", "py-2.5");
    expect(prLink).toHaveClass("font-mono", "text-[13px]", "text-accent");
    expect(screen.getAllByText("delivery-user")[0].closest("td")).toHaveClass(
      "px-5",
      "py-2.5"
    );
    expect(openBadge.closest("td")).toHaveClass("px-5", "py-2.5");
    expect(createdCell).toHaveClass("pl-5", "pr-1", "py-2.5");
    expect(createdTime.tagName).toBe("TIME");
    expect(createdTime).toHaveAttribute("dateTime", "2026-01-01T00:00:00.000Z");
    expect(createdTime).toHaveAttribute("title", "2026-01-01T00:00:00.000Z");
    expect(createdTime).toHaveClass(
      "font-mono",
      "text-[13px]",
      "bg-surface-1",
      "px-2",
      "py-0.5",
      "rounded",
      "border",
      "border-surface-2/70",
      "text-accent",
      "whitespace-nowrap"
    );
    expect(createdTime).not.toHaveClass("text-text-muted", "text-text-secondary");
    expect(titleCell).toHaveClass(
      "pl-0.5",
      "pr-4",
      "py-2.5",
      "truncate",
      "text-text-secondary"
    );
    expect(titleCell).not.toHaveClass("max-w-md", "pl-2");
    expect(screen.getByText("OPEN")).toHaveClass("text-xs");
    expect(screen.getByText("OPEN")).not.toHaveClass("text-sm");
    expect(openBadge).toHaveClass(
      "inline-flex",
      "items-center",
      "rounded-md",
      "border",
      "px-2",
      "py-0.5",
      "text-xs",
      "font-medium",
      "uppercase",
      "bg-state-open/10",
      "text-state-open",
      "border-state-open/30"
    );
    expect(openBadge).not.toHaveClass("border-l-2", "rounded-r", "text-green-400");
    expect(screen.getByText("MERGED")).toHaveClass(
      "bg-state-merged/10",
      "text-state-merged",
      "border-state-merged/30"
    );
    expect(screen.getByText("MERGED")).not.toHaveClass("text-purple-400");
    expect(actionGroup).toHaveClass(
      "inline-flex",
      "items-center",
      "overflow-hidden",
      "rounded-md",
      "border",
      "border-accent/30"
    );
    expect(observeLink).toHaveClass("h-7", "px-2.5", "text-xs", "font-medium");
    expect(observeLink).toHaveClass("border-r", "border-accent/20");
    expect(observeLink).not.toHaveClass("px-3", "h-8");
    expect(agentsLink).toHaveClass("h-7", "px-2.5", "text-xs", "font-medium");
    expect(agentsLink).not.toHaveClass("px-3", "h-8");
    expect(actionGroup?.closest("td")).toHaveClass("pl-2", "pr-4", "py-2.5");
    expect(actionGroup?.closest("td")).not.toHaveClass("pl-4");
  });

  it("renders created time to the minute in UTC+8", async () => {
    render(<HistoryPage />);

    expect(await screen.findByText("2026-01-01 08:00 UTC+8")).toBeInTheDocument();
    expect(screen.getByText("2026-01-02 08:00 UTC+8")).toBeInTheDocument();
  });

  it("retries by refetching /api/history instead of reloading window", async () => {
    const fetchMock = vi
      .fn()
      .mockResolvedValueOnce(
        new Response(
          JSON.stringify({
            ok: false,
            error: { code: "INTERNAL", message: "history failed" },
          }),
          { status: 500, headers: { "Content-Type": "application/json" } }
        )
      )
      .mockResolvedValueOnce(
        new Response(
          JSON.stringify({
            ok: true,
            data: {
              prs: [
                {
                  number: 125,
                  title: "release(acs-bump): retry",
                  author: "delivery-user",
                  state: "OPEN",
                  url: "https://github.com/example/repo/pull/125",
                  createdAt: "2026-01-03T00:00:00.000Z",
                  mergedAt: null,
                },
              ],
            },
          }),
          { status: 200, headers: { "Content-Type": "application/json" } }
        )
      );
    vi.stubGlobal("fetch", fetchMock);

    render(<HistoryPage />);

    expect(await screen.findByText("history failed")).toBeInTheDocument();

    const retryButton = screen.getByRole("button", { name: "Try again" });
    fireEvent.click(retryButton);

    expect(await screen.findByRole("link", { name: "#125" })).toBeInTheDocument();
    expect(fetchMock.mock.calls.length).toBeGreaterThanOrEqual(2);
    expect(fetchMock.mock.calls.every((args) => args[0] === "/api/history")).toBe(true);
  });

  describe("pagination", () => {
    it("paginates 15 PRs per page", async () => {
      const prs = Array.from({ length: 35 }, (_, i) =>
        makePR({ number: i + 1, createdAt: `2026-01-${String(i + 1).padStart(2, "0")}T00:00:00.000Z` })
      );
      mockHistoryApi({ ok: true, data: { prs } });

      render(<HistoryPage />);
      await screen.findByText("Operation History");

      const rows = screen.getAllByRole("row").filter((r) => r.closest("tbody"));
      expect(rows).toHaveLength(15);
      expect(screen.getByText("1 / 3")).toBeInTheDocument();

      const nextButton = screen.getByRole("button", { name: "Next" });
      expect(nextButton).toBeEnabled();
      expect(screen.getByRole("button", { name: "Prev" })).toBeDisabled();

      fireEvent.click(nextButton);
      expect(screen.getByText("2 / 3")).toBeInTheDocument();
      const page2Rows = screen.getAllByRole("row").filter((r) => r.closest("tbody"));
      expect(page2Rows).toHaveLength(15);

      fireEvent.click(nextButton);
      expect(screen.getByText("3 / 3")).toBeInTheDocument();
      const page3Rows = screen.getAllByRole("row").filter((r) => r.closest("tbody"));
      expect(page3Rows).toHaveLength(5);
      expect(screen.getByRole("button", { name: "Next" })).toBeDisabled();
    });

    it("does not show pagination for a single page", async () => {
      mockHistoryApi({
        ok: true,
        data: {
          prs: [
            makePR({ number: 1 }),
            makePR({ number: 2 }),
          ],
        },
      });

      render(<HistoryPage />);
      await screen.findByText("Operation History");

      expect(screen.queryByText(/\d+ \/ \d+/)).not.toBeInTheDocument();
      expect(screen.queryByRole("button", { name: "Next" })).not.toBeInTheDocument();
    });
  });
});
