// @vitest-environment happy-dom
// Tell React this is an `act`-aware test environment so react-query's
// internal state updates flush through without noisy console warnings.
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
  true;

import React, { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { describe, expect, it, vi, beforeEach } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Remittances } from "./Remittances";
import { api } from "@/lib/api";
import { useTailStore } from "@/store/tail-store";

// Module-level mock: vitest hoists `vi.mock` calls above imports. We only
// stub the method this page touches via the `useRemittances` hook.
// `isConfigured: true` is critical — without it, `useRemittances` falls
// back to the empty zustand store instead of calling our mocked API.
vi.mock("@/lib/api", () => ({
  api: {
    isConfigured: true,
    listRemittances: vi.fn(),
    getRemittance: vi.fn(),
  },
}));

// Mock the live-tail hook so the page renders the pill in the settled
// `live` state without driving the real streamTail parser. The hook's
// own test file (`useTailStream.test.ts`) covers the lifecycle in depth.
vi.mock("@/hooks/useTailStream", () => ({
  useTailStream: vi.fn(() => ({
    status: "live",
    lastEventAt: null,
    error: null,
    forceReconnect: vi.fn(),
  })),
}));

/**
 * Minimal `render` helper using react-dom/client + act(). Mirrors the
 * `renderIntoContainer` helper in `Reconciliation.test.tsx` — see that
 * file's comment for the rationale (project doesn't ship a DOM test
 * library yet, and adding one just for these tests would inflate the
 * dev-deps tree). Returns the rendered container so tests can assert
 * against the live DOM via `document.body.textContent`.
 */
function renderIntoContainer(element: React.ReactElement): {
  container: HTMLDivElement;
  unmount: () => void;
} {
  const container = document.createElement("div");
  document.body.appendChild(container);
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });

  const root: Root = createRoot(container);
  act(() => {
    root.render(
      React.createElement(QueryClientProvider, { client: qc }, element)
    );
  });

  return {
    container,
    unmount: () => {
      act(() => root.unmount());
      container.remove();
    },
  };
}

/**
 * Flush microtasks + react state updates until the predicate holds or we
 * time out. react-query's fetch resolves asynchronously, so we have to
 * await a tick before the rendered DOM reflects the mocked data.
 */
async function waitForText(
  text: string,
  timeoutMs = 2000
): Promise<void> {
  const start = Date.now();
  while (!document.body.textContent?.includes(text)) {
    if (Date.now() - start > timeoutMs) {
      throw new Error(`waitForText: "${text}" did not appear within ${timeoutMs}ms`);
    }
    await act(async () => {
      await Promise.resolve();
    });
  }
}

describe("Remittances", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Singleton tail-store: clear the remittances slice between tests
    // so a tail-arrival case (if added later) doesn't see rows from a
    // previous test.
    useTailStore.getState().reset("remittances");
  });

  it("renders a CAS adjustment label inside the expanded detail row", async () => {
    (
      api.listRemittances as unknown as ReturnType<typeof vi.fn>
    ).mockResolvedValue({
      items: [
        {
          id: "PCN-1",
          claimId: "CLM-1",
          payerName: "Colorado Medicaid",
          paidAmount: 100,
          adjustmentAmount: 60,
          receivedDate: "2026-06-19T12:00:00Z",
          checkNumber: "",
          status: "received",
          adjustments: [
            {
              group: "CO",
              reason: "45",
              label: "Charge exceeds fee schedule/maximum allowable",
              amount: 50,
              quantity: null,
            },
            {
              group: "PR",
              reason: "1",
              label: "Deductible amount",
              amount: 10,
              quantity: 1,
            },
          ],
        },
      ],
      total: 1,
      returned: 1,
      has_more: false,
    });

    const { unmount } = renderIntoContainer(React.createElement(Remittances));
    await waitForText("PCN-1");

    // The chevron + "Adjustments" header should not yet be visible because
    // the row hasn't been expanded yet.
    expect(document.body.textContent).not.toContain("Adjustments (2)");

    // Expand the row by clicking on the remit ID cell. We click the parent
    // row by selecting the cell containing "PCN-1" and bubbling up.
    const cell = Array.from(document.querySelectorAll("td")).find(
      (td) => td.textContent === "PCN-1"
    );
    const row = cell?.closest("tr");
    expect(row).toBeTruthy();
    await act(async () => {
      (row as HTMLTableRowElement).click();
    });
    await waitForText("Adjustments (2)");

    // Both CAS labels must surface (not the raw codes alone).
    expect(document.body.textContent).toContain(
      "Charge exceeds fee schedule/maximum allowable"
    );
    expect(document.body.textContent).toContain("Deductible amount");
    // Group/reason pills show the CARC code alongside.
    expect(document.body.textContent).toContain("CO-45");
    expect(document.body.textContent).toContain("PR-1");
    unmount();
  });

  // -------------------------------------------------------------------
  // Live-tail status pill (sub-project 5, Phase 5 Task 23, optional
  // page-level test). Cheap smoke check: the toolbar renders the pill
  // with the `live` label whenever the mocked stream is settled. The
  // hook's lifecycle is covered exhaustively in its own test file.
  // -------------------------------------------------------------------

  it("test_status_pill_shows_live_in_toolbar", async () => {
    (
      api.listRemittances as unknown as ReturnType<typeof vi.fn>
    ).mockResolvedValue({
      items: [],
      total: 0,
      returned: 0,
      has_more: false,
    });

    const { unmount } = renderIntoContainer(React.createElement(Remittances));

    // Wait for the empty-state to mount — once the page body is rendered,
    // the toolbar (and thus the pill) is in the DOM.
    await waitForText("Remittances · awaiting first 835");

    const pill = document.body.querySelector(
      '[data-testid="tail-status-pill"]',
    );
    expect(pill).not.toBeNull();
    expect(pill?.textContent).toContain("Live");

    unmount();
  });
});