import { fireEvent, render, waitFor } from '@testing-library/react';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { BrowserRouter as Router } from 'react-router-dom';
import { describe, expect, it, vi } from 'vitest';
import { noop } from '../../../helpers/utils';
import { ConfirmationConfig } from '../../ConfirmDialog';
import { IconName } from '../../Icons';
import {
  PageHeaderAction,
  isPageHeaderJsAction,
  isPageHeaderNavigationAction,
} from './PageHeaderAction';
import {
  PageHeaderActionProps,
  PageHeaderActionType,
} from './PageHeaderAction.model';

const defaultProps: PageHeaderActionProps = {
  label: 'action-label',
  onClick: noop,
};

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

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

    it('renders a label', () => {
      const mockLabel = 'test-label';
      const { container } = render(
        <PageHeaderAction {...defaultProps} label={mockLabel} />,
      );

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

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

    it('renders a Icon, if set', () => {
      const mockLabel = 'test-label';
      const { container } = render(
        <PageHeaderAction
          {...defaultProps}
          label={mockLabel}
          icon={IconName.ChevronRight}
        />,
      );

      const Icon = container.querySelector('.icons');

      expect(Icon).toBeInTheDocument();
    });

    it('image is used instead of icon if icon prop is a string', () => {
      const mockLabel = 'test-label';
      const { container } = render(
        <PageHeaderAction
          {...defaultProps}
          label={mockLabel}
          icon={'test'}
          imgAlt={'alt'}
        />,
      );

      const Icon = container.querySelector('.icons');

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

    it('raises onActionSelected', () => {
      const spy = vi.fn();
      const { container } = render(
        <PageHeaderAction {...defaultProps} onClick={spy} />,
      );

      const actionContainer = container.querySelector(
        '.container',
      ) as HTMLElement;
      fireEvent.click(actionContainer);

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

    describe('confirmation', () => {
      it(`'Simple' mode changes the background color when clicked for the first time`, () => {
        const spy = vi.fn();
        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={'test-label'}
            confirmationMode="Simple"
            onClick={spy}
          />,
        );
        let actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;
        expect(actionContainer.classList.contains('hasConfirm')).toBe(false);

        fireEvent.click(actionContainer);

        actionContainer = container.querySelector('.container') as HTMLElement;

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

      it(`'Simple' mode renders a confirmation message, raises the 'onClick' event after confirmation, and resets the action label`, () => {
        const mockLabel = 'test-label';
        const confirmationMsg = 'Click again to confirm';
        const spy = vi.fn();
        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={mockLabel}
            confirmationMode="Simple"
            onClick={spy}
          />,
        );

        let span = container.querySelector('span') as HTMLElement;

        expect(span.textContent).toBe(mockLabel);

        const actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;
        fireEvent.click(actionContainer);

        span = container.querySelector('span') as HTMLElement;

        expect(span.textContent).toBe(confirmationMsg);

        fireEvent.click(actionContainer);

        span = container.querySelector('span') as HTMLElement;

        expect(spy).toHaveBeenCalledTimes(1);
        expect(span.textContent).toBe(mockLabel);
      });

      it(`'Simple' mode renders a confirmation message, and doesn't raise the 'onClick' event on 'mouseleave' when confirmation is required`, async () => {
        const mockLabel = 'test-label';
        const confirmationMsg = 'Click again to confirm';
        const spy = vi.fn();
        vi.useFakeTimers();

        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={mockLabel}
            confirmationMode="Simple"
            onClick={spy}
          />,
        );

        const actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;

        await act(async () => {
          fireEvent.click(actionContainer);
        });

        let span = container.querySelector('span') as HTMLElement;
        let containerElement = container.querySelector(
          '.container',
        ) as HTMLElement;

        expect(containerElement.classList.contains('hasConfirm')).toBe(true);
        expect(span.textContent).toBe(confirmationMsg);
        expect(spy).not.toHaveBeenCalled();

        await act(async () => {
          fireEvent.mouseLeave(actionContainer);
        });

        await act(async () => {
          vi.runAllTimers();
        });

        span = container.querySelector('span') as HTMLElement;
        containerElement = container.querySelector('.container') as HTMLElement;

        expect(span.textContent).toBe(mockLabel);
        expect(spy).not.toHaveBeenCalled();
        expect(containerElement.classList.contains('hasConfirm')).toBe(false);
      });

      it(`'Advanced' mode changes the background color when clicked for the first time`, async () => {
        const spy = vi.fn();
        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={'test-label'}
            confirmationMode="Advanced"
            onClick={spy}
          />,
        );
        let actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;
        expect(actionContainer.classList.contains('hasConfirm')).toBe(false);

        await act(async () => {
          fireEvent.click(actionContainer);
        });

        actionContainer = container.querySelector('.container') as HTMLElement;

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

      it(`'Advanced' mode confirmation pop up, raises the 'onClick' event after the 'Confirm' button is clicked, and finally dismisses the confirmation pop up`, async () => {
        const spy = vi.fn();
        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={'test-label'}
            confirmationMode="Advanced"
            onClick={spy}
          />,
        );
        let confirmation = container.querySelector('.confirmDialog');

        expect(confirmation).not.toBeInTheDocument();

        const actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;

        await act(async () => {
          fireEvent.click(actionContainer);
        });

        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(
          () => {
            confirmation = document.querySelector('.confirm-dialog-container');
            expect(confirmation).not.toBeInTheDocument();
          },
          { timeout: 1000 },
        );

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

      it(`'Advanced' mode confirmation pop up, does not raise the 'onClick' event after the 'Cancel' button is clicked, and finally dismisses the confirmation pop up`, async () => {
        const spy = vi.fn();
        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={'test-label'}
            confirmationMode="Advanced"
            onClick={spy}
          />,
        );
        let confirmation = container.querySelector('.confirmDialog');

        expect(confirmation).not.toBeInTheDocument();

        const actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;

        await act(async () => {
          fireEvent.click(actionContainer);
        });

        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(
          () => {
            confirmation = document.querySelector('.confirm-dialog-container');
            expect(confirmation).not.toBeInTheDocument();
          },
          { timeout: 1000 },
        );

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

      it(`'Advanced' mode renders a confirmation pop up, does not raise the 'onClick' event when the mouse leaves component, and finally dismisses the confirmation pop up`, async () => {
        const spy = vi.fn();
        vi.useFakeTimers();

        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={'test-label'}
            confirmationMode="Advanced"
            onClick={spy}
          />,
        );
        let confirmation = container.querySelector('.confirmDialog');

        expect(confirmation).not.toBeInTheDocument();

        const actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;

        await act(async () => {
          fireEvent.click(actionContainer);
        });

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

        await act(async () => {
          fireEvent.mouseLeave(actionContainer);
        });

        await act(async () => {
          vi.runAllTimers();
        });

        confirmation = container.querySelector('.confirmDialog');

        // TODO: Debug why this isn't passing
        // expect(confirmation).not.toBeInTheDocument();
        expect(spy).not.toHaveBeenCalled();
      });

      it(`'Advanced' mode accepts a 'confirmationConfig' object`, async () => {
        const mockConfig: ConfirmationConfig = {
          title: 'test-title',
          body: 'test-body',
          cancelButtonText: 'test-cancel',
          confirmButtonText: 'test-confirm',
        };

        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={'test-label'}
            confirmationMode="Advanced"
            confirmationConfig={mockConfig}
          />,
        );

        const actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;

        await act(async () => {
          fireEvent.click(actionContainer);
        });

        await waitFor(() => {
          const confirmation = document.querySelector(
            '.confirm-dialog-container',
          );
          expect(confirmation).toBeInTheDocument();
          // Check rendered content instead of props
          expect(container.textContent).toContain(mockConfig.title);
          expect(container.textContent).toContain(mockConfig.body);
          expect(container.textContent).toContain(mockConfig.cancelButtonText);
          expect(container.textContent).toContain(mockConfig.confirmButtonText);
        });
      });

      it(`'Simple' raises onConfirmOpen if set`, () => {
        const confirmToggleSpy = vi.fn();

        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={'test-label'}
            confirmationMode="Simple"
            confirmationConfig={{ onConfirmOpen: confirmToggleSpy }}
          />,
        );

        const actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;

        // toggle confirmation on
        act(() => {
          fireEvent.click(actionContainer);
        });

        expect(confirmToggleSpy).toHaveBeenCalledTimes(1);

        // toggle confirmation off
        act(() => {
          fireEvent.click(actionContainer);
        });

        expect(confirmToggleSpy).toHaveBeenCalledTimes(2);
        expect(confirmToggleSpy).toHaveBeenNthCalledWith(1, true, {
          mode: 'Simple',
        });
        expect(confirmToggleSpy).toHaveBeenNthCalledWith(2, false, {
          mode: 'Simple',
        });
      });

      it(`'Simple' raises onConfirmOpen when 'mouseleave' if set`, async () => {
        const confirmToggleSpy = vi.fn();

        vi.useFakeTimers();

        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={'test-label'}
            confirmationMode="Simple"
            confirmationConfig={{ onConfirmOpen: confirmToggleSpy }}
          />,
        );

        const actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;

        await act(async () => {
          fireEvent.click(actionContainer);
        });

        await act(async () => {
          fireEvent.mouseLeave(actionContainer);
        });

        await act(async () => {
          vi.runAllTimers();
        });

        expect(confirmToggleSpy).toHaveBeenCalledTimes(2);
        expect(confirmToggleSpy).toHaveBeenNthCalledWith(1, true, {
          mode: 'Simple',
        });
        expect(confirmToggleSpy).toHaveBeenNthCalledWith(2, false, {
          mode: 'Simple',
        });
      });

      it(`'Advanced' raises onConfirmOpen when 'Cancel' is clicked, if set`, async () => {
        const confirmToggleSpy = vi.fn();

        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={'test-label'}
            confirmationMode="Advanced"
            confirmationConfig={{ onConfirmOpen: confirmToggleSpy }}
          />,
        );

        const actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;

        // toggle confirmation on
        act(() => {
          fireEvent.click(actionContainer);
        });

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

        expect(confirmToggleSpy).toHaveBeenCalledTimes(1);
        expect(confirmToggleSpy).toHaveBeenNthCalledWith(1, true, {
          mode: 'Advanced',
        });

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

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

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

        expect(confirmToggleSpy).toHaveBeenNthCalledWith(2, false, {
          mode: 'Advanced',
        });
      });

      it(`'Advanced' raises onConfirmOpen when 'Confirm' is clicked, if set`, async () => {
        const confirmToggleSpy = vi.fn();

        const { container } = render(
          <PageHeaderAction
            {...defaultProps}
            label={'test-label'}
            confirmationMode="Advanced"
            confirmationConfig={{ onConfirmOpen: confirmToggleSpy }}
          />,
        );

        const actionContainer = container.querySelector(
          '.container',
        ) as HTMLElement;

        // toggle confirmation on
        act(() => {
          fireEvent.click(actionContainer);
        });

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

        expect(confirmToggleSpy).toHaveBeenCalledTimes(1);
        expect(confirmToggleSpy).toHaveBeenNthCalledWith(1, true, {
          mode: 'Advanced',
        });

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

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

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

        expect(confirmToggleSpy).toHaveBeenNthCalledWith(2, false, {
          mode: 'Advanced',
        });
      });
    });

    it(`sets background color when actionType is 'Context'`, () => {
      const spy = vi.fn();
      const { container } = render(
        <PageHeaderAction
          label={'action-label'}
          actionType={PageHeaderActionType.Context}
          onClick={spy}
        />,
      );

      const actionContainer = container.querySelector(
        '.container',
      ) as HTMLElement;

      expect(actionContainer.classList.contains('context')).toBe(true);
    });

    it(`sets background color when actionType is 'Active'`, () => {
      const spy = vi.fn();
      const { container } = render(
        <PageHeaderAction
          label={'action-label'}
          actionType={PageHeaderActionType.Active}
          onClick={spy}
        />,
      );

      const actionContainer = container.querySelector(
        '.container',
      ) as HTMLElement;

      expect(actionContainer.classList.contains('active')).toBe(true);
    });

    it(`disallows interactions when action is set to disabled`, () => {
      const spy = vi.fn();
      const { container } = render(
        <PageHeaderAction
          label={'action-label'}
          disabled={true}
          onClick={spy}
        />,
      );

      const actionContainer = container.querySelector(
        '.container',
      ) as HTMLElement;
      const disabledAttribute = actionContainer.getAttribute('disabled');

      fireEvent.click(actionContainer);

      expect(disabledAttribute).not.toBeNull();
      expect(spy).not.toHaveBeenCalled();
    });
  });

  describe('PageHeaderNavigationAction', () => {
    const defaultProps = {
      label: 'Action Label',
      path: '/action-path',
    };

    it('renders the component without crashing', () => {
      const { container } = render(
        <Router>
          <PageHeaderAction {...defaultProps} path="/test" />
        </Router>,
      );
      expect(container).toBeTruthy();
    });

    it('renders a Link when path is provided', () => {
      const { container } = render(
        <Router>
          <PageHeaderAction {...defaultProps} />
        </Router>,
      );

      const link = container.querySelector('a');
      expect(link).toBeInTheDocument();
    });

    it('renders a label', () => {
      const mockLabel = 'test-label';
      const { container } = render(
        <Router>
          <PageHeaderAction {...defaultProps} label={mockLabel} path="/test" />
        </Router>,
      );
      const label = container.querySelector('span');
      expect(label?.textContent).toBe(mockLabel);
    });

    it('renders an icon, if set', () => {
      const mockLabel = 'test-label';
      const { container } = render(
        <Router>
          <PageHeaderAction
            {...defaultProps}
            label={mockLabel}
            icon={IconName.ChevronRight}
            path="/test"
          />
        </Router>,
      );
      const icon = container.querySelector('.icons');
      expect(icon).toBeInTheDocument();
    });

    it('renders an image instead of icon if icon prop is a string', () => {
      const mockLabel = 'test-label';
      const { container } = render(
        <Router>
          <PageHeaderAction
            {...defaultProps}
            label={mockLabel}
            icon="test"
            imgAlt="alt"
            path="/test"
          />
        </Router>,
      );
      const icon = container.querySelector('.icons');
      expect(icon).not.toBeInTheDocument();
      const image = container.querySelector('img');
      expect(image).toBeInTheDocument();
    });

    it('renders a link with correct path', () => {
      const mockPath = '/test-path';
      const { container } = render(
        <Router>
          <PageHeaderAction {...defaultProps} path={mockPath} />
        </Router>,
      );
      const link = container.querySelector('a') as HTMLAnchorElement;
      expect(link.getAttribute('href')).toBe(mockPath);
    });

    it('opens link in a new tab if openInNewTab is true', () => {
      const { container } = render(
        <Router>
          <PageHeaderAction {...defaultProps} path="/test" openInNewTab />
        </Router>,
      );
      const link = container.querySelector('a') as HTMLAnchorElement;
      expect(link.target).toBe('_blank');
    });

    it('does not open link in a new tab if openInNewTab is false', () => {
      const { container } = render(
        <Router>
          <PageHeaderAction
            {...defaultProps}
            path="/test"
            openInNewTab={false}
          />
        </Router>,
      );
      const link = container.querySelector('a') as HTMLAnchorElement;
      expect(link.target).toBe('');
    });
  });
});

