import React from 'react';
import { render, fireEvent, screen, waitFor } from '@testing-library/react';
import { formatMessage } from '@theorchard/suite-i18n';
import { testComponent } from 'lib/test-utils/common';
import { Pagination } from '../pagination';
import { PaginationProps } from '../types';
import { formatPaginationMessage, getTotalPages } from '../utils';

const ARROW_FIRST = 'SuitePagination-arrow-first';
const ARROW_LAST = 'SuitePagination-arrow-last';
const ARROW_NEXT = 'SuitePagination-arrow-next';
const ARROW_PREV = 'SuitePagination-arrow-prev';

describe('<Pagination>', () => {
    const onChange = vi.fn();
    const onSetPageSize = vi.fn();

    const currentPage = 1;
    const pageSize = 5;
    const totalCount = 100;

    const defaultProps: PaginationProps = {
        totalCount,
        currentPage,
        pageSize,
        onChange,
        onSetPageSize,
    };

    const renderComponent = (props?: Partial<PaginationProps>) =>
        render(<Pagination {...defaultProps} {...props} />);

    test('applies style', () => {
        const { getByTestId } = renderComponent({ style: { marginTop: 10 } });
        expect(getByTestId('SuitePagination')).toHaveStyle({ marginTop: '10px' });
    });

    const renderPopover = async (props: Partial<PaginationProps>) => {
        const mergedProps = {
            ...defaultProps,
            ...props,
        };

        const { getByText, findByText } = renderComponent(mergedProps);

        const message = getByText(
            formatPaginationMessage(
                mergedProps.currentPage,
                mergedProps.pageSize,
                mergedProps.totalCount
            )
        );

        fireEvent.click(message);

        await findByText(formatMessage('pagination_rows_per_page'));
    };

    const testPageSizeSection = (props: Partial<PaginationProps>) => {
        const pageSizeOptions = [5, 10, 50, 100];

        test('renders page size selection in popover', async () => {
            await renderPopover({
                ...props,
                pageSizeOptions,
            });

            pageSizeOptions.forEach((size) => {
                expect(
                    screen.getByText(size, {
                        selector: '.SuitePaginationPopover-page-size .btn',
                    })
                ).toBeInTheDocument();
            });
        });

        test('invokes "onSetPageSize" handler when page size buttons are clicked', async () => {
            await renderPopover({
                ...props,
                pageSizeOptions,
            });

            const btn = screen.getByText('100', {
                selector: '.SuitePaginationPopover-page-size .btn',
            });

            fireEvent.click(btn);

            expect(onSetPageSize).toHaveBeenCalledTimes(1);
            expect(onSetPageSize).toHaveBeenCalledWith(100);
        });
    };

    const testNextPrevArrows = (partialProps: Partial<PaginationProps>) => {
        const props = { ...defaultProps, ...partialProps };

        test('renders "prev/next" page arrows', () => {
            renderComponent(props);

            expect(screen.getByTestId(ARROW_NEXT)).toBeEnabled();
            expect(screen.getByTestId(ARROW_PREV)).toBeEnabled();
        });

        test('invokes "onChange" handler when clicking on "next" arrow', () => {
            renderComponent(props);

            const arrow = screen.getByTestId(ARROW_NEXT);
            fireEvent.click(arrow);

            expect(onChange).toHaveBeenCalledTimes(1);
            expect(onChange).toHaveBeenCalledWith(currentPage + 1);
        });

        test('invokes "onChange" handler when clicking on "prev" arrow', () => {
            renderComponent(props);

            const arrow = screen.getByTestId(ARROW_PREV);
            fireEvent.click(arrow);

            expect(onChange).toHaveBeenCalledTimes(1);
            expect(onChange).toHaveBeenCalledWith(currentPage - 1);
        });

        test('"next" arrow is disabled if on last page', () => {
            renderComponent({
                ...props,
                currentPage: getTotalPages(props.totalCount, props.pageSize) - 1,
            });

            const arrow = screen.getByTestId(ARROW_NEXT);
            fireEvent.click(arrow);

            expect(arrow).toBeDisabled();
            expect(onChange).toHaveBeenCalledTimes(0);
        });

        test('"next" arrow is not disabled if not on last page', () => {
            renderComponent({
                ...props,
                currentPage: getTotalPages(props.totalCount, props.pageSize) - 2,
            });

            const arrow = screen.getByTestId(ARROW_NEXT);
            fireEvent.click(arrow);

            expect(arrow).toBeEnabled();
            expect(onChange).toHaveBeenCalledTimes(1);
        });

        test('"prev" arrow is disabled if on first page', () => {
            renderComponent({
                ...props,
                currentPage: 0,
            });

            const arrow = screen.getByTestId(ARROW_PREV);
            fireEvent.click(arrow);

            expect(arrow).toBeDisabled();
            expect(onChange).toHaveBeenCalledTimes(0);
        });
    };

    afterEach(() => {
        onChange.mockClear();
        onSetPageSize.mockClear();
    });

    testComponent(Pagination, defaultProps, 'SuitePagination');

    describe('message', () => {
        const totalCount = 10;
        const currentPage = 1;
        const pageSize = 1;

        const props = { totalCount, pageSize, currentPage };

        test('renders', () => {
            renderComponent(props);

            expect(
                screen.getByText(formatPaginationMessage(currentPage, pageSize, totalCount))
            ).toBeInTheDocument();
        });

        test('clicking the message shows the Popover', async () => {
            renderComponent();

            const button = screen.getByTestId('SuitePagination-message');
            expect(button).toBeEnabled();

            fireEvent.click(button);

            expect(
                await screen.findByTestId('SuitePaginationPopover-PopoverOverlay')
            ).toBeVisible();
        });

        describe('when totalCount is zero', () => {
            test('does render its all zero message', () => {
                renderComponent({ ...props, totalCount: 0 });

                const text = formatPaginationMessage(currentPage, pageSize, 0);
                expect(screen.queryByText(text)).toHaveTextContent(
                    'pagination_message{"from":"0","to":"0","total":"0"}'
                );
            });
        });
    });

    describe('"disabled" prop is true', () => {
        test('message button is disabled and wont show Popover', () => {
            const { getByTestId, queryByTestId } = renderComponent({
                disabled: true,
            });

            const button = getByTestId('SuitePagination-message');
            expect(button).toBeDisabled();

            fireEvent.click(button);

            const pop = queryByTestId('SuitePaginationPopover-PopoverOverlay');
            expect(pop).not.toBeInTheDocument();
        });
    });

    describe('variant "compact"', () => {
        const props: Partial<PaginationProps> = { variant: 'compact' };

        testPageSizeSection(props);
        testNextPrevArrows(props);

        test('does not render "first/last" arrows', () => {
            renderComponent(props);

            expect(screen.queryByTestId(ARROW_FIRST)).toBeNull();
            expect(screen.queryByTestId(ARROW_LAST)).toBeNull();
        });

        test('renders "first/last" links in popover', async () => {
            await renderPopover(props);

            expect(screen.getByText(formatMessage('pagination_first_page'))).toBeInTheDocument();

            expect(screen.getByText(formatMessage('pagination_last_page'))).toBeInTheDocument();
        });

        test('"last" link in popover is disabled if on last page', async () => {
            await renderPopover({
                ...props,
                currentPage: getTotalPages(totalCount, defaultProps.pageSize) - 1,
            });

            const link = screen.getByText(formatMessage('pagination_last_page'));

            fireEvent.click(link);

            expect(link).toBeDisabled();
            expect(onChange).toHaveBeenCalledTimes(0);
        });

        test('"first" link in popover is disabled if on first page', async () => {
            await renderPopover({
                ...props,
                currentPage: 0,
            });

            const link = screen.getByText(formatMessage('pagination_first_page'));

            fireEvent.click(link);

            expect(link).toBeDisabled();
            expect(onChange).toHaveBeenCalledTimes(0);
        });

        test('invokes "onChange" handler when clicking on "first" link in popover', async () => {
            await renderPopover(props);

            const firstPage = screen.getByText(formatMessage('pagination_first_page'));

            fireEvent.click(firstPage);

            expect(onChange).toHaveBeenCalledTimes(1);
            expect(onChange).toHaveBeenCalledWith(0);
        });

        test('invokes "onChange" handler when clicking on "last" link in popover', async () => {
            await renderPopover(props);

            const lastPage = screen.getByText(formatMessage('pagination_last_page'));

            fireEvent.click(lastPage);

            expect(onChange).toHaveBeenCalledTimes(1);
            expect(onChange).toHaveBeenCalledWith(totalCount / pageSize - 1);
        });
    });

    describe('variant "regular"', () => {
        const props: Partial<PaginationProps> = { variant: 'expanded' };

        testPageSizeSection(props);
        testNextPrevArrows(props);

        test('renders "first/last" arrows', () => {
            renderComponent(props);

            expect(screen.getByTestId(ARROW_FIRST)).toBeInTheDocument();
            expect(screen.getByTestId(ARROW_LAST)).toBeInTheDocument();
        });

        test('invokes "onChange" handler when clicking on "first" arrow', () => {
            renderComponent(props);

            const arrow = screen.getByTestId(ARROW_FIRST);
            fireEvent.click(arrow);

            expect(onChange).toHaveBeenCalledTimes(1);
            expect(onChange).toHaveBeenCalledWith(0);
        });

        test('invokes "onChange" handler when clicking on "last" arrow', () => {
            renderComponent(props);

            const arrow = screen.getByTestId(ARROW_LAST);
            fireEvent.click(arrow);

            expect(onChange).toHaveBeenCalledTimes(1);
            expect(onChange).toHaveBeenCalledWith(totalCount / pageSize - 1);
        });

        test('"last" arrow is disabled if on last page', () => {
            renderComponent({
                ...props,
                currentPage: getTotalPages(totalCount, defaultProps.pageSize) - 1,
            });

            const arrow = screen.getByTestId(ARROW_LAST);
            fireEvent.click(arrow);

            expect(arrow).toBeDisabled();
            expect(onChange).toHaveBeenCalledTimes(0);
        });

        test('"first" arrow is disabled if on first page', () => {
            renderComponent({
                ...props,
                currentPage: 0,
            });

            const arrow = screen.getByTestId(ARROW_FIRST);
            fireEvent.click(arrow);

            expect(arrow).toBeDisabled();
            expect(onChange).toHaveBeenCalledTimes(0);
        });

        test('does not render navigation links in popover', async () => {
            await renderPopover(props);

            expect(screen.queryByText(formatMessage('pagination_first_page'))).toBeNull();
            expect(screen.queryByText(formatMessage('pagination_last_page'))).toBeNull();
        });
    });

    describe('with "menuPlacement"', () => {
        test('applies placement', async () => {
            await renderPopover({
                menuPlacement: 'top',
            });

            const popover = screen.getByTestId('SuitePaginationPopover-PopoverOverlay');

            expect(popover).toHaveClass('bs-popover-top');
        });
    });

    test.each([
        ['first page', formatMessage('pagination_first_page')],
        ['last page', formatMessage('pagination_last_page')],
        ['page size', '50'],
    ])('hides the popover when clicking on "%s" link', async (name, text) => {
        await renderPopover({});

        const link = screen.getByText(text);

        fireEvent.click(link);

        await waitFor(() => {
            expect(screen.queryByText(text)).toBeNull();
        });
    });

    test.each([
        ['first page', ARROW_FIRST],
        ['last page', ARROW_LAST],
        ['next page', ARROW_NEXT],
        ['prev page', ARROW_PREV],
    ])('hides the popover when clicking on "%s" arrow', async (name, testId) => {
        await renderPopover({ variant: 'expanded' });

        const arrow = screen.getByTestId(testId);

        screen.getByText(formatMessage('pagination_rows_per_page'));

        fireEvent.click(arrow);

        await waitFor(() => {
            expect(screen.queryByText(formatMessage('pagination_rows_per_page'))).toBeNull();
        });
    });

    describe('loading state', () => {
        test('on a regular pagination', () => {
            renderComponent({ variant: 'expanded', loading: true });

            expect(screen.getAllByTestId('SkeletonLoader')).toHaveLength(5);
        });

        test('on a compact pagination', () => {
            renderComponent({ loading: true });

            expect(screen.getAllByTestId('SkeletonLoader')).toHaveLength(3);
        });
    });
});
