import { fireEvent, render, screen } from '@testing-library/react';
import React, { useState } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { ConfirmDialog } from './ConfirmDialog';
import { ConfirmationConfig } from './ConfirmDialog.models';

const TestWrapper: React.FC<{
  onConfirmOpen: ConfirmationConfig['onConfirmOpen'];
}> = ({ onConfirmOpen }) => {
  const [state, setState] = useState<boolean>(false);

  return (
    <div>
      <button data-test-id="test-mount" onClick={() => setState(true)}></button>
      <button
        data-test-id="test-unmount"
        onClick={() => setState(false)}
      ></button>
      {state && <ConfirmDialog onConfirmOpen={onConfirmOpen} />}
    </div>
  );
};

describe('ConfirmDialog', () => {
  it('renders the component without crashing', () => {
    const { container } = render(<ConfirmDialog />);

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

  it('allows an optional class to be passed in', () => {
    const mockClass = 'test-class';
    const { container } = render(<ConfirmDialog className={mockClass} />);

    const dialogContainer = container.querySelector(
      '.confirm-dialog-container',
    );

    expect(dialogContainer?.classList.contains(mockClass)).toBe(true);
  });

  it(`'accepts placement prop`, () => {
    const { container } = render(<ConfirmDialog placement="top" />);

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

  it('disables container click event propagation', () => {
    const parentClickSpy = vi.fn();
    const { container } = render(
      <div onClick={parentClickSpy}>
        <ConfirmDialog />
      </div>,
    );

    const dialogContainer = container.querySelector(
      '.confirm-dialog-container',
    )!;
    fireEvent.click(dialogContainer);

    expect(parentClickSpy).not.toHaveBeenCalled();
  });

  describe('title and body', () => {
    it(`renders a default title and body and allows both props to be passed in`, () => {
      const mockTitle = 'test-title';
      const mockBody = 'test-body';
      const { container, rerender } = render(<ConfirmDialog />);

      let message = screen.getByTestId('message-title');
      let body = container.querySelector('.confirmBodyText');

      expect(message.textContent).toBe('Confirmation needed');
      expect(body?.textContent).toBe(
        'The action is irreversible. Please confirm to proceed.',
      );

      rerender(<ConfirmDialog title={mockTitle}>{mockBody}</ConfirmDialog>);

      message = screen.getByTestId('message-title');
      body = container.querySelector('.confirmBodyText');

      expect(message.textContent).toBe(mockTitle);
      expect(body?.textContent).toBe(mockBody);
    });

    it(`accepts an element`, () => {
      const mockBody: JSX.Element = <span>test-body</span>;
      const { container } = render(<ConfirmDialog>{mockBody}</ConfirmDialog>);

      const body = container.querySelector('.confirmBodyText');

      expect(body?.querySelector('span')?.textContent).toBe('test-body');
    });

    it(`disables 'isBodyPositionAbsolute' in the Message component`, () => {
      const { container } = render(<ConfirmDialog />);

      // Check that the body element doesn't have the absolute class
      const body = container.querySelector('.body');
      expect(body?.classList.contains('absolute')).toBe(false);
    });
  });

  describe('cancel and confirm', () => {
    it(`renders 'Cancel' and 'Confirm' button by default allows both button texts to be passed in`, () => {
      const mockCancelButton = 'test-cancel-button-text';
      const mockConfirmButton = 'test-confirm-button-text';
      const { rerender } = render(<ConfirmDialog />);

      let cancelButton = screen.getByTestId('cancel');
      let confirmButton = screen.getByTestId('confirm');

      expect(cancelButton.textContent).toBe('Cancel');
      expect(confirmButton.textContent).toBe('Confirm');

      rerender(
        <ConfirmDialog
          cancelButtonText={mockCancelButton}
          confirmButtonText={mockConfirmButton}
        />,
      );

      cancelButton = screen.getByTestId('cancel');
      confirmButton = screen.getByTestId('confirm');

      expect(cancelButton.textContent).toBe(mockCancelButton);
      expect(confirmButton.textContent).toBe(mockConfirmButton);
    });

    it(`raises both 'onCancel' and 'onConfirm'`, () => {
      const cancelSpy = vi.fn();
      const confirmSpy = vi.fn();

      render(<ConfirmDialog onCancel={cancelSpy} onConfirm={confirmSpy} />);

      const cancelButton = screen.getByTestId('cancel');
      const confirmButton = screen.getByTestId('confirm');

      fireEvent.click(cancelButton);
      fireEvent.click(confirmButton);

      expect(cancelSpy).toHaveBeenCalledTimes(1);
      expect(confirmSpy).toHaveBeenCalledTimes(1);
    });
  });

  it(`raises 'onConfirmOpen' if set`, () => {
    const spy = vi.fn();
    render(<TestWrapper onConfirmOpen={spy} />);

    const mountButton = screen.getByTestId('test-mount');
    const unmountButton = screen.getByTestId('test-unmount');

    fireEvent.click(mountButton);

    expect(spy).toHaveBeenCalledWith(true);

    fireEvent.click(unmountButton);

    expect(spy).toHaveBeenCalledWith(false);
    expect(spy).toHaveBeenCalledTimes(2);
  });
});