describe('isPageHeaderNavigationAction', () => {
  it('should return true for a valid PageHeaderNavigationActionProps object', () => {
    const action = { label: 'Navigation Action', path: '/home' };
    expect(isPageHeaderNavigationAction(action)).toBe(true);
  });

  it('should return false for an invalid PageHeaderNavigationActionProps object', () => {
    const action = { label: 'Navigation Action', onClick: noop };
    expect(isPageHeaderNavigationAction(action)).toBe(false);
  });

  it('should return false for a non-PageHeaderNavigationActionProps object', () => {
    const action = { label: 'JS Action', onClick: noop };
    expect(isPageHeaderNavigationAction(action)).toBe(false);
  });
});

describe('isPageHeaderJsAction', () => {
  it('should return true for a valid PageHeaderJsActionProps object', () => {
    const action = { label: 'JS Action', onClick: noop };
    expect(isPageHeaderJsAction(action)).toBe(true);
  });

  it('should return false for an invalid PageHeaderNavigationActionProps object', () => {
    const action = { label: 'JS Action', path: '/home' };
    expect(isPageHeaderJsAction(action)).toBe(false);
  });

  it('should return false in case `onClick` property is set to `undefined`', () => {
    const action = {
      label: 'Navigation Action',
      path: '/home',
      onClick: undefined,
    };
    expect(isPageHeaderJsAction(action)).toBe(false);
  });
});
