import React, { useEffect, useRef } from 'react';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';

import Tour from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, screen } from '../../../tests/utils';
import type { TourProps } from '../interface';

const mockBtnRect = (
  rect: { x: number; y: number; width: number; height: number },
  scrollIntoViewCb?: () => void,
) => {
  spyElementPrototypes(HTMLButtonElement, {
    getBoundingClientRect: {
      get(): any {
        return () => ({ ...rect, left: rect.x, top: rect.y });
      },
    },
    scrollIntoView: {
      get(): any {
        scrollIntoViewCb?.();
        return (val: boolean | ScrollIntoViewOptions) => val;
      },
    },
  });
};
describe('Tour', () => {
  mountTest(Tour);
  rtlTest(Tour);

  it('single', () => {
    const App: React.FC = () => {
      const coverBtnRef = useRef<HTMLButtonElement>(null);
      return (
        <>
          <button disabled ref={coverBtnRef} type="button">
            Cover
          </button>
          <Tour
            steps={[
              {
                title: 'cover title',
                description: 'cover description.',
                target: () => coverBtnRef.current!,
              },
            ]}
          />
        </>
      );
    };
    const { getByText, baseElement } = render(<App />);
    expect(getByText('cover title')).toBeTruthy();
    expect(getByText('cover description.')).toBeTruthy();
    expect(baseElement).toMatchSnapshot();
  });

  it('steps is empty', () => {
    const App: React.FC = () => {
      const coverBtnRef = useRef<HTMLButtonElement>(null);
      return (
        <>
          <button disabled ref={coverBtnRef} type="button">
            Cover
          </button>
          <Tour steps={[]} />
          <Tour />
        </>
      );
    };
    const { baseElement } = render(<App />);
    expect(baseElement).toMatchSnapshot();
  });

  it('steps props indicatorsRender', () => {
    const onClickMock = jest.fn();
    const indicatorsRenderMock = jest.fn();
    const App: React.FC = () => {
      const coverBtnRef = useRef<HTMLButtonElement>(null);
      return (
        <>
          <button disabled ref={coverBtnRef} type="button">
            Cover
          </button>
          <Tour
            type="default"
            indicatorsRender={indicatorsRenderMock}
            steps={[
              {
                title: 'With Cover',
                nextButtonProps: {
                  onClick: onClickMock,
                },
              },
              {
                title: 'With Cover',
                nextButtonProps: {
                  onClick: onClickMock,
                },
                prevButtonProps: {
                  onClick: onClickMock,
                },
              },
              {
                title: 'With Cover',
                prevButtonProps: {
                  onClick: onClickMock,
                },
                nextButtonProps: {
                  onClick: onClickMock,
                },
              },
            ]}
          />
        </>
      );
    };
    const { baseElement } = render(<App />);
    fireEvent.click(screen.getByRole('button', { name: 'Next' }));
    fireEvent.click(screen.getByRole('button', { name: 'Previous' }));
    fireEvent.click(screen.getByRole('button', { name: 'Next' }));
    fireEvent.click(screen.getByRole('button', { name: 'Next' }));
    fireEvent.click(screen.getByRole('button', { name: 'Finish' }));
    expect(onClickMock).toHaveBeenCalledTimes(5);
    expect(baseElement).toMatchSnapshot();
  });

  it('button props onClick', () => {
    const App: React.FC = () => {
      const coverBtnRef = useRef<HTMLButtonElement>(null);
      const [btnName, steBtnName] = React.useState<string>('defaultBtn');
      return (
        <>
          <span id="btnName">{btnName}</span>
          <button disabled ref={coverBtnRef} type="button">
            target
          </button>

          <Tour
            steps={[
              {
                title: '',
                description: '',
                target: () => coverBtnRef.current!,
                nextButtonProps: {
                  onClick: () => steBtnName('nextButton'),
                },
              },
              {
                title: '',
                target: () => coverBtnRef.current!,
                prevButtonProps: {
                  onClick: () => steBtnName('prevButton'),
                },
                nextButtonProps: {
                  onClick: () => steBtnName('finishButton'),
                },
              },
            ]}
          />
        </>
      );
    };
    const { baseElement } = render(<App />);
    expect(baseElement.querySelector('#btnName')).toHaveTextContent('defaultBtn');
    fireEvent.click(screen.getByRole('button', { name: 'Next' }));
    expect(baseElement.querySelector('#btnName')).toHaveTextContent('nextButton');
    fireEvent.click(screen.getByRole('button', { name: 'Previous' }));
    expect(baseElement.querySelector('#btnName')).toHaveTextContent('prevButton');
    fireEvent.click(screen.getByRole('button', { name: 'Next' }));
    fireEvent.click(screen.getByRole('button', { name: 'Finish' }));
    expect(baseElement.querySelector('#btnName')).toHaveTextContent('finishButton');
    expect(baseElement).toMatchSnapshot();
  });

  it('Primary', () => {
    const App: React.FC = () => {
      const coverBtnRef = useRef<HTMLButtonElement>(null);
      return (
        <>
          <button disabled ref={coverBtnRef} type="button">
            Cover
          </button>

          <Tour
            type="primary"
            steps={[
              {
                title: 'primary title',
                description: 'primary description.',
                target: () => coverBtnRef.current!,
              },
            ]}
          />
        </>
      );
    };
    const { getByText, baseElement } = render(<App />);
    expect(getByText('primary description.')).toBeTruthy();
    expect(baseElement.querySelector('.ant-tour-content')?.parentElement).toHaveClass(
      'ant-tour-primary',
    );
    expect(baseElement).toMatchSnapshot();
  });

  it('step support Primary', () => {
    const App: React.FC = () => {
      const coverBtnRef = useRef<HTMLButtonElement>(null);
      return (
        <>
          <button disabled ref={coverBtnRef} type="button">
            Cover
          </button>

          <Tour
            type="default"
            steps={[
              {
                title: 'cover title',
                description: 'cover description.',
                target: () => coverBtnRef.current!,
              },
              {
                title: 'primary title',
                description: 'primary description.',
                target: () => coverBtnRef.current!,
                type: 'primary',
              },
            ]}
          />
        </>
      );
    };
    const { getByText, container, baseElement } = render(<App />);
    expect(getByText('cover description.')).toBeTruthy();
    expect(container.querySelector('.ant-tour-primary .ant-tour-content')).toBeFalsy();
    fireEvent.click(screen.getByRole('button', { name: 'Next' }));
    expect(getByText('primary description.')).toBeTruthy();
    expect(container.querySelector('.ant-tour-primary .ant-tour-content')).toBeTruthy();
    expect(baseElement).toMatchSnapshot();
  });

  it('basic', () => {
    const App: React.FC = () => {
      const coverBtnRef = useRef<HTMLButtonElement>(null);
      const placementBtnRef = useRef<HTMLButtonElement>(null);

      const [show, setShow] = React.useState<boolean>();

      useEffect(() => {
        if (show === false) {
          setShow(true);
        }
      }, [show]);

      return (
        <>
          <div>
            <button
              type="button"
              onClick={() => {
                setShow(false);
              }}
            >
              Show
            </button>
            <button disabled ref={coverBtnRef} type="button">
              Cover
            </button>
            <button disabled ref={placementBtnRef} type="button">
              Placement
            </button>
          </div>

          {show && (
            <Tour
              steps={[
                {
                  title: 'Show in Center',
                  description: 'Here is the content of Tour.',
                  target: null,
                },
                {
                  title: 'With Cover',
                  description: 'Here is the content of Tour.',
                  target: () => coverBtnRef.current!,
                  cover: (
                    <img
                      alt="tour.png"
                      src="https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png"
                    />
                  ),
                },
                {
                  title: 'Adjust Placement',
                  description: 'Here is the content of Tour which show on the right.',
                  placement: 'right',
                  target: () => placementBtnRef.current!,
                },
              ]}
            />
          )}
        </>
      );
    };
    const { getByText, container, baseElement } = render(<App />);
    fireEvent.click(screen.getByRole('button', { name: 'Show' }));
    expect(getByText('Show in Center')).toBeTruthy();
    fireEvent.click(screen.getByRole('button', { name: 'Next' }));
    expect(getByText('Here is the content of Tour.')).toBeTruthy();
    fireEvent.click(screen.getByRole('button', { name: 'Next' }));
    expect(getByText('Adjust Placement')).toBeTruthy();
    fireEvent.click(screen.getByRole('button', { name: 'Finish' }));
    expect(container.querySelector('.ant-tour')).toBeFalsy();
    expect(baseElement).toMatchSnapshot();
  });

  it('panelRender should correct render when total is undefined or null', () => {
    [undefined, null].forEach((total: any) => {
      const { container } = render(<Tour open steps={[{ title: <div>test</div>, total }]} />);
      expect(
        container.querySelector<HTMLDivElement>('.ant-tour-content .ant-tour-indicators'),
      ).toBeFalsy();
    });
  });

  it('panelRender should correct render when title is undefined or null', () => {
    [undefined, null].forEach((title) => {
      const { container } = render(<Tour open steps={[{ title, total: 1 }]} />);
      expect(
        container.querySelector<HTMLDivElement>('.ant-tour-content .ant-tour-header'),
      ).toBeFalsy();
    });
  });

  it('custom step pre btn & next btn className & style', () => {
    const App: React.FC = () => (
      <Tour
        steps={[
          {
            title: 'Show in Center',
            description: 'Here is the content of Tour.',
            nextButtonProps: {
              className: 'customClassName',
              style: {
                backgroundColor: 'rgb(69,69,255)',
              },
            },
          },
          {
            title: 'With Cover',
            description: 'Here is the content of Tour.',
            cover: (
              <img
                alt="tour.png"
                src="https://user-images.githubusercontent.com/5378891/197385811-55df8480-7ff4-44bd-9d43-a7dade598d70.png"
              />
            ),
          },
        ]}
      />
    );

    const { container } = render(<App />);
    // className
    expect(screen.getByRole('button', { name: 'Next' }).className.includes('customClassName')).toBe(
      true,
    );
    // style
    expect(screen.getByRole('button', { name: 'Next' }).style.backgroundColor).toEqual(
      'rgb(69, 69, 255)',
    );
    expect(container.firstChild).toMatchSnapshot();
  });

  it('custom indicator', () => {
    const steps: TourProps['steps'] = [
      {
        title: 'Upload File',
        description: 'Put your files here.',
      },
      {
        title: 'Save',
        description: 'Save your changes.',
      },
      {
        title: 'Other Actions',
        description: 'Click to see other actions.',
      },
    ];
    const App: React.FC = () => (
      <Tour
        open
        steps={steps}
        indicatorsRender={(current, total) => (
          <span className="custom-indicator">
            {current + 1} / {total}
          </span>
        )}
      />
    );
    const { container } = render(<App />);
    expect(container.querySelector<HTMLSpanElement>('.custom-indicator')).toBeTruthy();
  });

  it('controlled current', () => {
    const App: React.FC = () => {
      const [current, setCurrent] = React.useState(0);
      return (
        <>
          <div>
            <button
              type="button"
              onClick={() => {
                setCurrent(1);
              }}
            >
              SetCurrent
            </button>
          </div>

          <Tour
            open
            current={current}
            steps={[
              {
                title: 'Show in Center',
                description: 'Here is the content of Tour.',
              },
              {
                title: 'Primary title',
                description: 'Primary description.',
                type: 'primary',
              },
            ]}
            onChange={setCurrent}
          />
        </>
      );
    };
    const { getByText, container, baseElement } = render(<App />);

    fireEvent.click(screen.getByRole('button', { name: 'SetCurrent' }));
    expect(getByText('Primary description.')).toBeTruthy();
    expect(container.querySelector('.ant-tour-primary .ant-tour-content')).toBeTruthy();
    expect(baseElement).toMatchSnapshot();
  });

  it('support closeIcon', () => {
    const Demo = ({ closeIcon = false }: { closeIcon?: React.ReactNode }) => {
      const createBtnRef = useRef<HTMLButtonElement>(null);
      const updateBtnRef = useRef<HTMLButtonElement>(null);
      const deleteBtnRef = useRef<HTMLButtonElement>(null);
      return (
        <div style={{ margin: 20 }}>
          <div>
            <button type="button" ref={createBtnRef}>
              Create
            </button>
            <div style={{ height: 200 }} />
            <button type="button" ref={updateBtnRef}>
              Update
            </button>
            <button type="button" ref={deleteBtnRef}>
              Delete
            </button>
          </div>
          <div style={{ height: 200 }} />

          <Tour
            closeIcon={closeIcon}
            steps={[
              {
                title: '创建',
                description: '创建一条数据',
                target: () => createBtnRef.current!,
                mask: true,
              },
              {
                title: '更新',
                closeIcon: !closeIcon,
                description: (
                  <div>
                    <span>更新一条数据</span>
                    <button type="button">帮助文档</button>
                  </div>
                ),
                target: () => updateBtnRef.current!,
              },
              {
                title: '删除',
                closeIcon: <span className="custom-del-close-icon">Close</span>,
                description: (
                  <div>
                    <span>危险操作:删除一条数据</span>
                    <button type="button">帮助文档</button>
                  </div>
                ),
                target: () => deleteBtnRef.current!,
              },
            ]}
          />
        </div>
      );
    };

    const { baseElement, rerender } = render(<Demo />);
    const resetIndex = () => {
      // reset
      fireEvent.click(baseElement.querySelector('.ant-tour-prev-btn')!);
      fireEvent.click(baseElement.querySelector('.ant-tour-prev-btn')!);
    };
    expect(baseElement.querySelector('.ant-tour-close')).toBeFalsy();
    fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!);
    expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy();
    expect(baseElement.querySelector('.ant-tour-close-icon')).toBeTruthy();
    fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!);
    expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy();
    expect(baseElement.querySelector('.ant-tour-close-icon')).toBeFalsy();
    expect(baseElement.querySelector('.custom-del-close-icon')).toBeTruthy();

    resetIndex();

    rerender(<Demo closeIcon />);
    expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy();
    expect(baseElement.querySelector('.ant-tour-close-icon')).toBeTruthy();
    fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!);
    expect(baseElement.querySelector('.ant-tour-close')).toBeFalsy();
    expect(baseElement.querySelector('.ant-tour-close-icon')).toBeFalsy();
    fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!);
    expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy();
    expect(baseElement.querySelector('.ant-tour-close-icon')).toBeFalsy();
    expect(baseElement.querySelector('.custom-del-close-icon')).toBeTruthy();

    resetIndex();

    rerender(<Demo closeIcon={<span className="custom-global-close-icon">X</span>} />);
    expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy();
    expect(baseElement.querySelector('.custom-global-close-icon')).toBeTruthy();
    fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!);
    expect(baseElement.querySelector('.ant-tour-close')).toBeFalsy();
    expect(baseElement.querySelector('.ant-tour-close-icon')).toBeFalsy();
    expect(baseElement.querySelector('.custom-global-close-icon')).toBeFalsy();
    fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!);
    expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy();
    expect(baseElement.querySelector('.ant-tour-close-icon')).toBeFalsy();
    expect(baseElement.querySelector('.custom-del-close-icon')).toBeTruthy();

    resetIndex();
  });

  it('first step should be primary', () => {
    const App: React.FC = () => {
      const coverBtnRef = useRef<HTMLButtonElement>(null);
      return (
        <>
          <button ref={coverBtnRef} type="button">
            target
          </button>

          <Tour
            steps={[
              {
                title: '',
                description: '',
                target: () => coverBtnRef.current!,
                type: 'primary',
                className: 'should-be-primary',
              },
              {
                title: '',
                target: () => coverBtnRef.current!,
              },
            ]}
          />
        </>
      );
    };

    render(<App />);
    fireEvent.click(screen.getByRole('button', { name: 'target' }));
    expect(document.querySelector('.should-be-primary')).toBeTruthy();
    expect(document.querySelector('.should-be-primary')).toHaveClass('ant-tour-primary');
  });

  // https://github.com/ant-design/ant-design/issues/49117
  it('onClose current is correct', () => {
    const onClose = jest.fn();
    const { container } = render(
      <Tour
        onClose={onClose}
        open
        steps={[
          {
            title: '',
            description: '',
            type: 'primary',
            className: 'should-be-primary',
          },
          {
            title: '',
          },
        ]}
      />,
    );
    fireEvent.click(container.querySelector('.ant-tour-next-btn')!);
    fireEvent.click(container.querySelector('.ant-tour-close-icon')!);
    expect(onClose).toHaveBeenLastCalledWith(1);
  });

  it('should support gap.radius', () => {
    const App: React.FC<{ gap: TourProps['gap'] }> = ({ gap }) => {
      const ref = useRef<HTMLButtonElement>(null);
      const [show, setShow] = React.useState<boolean>();
      const steps: TourProps['steps'] = [
        {
          title: 'Show in Center',
          description: 'Here is the content of Tour.',
          target: () => ref.current!,
        },
      ];
      return (
        <>
          <button type="button" onClick={() => setShow(true)} ref={ref}>
            Show
          </button>

          <Tour open={show} steps={steps} gap={gap} />
        </>
      );
    };
    const { rerender, baseElement } = render(<App gap={{ radius: 4 }} />);
    fireEvent.click(screen.getByRole('button', { name: 'Show' }));

    expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toBeTruthy();
    expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute('rx', '4');
    rerender(<App gap={{ radius: 0 }} />);
    fireEvent.click(screen.getByRole('button', { name: 'Show' }));
    expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toBeTruthy();
    expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute('rx', '0');
  });
  it('should support gap.offset', () => {
    const gap = { offset: 10 };
    const pos = { x: 100, y: 200, width: 230, height: 180 };
    mockBtnRect(pos);
    const App: React.FC = () => {
      const ref = useRef<HTMLButtonElement>(null);
      const [show, setShow] = React.useState<boolean>();
      const steps: TourProps['steps'] = [
        {
          title: 'Show in Center',
          description: 'Here is the content of Tour.',
          target: () => ref.current!,
        },
      ];

      return (
        <>
          <button type="button" onClick={() => setShow(true)} ref={ref}>
            Show
          </button>

          <Tour steps={steps} gap={gap} open={show} />
        </>
      );
    };

    const { baseElement } = render(<App />);
    const targetBtn = screen.getByRole('button', { name: 'Show' });
    fireEvent.click(targetBtn);

    expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute(
      'width',
      String(pos.width + gap.offset * 2),
    );
    expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute(
      'height',
      String(pos.height + gap.offset * 2),
    );
    expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute(
      'x',
      String(pos.x - gap.offset),
    );
    expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute(
      'y',
      String(pos.y - gap.offset),
    );

    expect(baseElement).toMatchSnapshot();
  });
  // This test is for PurePanel which means safe to remove.
  describe('PurePanel', () => {
    const PurePanel = Tour._InternalPanelDoNotUseOrYouWillBeFired;

    it('closeIcon', () => {
      const { container } = render(
        <PurePanel
          closeIcon={[
            <span className="bamboo" key="bamboo" />,
            <span className="little" key="little" />,
          ]}
          title="a"
        />,
      );

      expect(container.querySelector('.bamboo')).toBeTruthy();
      expect(container.querySelector('.little')).toBeTruthy();
    });
  });
});
