import { fireEvent, render, waitFor } from '@testing-library/react';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { describe, expect, it, vi } from 'vitest';
import { Radio, RadioProps } from './Radio';

describe('Radio', () => {
  const defaultProps: RadioProps = {
    autoFocus: true,
    disabled: true,
    id: 'test-id',
    name: 'test-name',
    options: [
      { label: 'Test-Option-Label-1', value: 'test-option-1' },
      { label: 'Test-Option-Label-2', value: 'test-option-2' },
      { label: 'Test-Option-Label-3', value: 'test-option-3' },
    ],
    onBlur: () => null,
    onFocus: () => null,
  };

  it('renders the component without crashing', () => {
    const { container } = render(<Radio name="test-name" />);

    expect(container).toBeTruthy();
  });

  it('displays a label', () => {
    const mockLabel = 'mockLabel';
    const { container } = render(<Radio name="test-name" label={mockLabel} />);

    const label = container.querySelector('label');

    expect(label?.textContent).toBe(mockLabel);
  });

  it('each radio button accepts optional props when passed in', () => {
    const spy = vi.fn();
    const mockId = 'test-id';

    const mockInputProps: Partial<RadioProps> = {
      autoFocus: false,
      disabled: true,
      onBlur: spy,
      onFocus: spy,
    };

    const { container } = render(
      <Radio
        name="test-name"
        id={mockId}
        options={defaultProps.options}
        {...mockInputProps}
      />,
    );

    const radio = container.querySelector('.container') as HTMLElement;
    const radioButton = container.querySelector('input') as HTMLInputElement;

    expect(radio.id).toBe(mockId);
    expect(radioButton.autofocus).toBe(mockInputProps.autoFocus);
    expect(radioButton.disabled).toBe(mockInputProps.disabled);
  });

  it(`- each radio button 'name' should have the same name
  - each 'id' is a combination of 'name' and 'value'
  - each radio button should have a 'label
  - each radio button 'htmlFor' prop should be the same as its 'id'`, () => {
    const mockName = 'test-name';
    const mockOptions = defaultProps.options ?? [];
    const { container } = render(
      <Radio {...defaultProps} name={mockName} options={mockOptions} />,
    );

    const radioButtons = container.querySelectorAll('.option');

    radioButtons.forEach((radioOption, idx) => {
      const id = `${mockName}-${mockOptions[idx].value}`;
      const input = radioOption.querySelector('input') as HTMLInputElement;
      const label = radioOption.querySelector('label') as HTMLLabelElement;

      expect(input.name).toBe(mockName);
      expect(input.id).toBe(id);
      expect(label.htmlFor).toBe(id);
      expect(label.textContent).toContain(mockOptions[idx].label);
    });
  });

  it(`sets current button to 'checked' using the 'value' prop and autofocus selected button`, () => {
    const [, option2] = defaultProps.options ?? [];
    const mockValue: string = option2.value as string;
    const { container, rerender } = render(<Radio {...defaultProps} />);

    let radioButtons = container.querySelectorAll('input');

    expect((radioButtons[0] as HTMLInputElement).checked).toBe(false);
    expect((radioButtons[0] as HTMLInputElement).autofocus).toBe(false);
    expect((radioButtons[1] as HTMLInputElement).checked).toBe(false);
    expect((radioButtons[1] as HTMLInputElement).autofocus).toBe(false);
    expect((radioButtons[2] as HTMLInputElement).checked).toBe(false);
    expect((radioButtons[2] as HTMLInputElement).autofocus).toBe(false);

    rerender(<Radio {...defaultProps} value={mockValue} />);

    radioButtons = container.querySelectorAll('input');

    expect((radioButtons[0] as HTMLInputElement).checked).toBe(false);
    expect((radioButtons[0] as HTMLInputElement).autofocus).toBe(false);
    expect((radioButtons[1] as HTMLInputElement).checked).toBe(true);
    expect((radioButtons[1] as HTMLInputElement).autofocus).toBe(true);
    expect((radioButtons[2] as HTMLInputElement).checked).toBe(false);
    expect((radioButtons[2] as HTMLInputElement).autofocus).toBe(false);
  });

  it(`emits onBlur, onFocus, and onChange events`, () => {
    const onBlurSpy = vi.fn();
    const onFocusSpy = vi.fn();
    const onChangeSpy = vi.fn();

    const { container } = render(
      <Radio
        {...defaultProps}
        onChange={onChangeSpy}
        onBlur={onBlurSpy}
        onFocus={onFocusSpy}
      />,
    );

    const radioButton = container.querySelector('input') as HTMLInputElement;

    fireEvent.blur(radioButton);
    expect(onBlurSpy).toHaveBeenCalledTimes(1);

    fireEvent.focus(radioButton);
    expect(onFocusSpy).toHaveBeenCalledTimes(1);

    fireEvent.click(radioButton);
    expect(onChangeSpy).toHaveBeenCalledTimes(1);
  });

  it(`clicking on the radio button or it's label will raise onChange with the value`, () => {
    const onChangeSpy = vi.fn();

    const { container } = render(
      <Radio {...defaultProps} onChange={onChangeSpy} />,
    );

    const radioButtons = container.querySelectorAll('input');
    const radioButton = radioButtons[0] as HTMLInputElement;
    const secondButton = radioButtons[1] as HTMLInputElement;

    fireEvent.click(radioButton);
    fireEvent.click(secondButton);

    expect(onChangeSpy).toHaveBeenCalledTimes(2);
  });

  it('applies error styling and renders error message when an error is passed', () => {
    const [option1] = defaultProps.options ?? [];
    const mockErrorMessage = 'test-error-message';
    const { container } = render(
      <Radio {...defaultProps} options={[option1]} error={mockErrorMessage} />,
    );

    const errorMsg = container.querySelector('small');
    const svg = container.querySelector('svg');

    expect(errorMsg?.textContent).toBe(mockErrorMessage);
    expect(svg?.classList.contains('hasError')).toBe(true);
  });

  describe('confirmation', () => {
    const mockEvent = {
      e: 'event',
      persist: () => vi.fn(),
    };

    it(`'Advanced' mode changes applies confirmation styles`, async () => {
      const { container } = render(
        <Radio {...defaultProps} confirmationMode="Advanced" />,
      );

      const radioButton = container.querySelector('input') as HTMLInputElement;
      let confirmation = document.querySelector('.confirm-dialog-container');

      expect(confirmation).not.toBeInTheDocument();

      act(() => {
        fireEvent.click(radioButton);
      });

      await waitFor(() => {
        confirmation = document.querySelector('.confirm-dialog-container');
        expect(confirmation).toBeInTheDocument();
      });

      const radioInputs = container.querySelectorAll('input');
      const radioSVG = container.querySelectorAll('svg');

      expect(radioSVG[0].classList.contains('hasConfirm')).toBe(true);
      expect(radioSVG[0].classList.contains('unSelectable')).toBe(true);
      expect(radioSVG[1].classList.contains('hasConfirm')).toBe(false);
      expect(radioSVG[1].classList.contains('unSelectable')).toBe(true);
      expect(radioSVG[2].classList.contains('hasConfirm')).toBe(false);
      expect(radioSVG[2].classList.contains('unSelectable')).toBe(true);
      radioInputs.forEach((radioInput) => {
        expect((radioInput as HTMLInputElement).disabled).toBe(true);
      });
    });

    it(`'Advanced' mode renders a confirmation pop up, emits new value when 'Confirm' button is clicked, and finally dismisses the confirmation pop up`, async () => {
      const spy = vi.fn();
      const [option1] = defaultProps.options ?? [];

      const { container } = render(
        <Radio {...defaultProps} confirmationMode="Advanced" onChange={spy} />,
      );

      const radioButton = container.querySelector('input') as HTMLInputElement;
      let confirmation = document.querySelector('.confirm-dialog-container');

      expect(confirmation).not.toBeInTheDocument();

      act(() => {
        fireEvent.click(radioButton);
      });

      await waitFor(() => {
        confirmation = document.querySelector('.confirm-dialog-container');
        expect(confirmation).toBeInTheDocument();
      });

      const confirmButton = document.querySelector(
        '[data-test-id="confirm"]',
      ) as HTMLButtonElement;

      act(() => {
        fireEvent.click(confirmButton);
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalledTimes(1);
        expect(spy).toHaveBeenCalledWith(
          expect.objectContaining({ currentTarget: { value: option1.value } }),
        );
      });

      const radioButtons = container.querySelectorAll('input');

      await waitFor(
        () => {
          confirmation = document.querySelector('.confirm-dialog-container');
          expect(confirmation).not.toBeInTheDocument();
        },
        { timeout: 1000 },
      );

      radioButtons.forEach((radioInput) => {
        expect(
          (radioInput as HTMLInputElement).classList.contains('hasConfirm'),
        ).toBe(false);
      });
    });

    it(`'Advanced' mode renders a confirmation pop up, does not raise the 'onChange' event after the 'Cancel' button is clicked, and finally dismisses the confirmation pop up`, async () => {
      const spy = vi.fn();

      const { container } = render(
        <Radio {...defaultProps} confirmationMode="Advanced" onChange={spy} />,
      );

      const radioButton = container.querySelector('input') as HTMLInputElement;
      let confirmation = document.querySelector('.confirm-dialog-container');

      expect(confirmation).not.toBeInTheDocument();

      act(() => {
        fireEvent.click(radioButton);
      });

      await waitFor(() => {
        confirmation = document.querySelector('.confirm-dialog-container');
        expect(confirmation).toBeInTheDocument();
      });

      const cancelButton = document.querySelector(
        '[data-test-id="cancel"]',
      ) as HTMLButtonElement;

      act(() => {
        fireEvent.click(cancelButton);
      });

      await waitFor(() => {
        expect(spy).not.toHaveBeenCalled();
      });

      const radioButtons = container.querySelectorAll('input');

      await waitFor(
        () => {
          confirmation = document.querySelector('.confirm-dialog-container');
          expect(confirmation).not.toBeInTheDocument();
        },
        { timeout: 1000 },
      );

      radioButtons.forEach((radioInput) => {
        expect(
          (radioInput as HTMLInputElement).classList.contains('hasConfirm'),
        ).toBe(false);
      });
    });

    it(`emits 'onConfirmOpen' if set`, async () => {
      const spy = vi.fn();

      const { container } = render(
        <Radio
          {...defaultProps}
          confirmationMode="Advanced"
          confirmationConfig={{ onConfirmOpen: spy }}
        />,
      );

      const radioButton = container.querySelector('input') as HTMLInputElement;

      act(() => {
        fireEvent.click(radioButton);
      });

      await waitFor(() => {
        const confirmation = document.querySelector(
          '.confirm-dialog-container',
        );
        expect(confirmation).toBeInTheDocument();
      });

      expect(spy).toHaveBeenCalledTimes(1);
      expect(spy).toHaveBeenNthCalledWith(1, true);

      const cancelButton = document.querySelector(
        '[data-test-id="cancel"]',
      ) as HTMLButtonElement;

      act(() => {
        fireEvent.click(cancelButton);
      });

      await waitFor(() => {
        expect(spy).toHaveBeenCalledTimes(2);
      });

      expect(spy).toHaveBeenNthCalledWith(2, false);
    });
  });
});
