import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { createHref } from '@theorchard/suite-utils';
import { MemoryRouter, Route } from 'react-router-dom';
import PageNav, { PageNavProps, PageNavItem } from '../pageNav';

describe('<PageNav>', () => {
    const intercomProperty = 'data-intercom-target';
    const customDataProperty = 'intercom-data';
    const items = [
        { key: 'one', label: 'TabSection One' },
        { key: 'two', label: 'TabSection Two' },
        {
            key: 'three',
            label: 'TabSection Three',
            route: '/page/:tab/category',
            keepParams: ['country'],
            dataAttributes: {
                [intercomProperty]: 'three',
                [customDataProperty]: 42,
            },
        },
    ];

    const renderComponent = ({
        pathname = '/',
        search,
        navItems,
        ...rest
    }: Partial<PageNavProps> & {
        pathname?: string;
        search?: string;
        navItems?: PageNavItem[];
    } = {}) =>
        render(<PageNav items={navItems || items} {...rest} />, {
            wrapper: ({ children }) => (
                <MemoryRouter initialEntries={[{ pathname, search }]}>
                    <Route path={rest.route}>{children}</Route>
                </MemoryRouter>
            ),
        });

    describe('without route', () => {
        test('existing query params are kept', () => {
            const search = 'some=value';
            const pathname = '/some-url';
            const wrapper = renderComponent({ pathname, search });
            const [, item] = items;
            const element = wrapper.getByText(item.label);
            expect(element).toBeVisible();
            expect(element).toHaveAttribute('href', `${pathname}?${search}&tab=${item.key}`);
        });

        describe('without "tab" query param', () => {
            test('first item has active state', () => {
                const { getByText } = renderComponent({
                    pathname: '/tabbed-page',
                });

                const [item1, item2, item3] = items;
                const link1 = getByText(item1.label);
                const link2 = getByText(item2.label);
                const link3 = getByText(item3.label);

                expect(link1).toHaveClass('active');
                expect(link2).not.toHaveClass('active');
                expect(link3).not.toHaveClass('active');
            });
        });

        describe('with "tab" query param', () => {
            test.each(items)('matched item has active state', (item) => {
                const { getByText } = renderComponent({
                    pathname: '/tabbed-page',
                    search: `tab=${item.key}`,
                });

                const link = getByText(item.label);
                expect(link).toHaveClass('active');

                const rest = items.filter((tabItem) => tabItem !== item);
                rest.forEach((tabItem) => {
                    const tabLink = getByText(tabItem.label);
                    expect(tabLink).not.toHaveClass('active');
                });
            });
        });
    });

    describe('with route', () => {
        const route = '/page/:tab?';
        const params = { page: '1' };

        test('renders links', () => {
            const wrapper = renderComponent({
                route,
                pathname: '/page',
                params,
            });

            items.forEach((item) => {
                const element = wrapper.getByText(item.label);
                expect(element).toBeVisible();

                const url = createHref(item.route || route, {
                    tab: item.key,
                    ...params,
                });
                expect(element).toHaveAttribute('href', url);
            });
        });

        describe('without "tab" route param', () => {
            test('first item has active state', () => {
                const { getByText } = renderComponent({
                    pathname: '/page',
                    route,
                });

                const [item1, item2, item3] = items;
                const link1 = getByText(item1.label);
                const link2 = getByText(item2.label);
                const link3 = getByText(item3.label);

                expect(link1).toHaveClass('active');
                expect(link2).not.toHaveClass('active');
                expect(link3).not.toHaveClass('active');
            });

            test('last item has active state', () => {
                const item1 = { key: 'one', label: 'TabSection One' };
                const item2 = { key: 'two', label: 'TabSection Two' };
                const item3 = { key: '', label: 'TabSection Three' };
                const { getByText } = renderComponent({
                    pathname: '/page',
                    route,
                    navItems: [item1, item2, item3],
                });

                const link1 = getByText(item1.label);
                const link2 = getByText(item2.label);
                const link3 = getByText(item3.label);

                expect(link1).not.toHaveClass('active');
                expect(link2).not.toHaveClass('active');
                expect(link3).toHaveClass('active');
            });

            test('second item has active state', () => {
                const item1 = { key: 'one', label: 'TabSection One' };
                const item2 = { key: 'two', label: 'TabSection Two' };
                const item3 = { key: '', label: 'TabSection Three' };
                const { getByText } = renderComponent({
                    pathname: '/page/two',
                    route,
                    navItems: [item1, item2, item3],
                });

                const link1 = getByText(item1.label);
                const link2 = getByText(item2.label);
                const link3 = getByText(item3.label);

                expect(link1).not.toHaveClass('active');
                expect(link2).toHaveClass('active');
                expect(link3).not.toHaveClass('active');
            });
        });

        describe('with "tab" route param', () => {
            test.each(items)('matched item has active state', (item) => {
                const { getByText } = renderComponent({
                    pathname: `/page/${item.key}`,
                    route,
                });

                const link = getByText(item.label);
                expect(link).toHaveClass('active');

                const rest = items.filter((tabItem) => tabItem !== item);
                rest.forEach((tabItem) => {
                    const tabLink = getByText(tabItem.label);
                    expect(tabLink).not.toHaveClass('active');
                });
            });
        });

        describe('with "concatParameterValueByComma" param', () => {
            const testParams = { country: ['GB', 'UA', 'NO'] };
            test('renders parameters as an array', () => {
                renderComponent({
                    route,
                    pathname: '/page',
                    params: testParams,
                    concatParameterValueByComma: false,
                });

                const [, , pageThree] = items;
                const page = screen.getByText(pageThree.label);
                expect(page).toHaveAttribute(
                    'href',
                    '/page/three/category?country=GB&country=UA&country=NO'
                );
            });

            test('renders parameters as a comma delimited array', () => {
                renderComponent({
                    route,
                    pathname: '/page',
                    params: testParams,
                });

                const [, , pageThree] = items;
                const page = screen.getByText(pageThree.label);
                expect(page).toHaveAttribute('href', '/page/three/category?country=GB%2CUA%2CNO');
            });
        });

        describe('with "keepParams"', () => {
            test('relays the named params', () => {
                const { getByText } = renderComponent({
                    pathname: '/page',
                    search: '?country=gb',
                    route,
                });

                const [, , item3] = items;
                const link3 = getByText(item3.label);
                expect(link3).toHaveAttribute('href', '/page/three/category?country=gb');
            });
        });

        describe('with "dataAttributes" property', () => {
            test('renders nav link with specified data properties', () => {
                const customDataAttributeName = `data-${customDataProperty}`;
                const [, , { key, label }] = items;
                const { getByText } = renderComponent({
                    pathname: '/page',
                    search: '?country=gb',
                    route,
                });
                const pageWithExtraProperties = getByText(label);

                expect(pageWithExtraProperties).toHaveAttribute(intercomProperty, key);
                expect(pageWithExtraProperties).toHaveAttribute(customDataAttributeName, '42');
            });
        });

        test('has correct history entries', () => {
            interface HistoryType {
                history?: { entries?: { pathname: string }[] };
            }

            const [tabItemOne, tabItemTwo, tabItemThree] = items;

            let router: (MemoryRouter & HistoryType) | null | undefined;

            const wrapper = render(<PageNav route={route} items={items} />, {
                wrapper: ({ children }) => (
                    <MemoryRouter
                        ref={(instance) => {
                            router = instance;
                        }}
                    >
                        {children}
                    </MemoryRouter>
                ),
            });

            const tabLinkOne = wrapper.getByText(tabItemOne.label);
            const tabLinkTwo = wrapper.getByText(tabItemTwo.label);
            const tabLinkThree = wrapper.getByText(tabItemThree.label);

            fireEvent.click(tabLinkOne);
            fireEvent.click(tabLinkTwo);
            fireEvent.click(tabLinkThree);

            const actualPaths = router?.history?.entries?.map(({ pathname }) => pathname);

            const expectedPaths = [
                '/',
                createHref(route, { tab: tabItemOne.key }),
                createHref(route, { tab: tabItemTwo.key }),
                `/page/${tabItemThree.key}/category`,
            ];

            expect(actualPaths).toEqual(expectedPaths);
        });
    });

    describe('with "onItemMouseOver" property', () => {
        test('invokes the callback on mouse over a nav item', () => {
            const onItemMouseOver = vi.fn();
            const [item] = items;

            const { getByText } = renderComponent({
                pathname: '/page',
                search: '?country=gb',
                onItemMouseOver,
            });

            const link1 = getByText(item.label);

            fireEvent.mouseOver(link1);

            expect(onItemMouseOver).toHaveBeenCalledTimes(1);
            expect(onItemMouseOver).toHaveBeenCalledWith(item);
        });
    });

    describe('with "onItemClick" property', () => {
        test('invokes the callback on nav item clicks', () => {
            const onItemClick = vi.fn();
            const [item] = items;

            const { getByText } = renderComponent({
                pathname: '/page',
                search: '?country=gb',
                onItemClick,
            });

            const link1 = getByText(item.label);

            fireEvent.click(link1);

            expect(onItemClick).toHaveBeenCalledTimes(1);
            expect(onItemClick).toHaveBeenCalledWith(item);
        });
    });

    describe('with custom "label" element', () => {
        test('renders element in nav', () => {
            const testId = 'some-test-id';
            const { getByTestId } = renderComponent({
                items: [
                    { key: 'one', label: 'TabSection One' },
                    {
                        key: 'test',
                        label: <div data-testid={testId}>asd</div>,
                    },
                ],
            });

            const element = getByTestId(testId);
            expect(element).toBeVisible();
            expect(element).toHaveTextContent('asd');
            expect(element.parentElement).toHaveAttribute('href', `/?tab=test`);
        });
    });

    describe('with "loading" prop', () => {
        test('only renders skeleton loader', () => {
            const testId = 'some-test-id';
            const { queryByTestId, getByTestId } = renderComponent({
                loading: true,
                items: [
                    { key: 'one', label: 'TabSection One' },
                    {
                        key: 'test',
                        label: <div data-testid={testId}>asd</div>,
                    },
                ],
            });

            expect(queryByTestId(testId)).toBeNull();

            const loader = getByTestId('SkeletonLoader');
            expect(loader).toBeVisible();
            expect(loader.parentElement).toHaveClass('PageNav-loading');
        });
    });
});
