// @vitest-environment happy-dom
// Pure prop-driven component; match the act() convention used by the
// other ClaimDrawer test files (ClaimDrawerHeader / ClaimDrawerSkeleton /
// ClaimDrawerError / ValidationPanel).
(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 } from "vitest";
import { ServiceLinesTable } from "./ServiceLinesTable";
import type { ClaimDetailServiceLine } from "@/types";

function renderIntoContainer(element: React.ReactElement): {
  container: HTMLDivElement;
  unmount: () => void;
} {
  const container = document.createElement("div");
  document.body.appendChild(container);
  const root: Root = createRoot(container);
  act(() => {
    root.render(element);
  });
  return {
    container,
    unmount: () => {
      act(() => root.unmount());
      container.remove();
    },
  };
}

function makeLine(
  overrides: Partial<ClaimDetailServiceLine> = {}
): ClaimDetailServiceLine {
  return {
    lineNumber: 1,
    procedureQualifier: "HC",
    procedureCode: "99213",
    modifiers: [],
    charge: 100.0,
    units: 1,
    unitType: "UN",
    serviceDate: "2026-01-10",
    ...overrides,
  };
}

describe("ServiceLinesTable", () => {
  it("test_section_label_includes_count", () => {
    const lines = [
      makeLine({ lineNumber: 1 }),
      makeLine({ lineNumber: 2, procedureCode: "99214" }),
      makeLine({ lineNumber: 3, procedureCode: "90834" }),
    ];

    const { container, unmount } = renderIntoContainer(
      <ServiceLinesTable serviceLines={lines} />
    );

    const label = container.querySelector('[data-testid="section-label"]');
    expect(label).not.toBeNull();

    // `uppercase` is applied via Tailwind CSS, so textContent gives the
    // source-case string "Service Lines"; assert case-insensitively and
    // confirm the visual transform via the className sniff below.
    const text = (label?.textContent ?? "").toLowerCase();
    expect(text).toContain("service lines");
    expect(text).toContain("(3)");

    // Eyebrow style (uppercase + tracking) — sniff the className.
    // The shared `.eyebrow` class in index.css consolidates the
    // eyebrow treatment; tests assert it lands on the section label.
    const cls = label?.className ?? "";
    expect(cls).toContain("eyebrow");

    unmount();
  });

  it("test_empty_state_renders_when_no_lines", () => {
    const { container, unmount } = renderIntoContainer(
      <ServiceLinesTable serviceLines={[]} />
    );

    const empty = container.querySelector('[data-testid="service-lines-empty"]');
    expect(empty).not.toBeNull();
    expect(empty?.textContent ?? "").toContain("No service lines");

    // No table rendered in the empty state.
    expect(container.querySelector('[data-testid="service-lines-table"]')).toBeNull();
    expect(container.querySelectorAll('[data-testid="service-line-row"]').length).toBe(0);

    // Section label still present, but with count "(0)".
    const label = container.querySelector('[data-testid="section-label"]');
    expect(label?.textContent ?? "").toContain("(0)");

    unmount();
  });

  it("test_single_line_renders_one_row", () => {
    const line = makeLine({
      lineNumber: 1,
      procedureQualifier: "HC",
      procedureCode: "99213",
      modifiers: [],
      charge: 150.0,
      units: 1,
      unitType: "UN",
      serviceDate: "2026-01-10",
    });

    const { container, unmount } = renderIntoContainer(
      <ServiceLinesTable serviceLines={[line]} />
    );

    const rows = container.querySelectorAll('[data-testid="service-line-row"]');
    expect(rows.length).toBe(1);

    // Row carries the line number as a data attribute for test stability.
    expect(rows[0].getAttribute("data-line-number")).toBe("1");

    // Cells: line #, procedure, charge, units, date, paid, adjustments.
    const cells = rows[0].querySelectorAll("td");
    expect(cells.length).toBe(7);

    const cellText = Array.from(cells).map((c) => c.textContent ?? "");
    expect(cellText[0]).toContain("1");                     // line #
    expect(cellText[1]).toContain("HC 99213");             // procedure
    expect(cellText[2]).toContain("$150.00");               // charge
    expect(cellText[3]).toContain("1");                     // units count
    expect(cellText[3]).toContain("UN");                    // units type
    expect(cellText[4]).toContain("2026");                  // date (year at minimum)
    // Paid + Adjustments default to em-dash when no lineReconciliation is provided.
    expect(cellText[5]).toContain("—");
    expect(cellText[6]).toContain("—");

    unmount();
  });

  it("test_paid_and_adjustments_columns_render_from_lineReconciliation", () => {
    const line = makeLine({ lineNumber: 1, charge: 100.0 });
    const lineReconciliation = [
      { lineNumber: 1, status: "matched" as const, paid: "80.00", adjustmentsSum: "20.00" },
    ];
    const { container, unmount } = renderIntoContainer(
      <ServiceLinesTable
        serviceLines={[line]}
        lineReconciliation={lineReconciliation}
      />
    );
    const row = container.querySelector('[data-testid="service-line-row"]');
    expect(row).not.toBeNull();
    const paid = row?.querySelector('[data-testid="paid-cell"]');
    const adjustments = row?.querySelector('[data-testid="adjustments-cell"]');
    expect(paid?.textContent ?? "").toContain("$80.00");
    expect(adjustments?.textContent ?? "").toContain("$20.00");
    unmount();
  });

  it("test_paid_and_adjustments_render_em_dash_when_null", () => {
    const line = makeLine({ lineNumber: 1 });
    const lineReconciliation = [
      { lineNumber: 1, status: "unmatched_837_only" as const, paid: null, adjustmentsSum: null },
    ];
    const { container, unmount } = renderIntoContainer(
      <ServiceLinesTable
        serviceLines={[line]}
        lineReconciliation={lineReconciliation}
      />
    );
    const row = container.querySelector('[data-testid="service-line-row"]');
    const paid = row?.querySelector('[data-testid="paid-cell"]');
    const adjustments = row?.querySelector('[data-testid="adjustments-cell"]');
    expect(paid?.textContent ?? "").toBe("—");
    expect(adjustments?.textContent ?? "").toBe("—");
    unmount();
  });

  it("test_multiple_lines_render_in_order", () => {
    const lines = [
      makeLine({ lineNumber: 1, procedureCode: "99213" }),
      makeLine({ lineNumber: 2, procedureCode: "99214" }),
      makeLine({ lineNumber: 3, procedureCode: "90834" }),
    ];

    const { container, unmount } = renderIntoContainer(
      <ServiceLinesTable serviceLines={lines} />
    );

    const rows = Array.from(
      container.querySelectorAll('[data-testid="service-line-row"]')
    );
    expect(rows.length).toBe(3);

    const lineNumbers = rows.map((r) => r.getAttribute("data-line-number"));
    expect(lineNumbers).toEqual(["1", "2", "3"]);

    // The procedure cell preserves insertion order across rows.
    const procedureCells = rows.map((r) =>
      r.querySelectorAll("td")[1]?.textContent ?? ""
    );
    expect(procedureCells[0]).toContain("99213");
    expect(procedureCells[1]).toContain("99214");
    expect(procedureCells[2]).toContain("90834");

    unmount();
  });

  it("test_procedure_column_shows_qualifier_code_and_modifiers", () => {
    const line = makeLine({
      lineNumber: 1,
      procedureQualifier: "HC",
      procedureCode: "99213",
      modifiers: ["25", "59"],
    });

    const { container, unmount } = renderIntoContainer(
      <ServiceLinesTable serviceLines={[line]} />
    );

    const procedureCell = container.querySelector(
      '[data-testid="service-line-row"] td:nth-child(2)'
    );
    expect(procedureCell).not.toBeNull();

    const text = procedureCell?.textContent ?? "";
    // Base: qualifier + code.
    expect(text).toContain("HC 99213");
    // Modifiers present (separated by middle dots per plan).
    expect(text).toContain("25");
    expect(text).toContain("59");
    // The two modifiers should both appear after the procedure code; the
    // separator used in the impl is the middle dot "·".
    expect(text).toContain("· 25");
    expect(text).toContain("· 59");

    unmount();
  });

  it("test_charge_column_is_formatted_and_mono", () => {
    const line = makeLine({ lineNumber: 1, charge: 123.45 });

    const { container, unmount } = renderIntoContainer(
      <ServiceLinesTable serviceLines={[line]} />
    );

    const chargeCell = container.querySelector(
      '[data-testid="service-line-row"] td:nth-child(3)'
    );
    expect(chargeCell).not.toBeNull();

    // Currency formatting: fmt.usdPrecise → "$123.45".
    expect(chargeCell?.textContent ?? "").toContain("$123.45");

    // Mono styling — either via Tailwind class or inline style.
    const cls = chargeCell?.className ?? "";
    const style = chargeCell?.getAttribute("style") ?? "";
    const isMono =
      cls.includes("font-mono") ||
      style.includes("--m-font-mono") ||
      style.includes("font-mono");
    expect(isMono).toBe(true);

    // Right-aligned (charge is the headline number, anchored to the right).
    expect(cls).toContain("text-right");

    // Tabular nums so charges align across rows.
    expect(cls).toContain("tabular-nums");

    unmount();
  });

  it("test_units_and_date_columns", () => {
    const line = makeLine({
      lineNumber: 1,
      units: 2,
      unitType: "UN",
      serviceDate: "2026-01-10",
    });

    const { container, unmount } = renderIntoContainer(
      <ServiceLinesTable serviceLines={[line]} />
    );

    const cells = container.querySelectorAll(
      '[data-testid="service-line-row"] td'
    );
    const unitsCell = cells[3];
    const dateCell = cells[4];

    // Units column: "<n> <unitType>".
    const unitsText = unitsCell?.textContent ?? "";
    expect(unitsText).toContain("2");
    expect(unitsText).toContain("UN");

    // Date column: fmt.date formats "2026-01-10" as a localized
    // "MMM D, YYYY" string. `new Date("YYYY-MM-DD")` is parsed as UTC
    // midnight, so the day can roll over by ±1 in non-UTC test
    // environments — assert only the year and month to stay
    // timezone-robust without forking fmt.date.
    const dateText = dateCell?.textContent ?? "";
    expect(dateText).toContain("2026");
    expect(dateText).toContain("Jan");
    // The date cell should NOT be empty (i.e. fmt.date was actually invoked).
    expect(dateText.trim().length).toBeGreaterThan(0);

    unmount();
  });
});
