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 { ToggleButton } from './ToggleButton';

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

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

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

  it(`'value' is 'false' by default and the 'OFF' button has the 'active' class`, () => {
    const { container } = render(<ToggleButton name="test-name" />);

    const buttons = container.querySelectorAll('div');
    const offButton = container.querySelector('.off');
    const onButton = container.querySelector('.on');

    expect(offButton?.classList.contains('active')).toBe(true);
    expect(onButton?.classList.contains('active')).toBe(false);
  });

  it(`when 'value' is to 'true', the 'ON' button has the 'active' class`, () => {
    const { container } = render(
      <ToggleButton name="test-name" value={true} />,
    );

    const offButton = container.querySelector('.off');
    const onButton = container.querySelector('.on');

    expect(offButton?.classList.contains('active')).toBe(false);
    expect(onButton?.classList.contains('active')).toBe(true);
  });

  it(`'value' updates current active state`, () => {
    const { container, rerender } = render(
      <ToggleButton name="test-name" value={true} />,
    );

    let offButton = container.querySelector('.off');
    let onButton = container.querySelector('.on');

    expect(offButton?.classList.contains('active')).toBe(false);
    expect(onButton?.classList.contains('active')).toBe(true);

    rerender(<ToggleButton name="test-name" value={false} />);

    offButton = container.querySelector('.off');
    onButton = container.querySelector('.on');

    expect(offButton?.classList.contains('active')).toBe(true);
    expect(onButton?.classList.contains('active')).toBe(false);

    rerender(<ToggleButton name="test-name" value={true} />);

    offButton = container.querySelector('.off');
    onButton = container.querySelector('.on');

    expect(offButton?.classList.contains('active')).toBe(false);
    expect(onButton?.classList.contains('active')).toBe(true);
  });

  it(`raises the onButtonClicked event with 'false' when button is toggled 'OFF' and adds the 'active' class`, () => {
    const spy = vi.fn();

    const { container } = render(
      <ToggleButton name="test-name" onButtonClicked={spy} value={true} />,
    );

    const buttons = container.querySelectorAll('button');
    const offButton = buttons[0];

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

    const offDiv = container.querySelector('.off');
    const onDiv = container.querySelector('.on');

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith(expect.any(Object), false);
    expect(offDiv?.classList.contains('active')).toBe(true);
    expect(onDiv?.classList.contains('active')).toBe(false);
  });

  it(`raises the onButtonClicked event with 'true' when the button is toggled 'ON' and adds the 'active' class`, () => {
    const spy = vi.fn();

    const { container } = render(
      <ToggleButton name="test-name" onButtonClicked={spy} value={false} />,
    );

    const button = container.querySelector('button') as HTMLButtonElement;
    let onDiv = container.querySelector('.on');
    let offDiv = container.querySelector('.off');

    expect(onDiv?.classList.contains('active')).toBe(false);

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

    onDiv = container.querySelector('.on');
    offDiv = container.querySelector('.off');

    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith(expect.any(Object), true);
    expect(onDiv?.classList.contains('active')).toBe(true);
    expect(offDiv?.classList.contains('active')).toBe(false);
  });

  it('creates a class based off of the className prop', () => {
    const mockClassName = 'test-class';
    const { container } = render(
      <ToggleButton name="test-name" className={mockClassName} />,
    );

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

    expect(containerDiv?.classList.contains(mockClassName)).toBe(true);
  });

  it('accepts both height and width', () => {
    const mockHeight = '75px';
    const mockWidth = '95px';
    const { container } = render(
      <ToggleButton name="test-name" height={mockHeight} width={mockWidth} />,
    );

    const toggle = container.querySelector('.toggle') as HTMLElement;

    expect(toggle.style.height).toBe(mockHeight);
    expect(toggle.style.width).toBe(mockWidth);
  });

  it('button is enabled by default', () => {
    const spy = vi.fn();
    const { container } = render(
      <ToggleButton name="test-name" onButtonClicked={spy} />,
    );

    const button = container.querySelector('button') as HTMLButtonElement;

    fireEvent.click(button);
    expect(button.disabled).toBe(false);

    expect(spy).toHaveBeenCalledTimes(1);
  });

  it('button can be disabled', () => {
    const spy = vi.fn();
    const { container } = render(
      <ToggleButton name="test-name" onButtonClicked={spy} disabled={true} />,
    );

    const button = container.querySelector('button') as HTMLButtonElement;
    fireEvent.click(button);

    expect(button.disabled).toBe(true);
    expect(spy).not.toHaveBeenCalled();
  });

  it(`button 'type' must be 'button' by default`, () => {
    const { container } = render(<ToggleButton name="test-name" />);

    const button = container.querySelector('button') as HTMLButtonElement;

    expect(button.type).toBe('button');
  });

  it('accepts type html attribute and height style', () => {
    const mockType = 'submit';
    const mockHeight = '75px';

    const { container, rerender } = render(<ToggleButton name="test-name" />);

    let button = container.querySelector('button') as HTMLButtonElement;
    expect(button.style.height).toBe('');
    expect(button.type).toBe('button');

    rerender(
      <ToggleButton name="test-name" type={mockType} height={mockHeight} />,
    );

    button = container.querySelector('button') as HTMLButtonElement;
    expect(button.style.height).toBe(mockHeight);
    expect(button.type).toBe(mockType);
  });

  it(`renders 'OFF' text by default when button is not selected`, () => {
    const { container } = render(<ToggleButton name="test-name" />);

    const offText = container.querySelectorAll('.svgText');

    expect(offText.length).toBeGreaterThan(0);
  });

  it(`does not render 'OFF' text if 'showONOFFText' is false`, () => {
    const { container } = render(
      <ToggleButton name="test-name" showONOFFText={false} />,
    );

    const offText = container.querySelector('.svgText');

    expect(offText).not.toBeInTheDocument();
  });

  it(`renders 'ON' text by default when button is selected`, () => {
    const { container } = render(<ToggleButton name="test-name" />);

    const onText = container.querySelectorAll('.svgText');

    expect(onText.length).toBeGreaterThan(0);
  });

  it(`does not render 'ON' text if 'showONOFFText' is false`, () => {
    const { container } = render(
      <ToggleButton name="test-name" showONOFFText={false} />,
    );

    const onText = container.querySelector('.svgText');

    expect(onText).not.toBeInTheDocument();
  });

  describe('Confirmation', () => {
    it(`'Advanced' mode changes the background color when clicked for the first time`, () => {
      const { container } = render(
        <ToggleButton name="test-name" confirmationMode="Advanced" />,
      );

      let button = container.querySelector('button') as HTMLButtonElement;

      expect(button.classList.contains('hasConfirm')).toBe(false);

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

      button = container.querySelector('button') as HTMLButtonElement;

      expect(button.classList.contains('hasConfirm')).toBe(true);
    });

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

      const { container } = render(
        <ToggleButton
          name="test-name"
          confirmationMode="Advanced"
          onButtonClicked={spy}
        />,
      );

      let toggle = container.querySelector('button') as HTMLButtonElement;
      let confirmation = document.querySelector('.confirm-dialog-container');

      expect(confirmation).not.toBeInTheDocument();

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

      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.any(Object), true);
      });

      toggle = container.querySelector('button') as HTMLButtonElement;

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

      expect(toggle.classList.contains('hasConfirm')).toBe(false);
    });

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

      const { container } = render(
        <ToggleButton
          name="test-name"
          confirmationMode="Advanced"
          onButtonClicked={spy}
        />,
      );

      let toggle = container.querySelector('button') as HTMLButtonElement;
      let confirmation = document.querySelector('.confirm-dialog-container');

      expect(confirmation).not.toBeInTheDocument();

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

      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();
      });

      toggle = container.querySelector('button') as HTMLButtonElement;

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

      expect(toggle.classList.contains('hasConfirm')).toBe(false);
    });

    it(`emits 'onConfirmOpen' if set`, async () => {
      const spy = vi.fn();
      const { container } = render(
        <ToggleButton
          name="test-name"
          confirmationMode="Advanced"
          confirmationConfig={{ onConfirmOpen: spy }}
        />,
      );

      const button = container.querySelector('button') as HTMLButtonElement;

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

      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);
    });
  });
});
