import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import assert from "node:assert";
import {
  describe, test, mock, afterEach,
} from "node:test";

import type { IChecker } from "../checker";
import { CheckerGroup } from "./index";
import type { ICheckerGroup, ICheckerGroupItemsGroup } from "./index";

describe("CheckerGroup", () => {
  const mockItems: ICheckerGroup["items"] = [
    {
      name: "option1",
      value: "1",
      label: "Option 1",
      isChecked: false,
    },
    {
      name: "option2",
      value: "2",
      label: "Option 2",
      isChecked: true,
    },
    {
      name: "option3",
      value: "3",
      label: "Option 3",
      isChecked: false,
    },
  ];

  const mockNestedItems: ICheckerGroup["items"] = [
    {
      label: "Group A",
      items: [
        {
          name: "a1",
          value: "a1",
          label: "A1",
          isChecked: false,
        },
        {
          name: "a2",
          value: "a2",
          label: "A2",
          isChecked: true,
        },
      ],
    },
    {
      label: "Group B",
      items: [
        {
          name: "b1",
          value: "b1",
          label: "B1",
          isChecked: false,
        },
      ],
    },
  ];

  const radioItems: ICheckerGroup["items"] = [
    {
      name: "group1",
      value: "opt1",
      label: "Option 1",
      isChecked: true,
    },
    {
      name: "group1",
      value: "opt2",
      label: "Option 2",
      isChecked: false,
    },
  ];

  /* COMMON */

  afterEach(() => {
    cleanup();
  });

  test("renders empty placeholder when items array is empty", () => {
    render(<CheckerGroup items={[]} emptyPlaceholder={"No items available"} />);

    assert.notStrictEqual(screen.queryByText("No items available"), null);
    const listItem = screen.getByRole("listitem");
    assert.ok(listItem.className.includes("isEmpty")); // adjust if actual class name differs
  });

  test("renders checkboxes by default", () => {
    render(<CheckerGroup items={mockItems} />);

    const checkboxes = screen.getAllByRole("checkbox");
    assert.strictEqual(checkboxes.length, mockItems.length);
    checkboxes.forEach((checker) => {
      assert.strictEqual(checker.getAttribute("type"), "checkbox");
    });
  });

  test("renders radio when type is 'radio'", () => {
    render(<CheckerGroup items={mockItems} type={"radio"} />);

    const radios = screen.getAllByRole("radio");
    assert.strictEqual(radios.length, mockItems.length);
    radios.forEach((radio) => {
      assert.strictEqual(radio.getAttribute("type"), "radio");
    });
  });

  test("renders nested groups with labels", () => {
    render(<CheckerGroup items={mockNestedItems} />);

    assert.notStrictEqual(screen.queryByText("Group A"), null);
    assert.notStrictEqual(screen.queryByText("Group B"), null);

    const checkboxes = screen.getAllByRole("checkbox");
    const expectedLength = mockNestedItems.reduce((sum, currentGroup) => {
      return sum + (currentGroup as ICheckerGroupItemsGroup).items.length;
    }, 0);
    assert.strictEqual(checkboxes.length, expectedLength);
  });

  /* HANDLERS */

  test("calls onChange when checker is changed", async () => {
    const user = userEvent.setup();
    const onChange = mock.fn();

    render(<CheckerGroup items={mockItems} onChange={onChange} />);

    const firstCheckbox = screen.getAllByRole("checkbox")[0];
    await user.click(firstCheckbox);

    assert.strictEqual(onChange.mock.calls.length, 1);
    const args = onChange.mock.calls[0]?.arguments;
    assert.ok(args);
    const [ firstArg, secondArg ] = args;
    assert.ok(firstArg && typeof firstArg === "object");
    assert.ok("event" in firstArg);
    assert.strictEqual(typeof secondArg, "function");
  });

  test("updates checkers state when onChange is not provided", async () => {
    const user = userEvent.setup();
    render(<CheckerGroup items={mockItems} />);

    const checkboxes = screen.getAllByRole("checkbox") as HTMLInputElement[];
    assert.strictEqual(checkboxes[0].checked, false);
    assert.strictEqual(checkboxes[1].checked, true);

    await user.click(checkboxes[0]);
    assert.strictEqual(checkboxes[0].checked, true);
    assert.strictEqual(checkboxes[1].checked, true);
  });

  test("handles radio group behaviour correctly", async () => {
    const user = userEvent.setup();
    render(<CheckerGroup items={radioItems} type={"radio"} />);

    const radios = screen.getAllByRole("radio") as HTMLInputElement[];
    assert.strictEqual(radios[0].checked, true);
    assert.strictEqual(radios[1].checked, false);

    await user.click(radios[1]);
    assert.strictEqual(radios[0].checked, false);
    assert.strictEqual(radios[1].checked, true);
  });

  /* CHILDREN */

  test("uses custom getItem function for rendering", () => {
    const getItem = mock.fn<NonNullable<ICheckerGroup["getItem"]>>(({ item, checker }) => (
      <div data-testid={`custom-${(item as IChecker).value}`}>
        {checker}
        <span>Custom wrapper</span>
      </div>
    ));

    render(<CheckerGroup items={mockItems} getItem={getItem} />);

    assert.strictEqual(getItem.mock.calls.length, 3);
    mockItems.forEach((checker) => {
      const testId = `custom-${(checker as IChecker).value}`;
      assert.notStrictEqual(screen.queryByTestId(testId), null);
    });
    const wrappers = screen.getAllByText("Custom wrapper");
    assert.strictEqual(wrappers.length, 3);
  });

  test("updates list when 'items' prop is changed", () => {
    const { rerender } = render(<CheckerGroup items={mockItems.slice(0, 1)} />);

    let checkboxes = screen.getAllByRole("checkbox");
    assert.strictEqual(checkboxes.length, 1);

    rerender(<CheckerGroup items={mockItems} />);
    checkboxes = screen.getAllByRole("checkbox");
    assert.strictEqual(checkboxes.length, mockItems.length);
  });

  test("renders children instead of default content", () => {
    render(
      <CheckerGroup items={mockItems}>
        <div data-testid={"customContent"}>Custom content</div>
      </CheckerGroup>
    );

    assert.notStrictEqual(screen.queryByTestId("customContent"), null);
    assert.strictEqual(screen.queryByRole("checkbox"), null);
  });
});
