import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Checkbox } from ".";

const CHECKBOX_CLASS_NAME = "mobius-checkbox__input";

describe("Checkbox", () => {
  it("should render without error", () => {
    const optionText = "Agree";
    const view = render(<Checkbox label={optionText} value="value" />);

    expect(view).toBeTruthy();
  });

  it("should render with a name", () => {
    const optionText = "Agree";
    const name = "name";

    render(
      <form name="form">
        <Checkbox label={optionText} name={name} value="value" />
      </form>,
    );

    const checkbox = screen.getByRole("checkbox");
    expect(checkbox).toHaveAttribute("name", name);
  });

  it("should call onChange when clicked", async () => {
    const callback = vi.fn();
    const optionText = "Agree";

    const { getByLabelText } = render(
      <Checkbox label={optionText} onChange={callback} value="value" />,
    );

    const option = getByLabelText(optionText);

    await userEvent.click(option);

    expect(callback).toHaveBeenCalled();
    expect(option).toBeChecked();
  });

  it("should be selectable via keyboard", async () => {
    const callback = vi.fn();
    const optionText = "Agree";

    const { getByLabelText } = render(
      <Checkbox label={optionText} onChange={callback} value="value" />,
    );

    const option = getByLabelText(optionText);

    await userEvent.tab();
    await userEvent.keyboard("[Space]");

    expect(callback).toHaveBeenCalled();
    expect(option).toBeChecked();

    await userEvent.keyboard("[Space]");

    expect(callback).toHaveBeenCalled();
    expect(option).not.toBeChecked();
  });

  it("should set the correct aria-describedby attribute", () => {
    const optionText = "Agree";
    const errorText = "This field is required";

    const { getByLabelText } = render(
      <Checkbox label={optionText} errorMessage={errorText} />,
    );

    const option = getByLabelText(optionText);

    expect(option).toHaveAccessibleDescription(`${errorText}`);
  });

  it("should pass additional props as attributes to the input element", () => {
    const optionText = "Agree";
    const dataTestId = "test";

    const { getByLabelText } = render(
      <Checkbox label={optionText} data-testid={dataTestId} value="value" />,
    );

    const option = getByLabelText(optionText);

    expect(option).toHaveAttribute("data-testid", dataTestId);
  });

  describe("isRequired", () => {
    it("should set required attribute", () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox label={optionText} value="value" isRequired />,
      );

      const option = getByLabelText(optionText);

      expect(option).toBeRequired();
    });

    it("should have className of --is-required when true", () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox label={optionText} value="value" isRequired />,
      );

      const option = getByLabelText(optionText);

      expect(option).toHaveClass("--is-required");
    });

    it("should have className of --is-optional when false", () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox label={optionText} value="value" isRequired={false} />,
      );

      const option = getByLabelText(optionText);

      expect(option).toHaveClass("--is-optional");
    });

    it("should have neither --is-required nor --is-optional when unset", () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox label={optionText} value="value" />,
      );

      const option = getByLabelText(optionText);

      expect(option).not.toHaveClass("--is-required");
      expect(option).not.toHaveClass("--is-optional");
    });
  });

  describe("uses correct class names", () => {
    it("uses mobius Mobius class", () => {
      const optionText = "Agree";

      const { container } = render(
        <Checkbox label={optionText} value="value" />,
      );

      expect(container.firstChild).toHaveClass("mobius");
    });

    it("uses base Mobius class", () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox label={optionText} value="value" />,
      );

      const option = getByLabelText(optionText);

      expect(option).toHaveClass(CHECKBOX_CLASS_NAME);
    });

    it("includes disabled class name", () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox label={optionText} isDisabled value="value" />,
      );

      const option = getByLabelText(optionText);

      expect(option).toHaveClass("--is-disabled");
    });

    it("includes valid class name", () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox label={optionText} isInvalid={false} value="value" />,
      );

      const option = getByLabelText(optionText);

      expect(option).toHaveClass("--is-valid");
    });

    it("includes invalid class name", () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox
          label={optionText}
          isInvalid
          errorMessage="There is a problem"
          value="value"
        />,
      );

      const option = getByLabelText(optionText);

      expect(option).toHaveClass("--is-invalid");
    });

    it("includes custom class name", () => {
      const labelText = "Agree";
      const customClass = "my-class";

      render(
        <Checkbox label={labelText} className={customClass} value="value" />,
      );

      const input = screen.getByRole("checkbox");

      expect(input).not.toHaveClass(customClass);
      expect(input).toHaveClass(CHECKBOX_CLASS_NAME);
    });

    it("only sets id on the input element", () => {
      const optionText = "Agree";
      const testId = "test";

      const { getByLabelText } = render(
        <Checkbox id={testId} label={optionText} value="value" />,
      );

      const option = getByLabelText(optionText);

      expect(option).toHaveAttribute("id", testId);
    });

    it("includes focused and selected class names", async () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox label={optionText} value="value" />,
      );

      const option = getByLabelText(optionText);

      await userEvent.tab();

      expect(option).toHaveClass(CHECKBOX_CLASS_NAME);

      await userEvent.keyboard("[Space]");

      expect(option).toHaveClass("--is-selected");
    });
  });

  describe("when checkbox is disabled", () => {
    it("renders disabled attribute", () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox label={optionText} value="value" isDisabled />,
      );

      const option = getByLabelText(optionText);

      expect(option).toBeDisabled();
    });
  });

  describe("when checkbox is invalid", () => {
    it("renders aria-invalid attribute", () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox label={optionText} value="value" isInvalid />,
      );

      const option = getByLabelText(optionText);

      expect(option).toHaveAttribute("aria-invalid", "true");
    });
  });

  describe("when checkbox is read only", () => {
    it("renders readonly attribute", () => {
      const optionText = "Agree";

      const { getByLabelText } = render(
        <Checkbox label={optionText} value="value" isReadOnly />,
      );

      const option = getByLabelText(optionText);

      expect(option).toHaveAttribute("readonly");
    });
  });

  describe("aria-errormessage", () => {
    describe("when checkbox is accompanied by an error message", () => {
      it("renders aria-erromessage with an id that matches error message id", () => {
        const optionText = "Agree";

        const { getByLabelText, getByTestId } = render(
          <Checkbox
            label={optionText}
            value="value"
            errorMessage="This is an error"
          />,
        );

        const option = getByLabelText(optionText);

        const errorMessageId = getByTestId("ErrorMessage");

        expect(option).toHaveAttribute("aria-errormessage", errorMessageId.id);
      });
    });

    describe("when radio is not accompanied by an error message", () => {
      it("does not render aria-erromessage", () => {
        const optionText = "Agree";

        const { getByLabelText } = render(
          <Checkbox label={optionText} value="value" />,
        );

        const option = getByLabelText(optionText);

        expect(option).not.toHaveAttribute("aria-errormessage");
      });
    });
  });
});
