// @vitest-environment happy-dom
// AckDrawer wires `useAckDetail` (TanStack Query) and renders a Radix
// Dialog portal — both need an act-aware, DOM-backed environment or
// React logs warnings and the portal can't mount.
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;

import { afterEach, describe, it, expect, vi } from "vitest";
import { cleanup, render } from "@testing-library/react";
import { ApiError } from "@/lib/api";
import { AckDrawer } from "@/components/AckDrawer";
import type { Ack } from "@/types";

// Mock the hook BEFORE the import above is resolved (vitest hoists
// `vi.mock` to the top of the file regardless of where it appears
// syntactically). Mocking the hook directly — rather than mocking
// `api.getAck` — lets each test pin the hook's exact return shape
// without standing up a real `QueryClient`.
const { useAckDetail } = vi.hoisted(() => ({
  useAckDetail: vi.fn(),
}));
vi.mock("@/hooks/useAckDetail", () => ({
  useAckDetail,
}));

/**
 * Minimal valid `Ack` fixture — every required key present so the
 * component typechecks. The wire shape extends with `raw_999_text`
 * (and `rawJson`), per `useAckDetail`'s `AckDetail` type — populated
 * when the backend serves it; absent on older rows.
 */
const SAMPLE_ACK: Ack & { raw_999_text: string } = {
  id: 42,
  sourceBatchId: "b-uuid-1",
  acceptedCount: 3,
  rejectedCount: 1,
  receivedCount: 4,
  ackCode: "P",
  parsedAt: "2026-06-20T12:00:00Z",
  raw_999_text: "ISA*...*~\nGS*...*~\nST*999*0001~",
};

/**
 * Configure the mocked hook's return value for a single test. The
 * `refetch` default is a fresh `vi.fn()` — tests that need to assert
 * on it can override via `overrides.refetch`.
 */
function mockDetail(
  overrides: Partial<{
    data: (Ack & { raw_999_text?: string }) | null;
    isLoading: boolean;
    isError: boolean;
    error: Error | null;
    refetch: () => void;
  }> = {}
) {
  useAckDetail.mockReturnValue({
    data: null,
    isLoading: false,
    isError: false,
    error: null,
    refetch: vi.fn(),
    ...overrides,
  });
}

// happy-dom keeps `document.body` between tests; without cleanup,
// `screen.getByText(...)` would find nodes from earlier renders.
afterEach(() => {
  cleanup();
  vi.clearAllMocks();
});

describe("AckDrawer", () => {
  it("test_renders_nothing_when_ackId_is_null", () => {
    mockDetail({ data: null });
    render(<AckDrawer ackId={null} onClose={() => {}} />);

    // No ack content should be in the document when the drawer is
    // closed — Radix's Dialog gates the portal on `open`.
    expect(document.body.textContent).not.toContain("b-uuid-1");
  });

  it("test_calls_useAckDetail_with_ackId", () => {
    mockDetail({ data: SAMPLE_ACK });
    render(<AckDrawer ackId="42" onClose={() => {}} />);

    expect(useAckDetail).toHaveBeenCalledWith("42");
  });

  it("test_renders_ack_summary_on_success", () => {
    mockDetail({ data: SAMPLE_ACK });
    render(<AckDrawer ackId="42" onClose={() => {}} />);

    // Header shows the source batch id as the title.
    expect(document.body.textContent).toContain("b-uuid-1");
    // Counts (3 accepted, 1 rejected, 4 received) are surfaced as
    // StatTile values.
    expect(document.body.textContent).toContain("3");
    expect(document.body.textContent).toContain("1");
    expect(document.body.textContent).toContain("4");
  });

  it("test_renders_ack_code_pill_with_human_label", () => {
    mockDetail({ data: { ...SAMPLE_ACK, ackCode: "P" } });
    render(<AckDrawer ackId="42" onClose={() => {}} />);

    // The pill renders a human label, not just the bare code letter.
    expect(document.body.textContent).toContain("Partially accepted");
  });

  it("test_renders_skeleton_while_loading", () => {
    mockDetail({ isLoading: true });
    render(<AckDrawer ackId="42" onClose={() => {}} />);

    // The `Skeleton` primitive sets `aria-busy="true"` — a stable
    // hook for the loading state.
    expect(document.querySelectorAll('[aria-busy="true"]').length).toBeGreaterThan(0);
    // And the ack id should NOT have leaked in yet.
    expect(document.body.textContent).not.toContain("b-uuid-1");
  });

  it("test_renders_not_found_error_on_404", () => {
    mockDetail({
      isError: true,
      error: new ApiError(404, "Ack ghost not found"),
    });
    render(<AckDrawer ackId="9999" onClose={() => {}} />);

    const errEl = document.querySelector('[data-testid="ack-drawer-error-not_found"]');
    expect(errEl).not.toBeNull();
    // Body should mention "doesn't exist" — the not_found COPY key.
    expect(errEl?.textContent).toContain("doesn't exist");
    // And the retry button should NOT be present (not_found has no
    // retry affordance — retrying a 404 won't help).
    expect(document.querySelector('[data-testid="error-retry"]')).toBeNull();
  });

  it("test_renders_network_error_with_retry", () => {
    mockDetail({ isError: true, error: new Error("network down") });
    render(<AckDrawer ackId="42" onClose={() => {}} />);

    const errEl = document.querySelector('[data-testid="ack-drawer-error-network"]');
    expect(errEl).not.toBeNull();
    expect(errEl?.textContent).toContain("Couldn't reach the server");

    // Network variant shows a Retry button.
    const retryBtn = document.querySelector(
      '[data-testid="error-retry"]'
    ) as HTMLButtonElement | null;
    expect(retryBtn).not.toBeNull();
  });

  it("test_close_button_calls_onClose", () => {
    const onClose = vi.fn<() => void>();
    mockDetail({ isError: true, error: new Error("network down") });
    render(<AckDrawer ackId="42" onClose={onClose} />);

    const closeBtn = document.querySelector(
      '[data-testid="error-close"]'
    ) as HTMLButtonElement | null;
    expect(closeBtn).not.toBeNull();
    closeBtn!.click();
    expect(onClose).toHaveBeenCalledTimes(1);
  });

  it("test_renders_download_button_when_raw_999_text_present", () => {
    mockDetail({ data: SAMPLE_ACK });
    render(<AckDrawer ackId="42" onClose={() => {}} />);

    // The header action slot populates with the Download 999 button
    // only when the ack detail carries raw_999_text.
    const dlBtn = document.querySelector('[data-testid="ack-drawer-download"]');
    expect(dlBtn).not.toBeNull();
  });

  it("test_omits_download_button_when_raw_999_text_absent", () => {
    const data: Ack = {
      id: 42,
      sourceBatchId: "b-uuid-1",
      acceptedCount: 3,
      rejectedCount: 1,
      receivedCount: 4,
      ackCode: "A",
      parsedAt: "2026-06-20T12:00:00Z",
    };
    mockDetail({ data });
    render(<AckDrawer ackId="42" onClose={() => {}} />);

    // No raw_999_text → no Download button.
    expect(document.querySelector('[data-testid="ack-drawer-download"]')).toBeNull();
  });
});
