import React from 'react';
import { render } from '@testing-library/react';
import { usePageContext } from '@theorchard/suite-components';
import { get } from '@theorchard/suite-utils';
import { MemoryRouter } from 'react-router-dom';
import { FRAMEWORK_CLASS_NAME } from '../../../constants';
import Segment from '../../../segment';
import { PageRoute, PageRouteProps } from '../pageRoute';

vi.mock('@theorchard/suite-config', () => ({
    useAppConfig: () => ({
        environment: 'dev',
    }),
}));

describe('<PageRoute>', () => {
    let globalConsoleError: typeof global.console.error;

    beforeEach(() => {
        globalConsoleError = global.console.error;
        global.console.error = vi.fn();
    });
    afterEach(() => {
        global.console.error = globalConsoleError;
    });

    const renderComponent = (props: PageRouteProps, pathname = '/') =>
        render(
            <MemoryRouter initialEntries={[{ pathname }]}>
                <PageRoute {...props} />
            </MemoryRouter>
        );

    const trackPage = vi.spyOn(Segment, 'trackPage').mockImplementation();

    beforeEach(() => {
        trackPage.mockClear();
    });

    describe('on matching route', () => {
        const options = {
            page: () => <div>hi</div>,
            path: '/',
        };

        test('renders page', () => {
            const { getByText } = renderComponent(options);
            expect(getByText('hi')).toBeVisible();
        });

        test('renders page with route props', () => {
            const id = 'some_id';
            const path = `/test/${id}`;
            const { getByText } = renderComponent(
                {
                    path: '/test/:id',
                    page: ({ match }) => (
                        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
                        <div>{`${get(match, 'params.id')}`}</div>
                    ),
                },
                path
            );
            expect(getByText(id)).toBeVisible();
        });

        test('renders PageContainer with custom class', () => {
            const className = 'test-class';
            const { container } = renderComponent({ ...options, className });
            expect(container.querySelector(`.PageContainer.${className}`)).toBeVisible();
        });

        test('renders PageContainer with PageContext.Provider', () => {
            const className = 'test-class';

            const TestPage = () => {
                const pageContext = usePageContext();

                return <div>PAGE: {pageContext.className}</div>;
            };

            const { getByText } = renderComponent({
                ...options,
                className,
                page: TestPage,
            });

            expect(
                getByText(`PAGE: ${FRAMEWORK_CLASS_NAME} ${className}`, {
                    collapseWhitespace: true,
                })
            ).toBeVisible();
        });

        test('renders error message on error', () => {
            const error = 'BROKEN PAGE';
            const BrokenPage = () => {
                throw new Error(error);
            };

            const { container } = renderComponent({
                ...options,
                page: BrokenPage,
            });
            const element = container.getElementsByClassName('ErrorMessage').item(0);
            expect(element).toBeVisible();
        });

        test('invokes Segment.trackPage on mount', () => {
            const { rerender } = renderComponent(options);
            expect(Segment.trackPage).toHaveBeenCalledTimes(1);

            rerender(
                <MemoryRouter initialEntries={[{ pathname: options.path }]}>
                    <PageRoute path={options.path} page={options.page} />
                </MemoryRouter>
            );
            expect(Segment.trackPage).toHaveBeenCalledTimes(1);
        });

        describe('with dynamic pages', () => {
            test('renders splash page while loading', () => {
                const DynamicPage = React.lazy(
                    async () =>
                        await new Promise(() => {
                            /*noop*/
                        })
                );

                const { getByTestId } = renderComponent({
                    ...options,
                    page: DynamicPage,
                });
                expect(getByTestId('SplashPage')).toBeInTheDocument();
            });

            test('renders component when loaded', async () => {
                interface LazyModule {
                    default: React.FC;
                }

                const text = 'LOADED!';
                const Component = () => <div>{text}</div>;
                let resolveComponent: ((module: LazyModule) => void) | undefined;
                const DynamicPage = React.lazy(
                    async () =>
                        await new Promise<LazyModule>((resolve) => {
                            resolveComponent = resolve;
                        })
                );

                const { getByTestId, findByText, queryByTestId } = renderComponent({
                    ...options,
                    page: DynamicPage,
                });
                expect(getByTestId('SplashPage')).toBeInTheDocument();

                resolveComponent?.({ default: Component });
                expect(await findByText(text)).toBeInTheDocument();
                expect(queryByTestId('SplashPage')).toBeNull();
            });
        });
    });
});
