// @vitest-environment happy-dom
// ProviderDrawer wires `useProviderDetail` (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, fireEvent, render } from "@testing-library/react";
import { ApiError } from "@/lib/api";
import { ProviderDrawer } from "@/components/ProviderDrawer";
import type { Provider } 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.getProvider` — lets each test pin the hook's exact return shape
// without standing up a real `QueryClient`.
//
// `vi.hoisted` is required because `vi.mock` is hoisted ABOVE top-level
// `const` declarations — referencing a top-level `vi.fn()` directly from
// the factory would hit "Cannot access before initialization" at runtime.
const { useProviderDetail } = vi.hoisted(() => ({
  useProviderDetail: vi.fn(),
}));
vi.mock("@/hooks/useProviderDetail", () => ({
  useProviderDetail,
}));

/**
 * Minimal valid `Provider` fixture — every required key present so the
 * component typechecks. The fields are what `ProviderOverview` reads,
 * so the success-path render exercises every prop.
 */
const SAMPLE_PROVIDER: Provider = {
  npi: "1881068062",
  name: "Montrose Memorial",
  taxId: "721587149",
  address: "123 Main St",
  city: "Montrose",
  state: "CO",
  zip: "81401",
  phone: "(970) 555-1234",
  claimCount: 184,
  outstandingAr: 12450,
};

/**
 * Extended provider fixture (SP21 Task 3.1) — adds the
 * `recent_claims` and `recent_activity` slices the Claims/Activity
 * tabs read from. Used by the tabs tests below. Field shapes match
 * `ClaimSummary` and `ActivityEvent` from `@/types`.
 */
const SAMPLE_PROVIDER_WITH_DETAILS: Provider = {
  ...SAMPLE_PROVIDER,
  recent_claims: [
    {
      id: "CLM-0001",
      state: "submitted",
      billedAmount: 250,
      patientName: "Jane Q Patient",
      providerNpi: "1881068062",
      payerName: "Aetna",
      cptCode: "99213",
      submissionDate: "2026-06-20",
      parsedAt: "2026-06-20",
      status: "submitted",
      batchId: "batch-001",
    },
  ],
  recent_activity: [
    {
      id: 1,
      ts: "2026-06-20T15:30:00Z",
      kind: "claim_submitted",
      batchId: "batch-001",
      claimId: "CLM-0001",
      remittanceId: null,
      payload: {},
    },
    {
      id: 2,
      ts: "2026-06-20T15:35:00Z",
      kind: "claim_paid",
      batchId: "batch-001",
      claimId: "CLM-0001",
      remittanceId: null,
      payload: {},
    },
  ],
};

/**
 * 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: Provider | null;
    isLoading: boolean;
    isError: boolean;
    error: Error | null;
    refetch: () => void;
  }> = {}
) {
  useProviderDetail.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("ProviderDrawer", () => {
  it("test_renders_nothing_when_npi_is_null", () => {
    mockDetail({ data: null });
    render(<ProviderDrawer npi={null} onClose={() => {}} />);

    // No provider 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("Montrose Memorial");
  });

  it("test_calls_useProviderDetail_with_npi", () => {
    mockDetail({ data: SAMPLE_PROVIDER });
    render(<ProviderDrawer npi="1881068062" onClose={() => {}} />);

    expect(useProviderDetail).toHaveBeenCalledWith("1881068062");
  });

  it("test_renders_provider_overview_on_success", () => {
    mockDetail({ data: SAMPLE_PROVIDER });
    render(<ProviderDrawer npi="1881068062" onClose={() => {}} />);

    // ProviderOverview renders NPI + Tax ID as Field values; the
    // drawer header shows the provider name as the title.
    expect(document.body.textContent).toContain("Montrose Memorial");
    expect(document.body.textContent).toContain("1881068062");
    expect(document.body.textContent).toContain("721587149");
  });

  it("test_renders_skeleton_while_loading", () => {
    mockDetail({ isLoading: true });
    render(<ProviderDrawer npi="1881068062" onClose={() => {}} />);

    // The `Skeleton` primitive sets `aria-busy="true"` — a stable hook
    // for the loading state that doesn't require a custom testid.
    expect(document.querySelectorAll('[aria-busy="true"]').length).toBeGreaterThan(0);
    // And the provider name should NOT have leaked in yet.
    expect(document.body.textContent).not.toContain("Montrose Memorial");
  });

  it("test_renders_not_found_error_on_404", () => {
    mockDetail({ isError: true, error: new ApiError(404, "Provider ghost not found") });
    render(<ProviderDrawer npi="ghost" onClose={() => {}} />);

    const errEl = document.querySelector('[data-testid="provider-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(<ProviderDrawer npi="123" onClose={() => {}} />);

    const errEl = document.querySelector('[data-testid="provider-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_renders_not_found_branch_in_demo_mode_for_unknown_npi", () => {
    // Demo-mode fallback: when `api.isConfigured` is false, the hook
    // reads from the in-memory zustand store instead of fetching. If the
    // queried NPI isn't in the providers list, the hook returns
    // `isError: true` with an `ApiError(404, "Provider not found")`.
    // The drawer's `errorKind` computation must route this to the
    // `not_found` branch — NOT the generic `network` branch, which
    // would mislead the user into thinking the server is down when
    // there is simply no backend configured (or the NPI is just not
    // in the local store). Regression guard for the `new Error` →
    // `new ApiError(404, ...)` fix on the demo-mode fallback branch.
    mockDetail({
      isError: true,
      error: new ApiError(404, "Provider not found"),
    });
    render(<ProviderDrawer npi="ghost-npi" onClose={() => {}} />);

    // The not_found branch renders — with the "doesn't exist" copy.
    const notFoundEl = document.querySelector(
      '[data-testid="provider-drawer-error-not_found"]'
    );
    expect(notFoundEl).not.toBeNull();
    expect(notFoundEl?.textContent).toContain("doesn't exist");

    // And — the whole point of this test — the network branch MUST NOT
    // be present. A plain `Error` from the demo-mode fallback would
    // have landed here and shown "Couldn't reach the server", which
    // is wrong for a known-local-miss.
    expect(
      document.querySelector('[data-testid="provider-drawer-error-network"]')
    ).toBeNull();
    expect(document.body.textContent).not.toContain("Couldn't reach the server");

    // not_found has no retry affordance — same invariant as the 404
    // test above, restated here so this test is self-contained as a
    // regression guard.
    expect(document.querySelector('[data-testid="error-retry"]')).toBeNull();
  });

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

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

  // -- Tabs (SP21 Task 3.1) -------------------------------------------------
  //
  // Radix Tabs only mounts the active `Tabs.Content` into the DOM (no
  // `forceMount`), so clicking a tab trigger causes the previous panel to
  // unmount and the new one to mount. The tests below rely on that —
  // "panel X renders" is asserted by checking that unique text from that
  // panel's component is present in `document.body` after the click.

  it("test_renders_three_tabs_overview_claims_activity", () => {
    mockDetail({ data: SAMPLE_PROVIDER_WITH_DETAILS });
    render(<ProviderDrawer npi="1881068062" onClose={() => {}} />);

    // All three tab triggers are present, with the spec-mandated labels
    // in the spec-mandated order.
    const triggers = Array.from(document.querySelectorAll('[role="tab"]'));
    expect(triggers.length).toBe(3);
    expect(triggers.map((t) => t.textContent)).toEqual([
      "Overview",
      "Claims",
      "Activity",
    ]);
    // Overview is the default; its trigger must be selected on mount.
    expect(triggers[0]?.getAttribute("aria-selected")).toBe("true");
    expect(triggers[0]?.getAttribute("data-state")).toBe("active");
  });

  it("test_switches_to_claims_tab_and_shows_recent_claims", () => {
    mockDetail({ data: SAMPLE_PROVIDER_WITH_DETAILS });
    render(<ProviderDrawer npi="1881068062" onClose={() => {}} />);

    const claimsTrigger = Array.from(document.querySelectorAll('[role="tab"]'))
      .find((t) => t.textContent === "Claims");
    expect(claimsTrigger).not.toBeNull();
    // Radix Tabs wires `onMouseDown` (not `onClick`) to its
    // `onValueChange` handler — fire the matching event so the tab
    // actually activates under happy-dom.
    fireEvent.mouseDown(claimsTrigger!);

    // Claims tab is now selected — and ProviderRecentClaims content is in
    // the DOM (claim id + patient name + the "View all claims" link).
    expect(claimsTrigger?.getAttribute("aria-selected")).toBe("true");
    expect(document.body.textContent).toContain("CLM-0001");
    expect(document.body.textContent).toContain("Jane Q Patient");
    expect(document.body.textContent).toContain("View all claims");
  });

  it("test_switches_to_activity_tab_and_shows_recent_activity", () => {
    mockDetail({ data: SAMPLE_PROVIDER_WITH_DETAILS });
    render(<ProviderDrawer npi="1881068062" onClose={() => {}} />);

    const activityTrigger = Array.from(document.querySelectorAll('[role="tab"]'))
      .find((t) => t.textContent === "Activity");
    expect(activityTrigger).not.toBeNull();
    fireEvent.mouseDown(activityTrigger!);

    // Activity tab is now selected — and ProviderRecentActivity content
    // is in the DOM (both `kind` strings from the fixture).
    expect(activityTrigger?.getAttribute("aria-selected")).toBe("true");
    expect(document.body.textContent).toContain("claim_submitted");
    expect(document.body.textContent).toContain("claim_paid");
  });
});
