import React from 'react';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { MAIN_CONTENT_CLASSNAME } from 'src/constants';
import InfiniteScroll, { InfiniteScrollProps, TESTID } from '../infiniteScroll';

describe('<InfiniteScroll>', () => {
    const CONTENT = 'CONTENT';

    const renderComponent = (props?: Partial<InfiniteScrollProps>) =>
        render(
            <div className={MAIN_CONTENT_CLASSNAME}>
                <InfiniteScroll
                    fetchMore={jest.fn()}
                    count={0}
                    loading={false}
                    {...props}
                >
                    <div>{CONTENT}</div>
                </InfiniteScroll>
            </div>
        );

    const renderAndScroll = (props?: Partial<InfiniteScrollProps>) => {
        const result = renderComponent(props);

        const mainContent = result.container
            .getElementsByClassName(MAIN_CONTENT_CLASSNAME)
            .item(0);
        if (!mainContent) throw new Error('MainContent not found');
        fireEvent.scroll(mainContent);

        return result;
    };

    test('renders wrapper and content component', () => {
        const { getByText, getByTestId } = renderComponent();
        expect(getByText(CONTENT)).toBeVisible();
        expect(getByTestId(TESTID)).toBeVisible();
    });

    test('shows loading indicator if count is > 0 and loading is true', () => {
        const { container } = renderComponent({ count: 100, loading: true });
        expect(
            container.getElementsByClassName('LoadingIndicator').length
        ).toBe(1);
    });

    test('does not show loading indicator if count is 0 and loading is true', () => {
        const { container } = renderComponent({ count: 0, loading: true });
        expect(
            container.getElementsByClassName('LoadingIndicator').length
        ).toBe(0);
    });

    test('fires fetchMore when scrolled', async () => {
        const fetchMore = jest.fn();
        renderAndScroll({ fetchMore, count: 0, loading: false });

        await waitFor(() => expect(fetchMore).toHaveBeenCalled(), {
            timeout: 500,
        });
    });

    test('does not fire fetchMore when loading', async () => {
        const fetchMore = jest.fn();
        renderAndScroll({ fetchMore, count: 0, loading: true });

        await waitFor(
            () => {
                expect(fetchMore).not.toHaveBeenCalled();
            },
            { timeout: 200 }
        );
    });

    test('does not fire fetchMore when count >= totalLimit', async () => {
        const fetchMore = jest.fn();
        renderAndScroll({
            fetchMore,
            count: 100,
            totalLimit: 100,
            loading: false,
        });

        await waitFor(
            () => {
                expect(fetchMore).not.toHaveBeenCalled();
            },
            { timeout: 200 }
        );
    });

    test('does not fire fetchMore when count >= totalResults', async () => {
        const fetchMore = jest.fn();
        renderAndScroll({
            fetchMore,
            count: 50,
            totalResults: 50,
            loading: false,
        });

        await waitFor(
            () => {
                expect(fetchMore).not.toHaveBeenCalled();
            },
            { timeout: 200 }
        );
    });

    test('fires fetchMore with correct parameters', async () => {
        const fetchMore = jest.fn();
        const loadLimit = 50;
        const count = 0;

        renderAndScroll({ fetchMore, count, loadLimit, loading: false });

        await waitFor(
            () => {
                expect(fetchMore).toHaveBeenCalledWith(loadLimit, count);
            },
            { timeout: 500 }
        );
    });

    test('uses custom loadLimit when provided', async () => {
        const fetchMore = jest.fn();
        const customLoadLimit = 25;

        renderAndScroll({
            fetchMore,
            count: 50,
            loadLimit: customLoadLimit,
            totalLimit: 10000,
            loading: false,
        });

        await waitFor(
            () => {
                expect(fetchMore).toHaveBeenCalledWith(customLoadLimit, 50);
            },
            { timeout: 500 }
        );
    });

    test('respects totalLimit when calculating fetchMore limit', async () => {
        const fetchMore = jest.fn();
        const loadLimit = 50;
        const count = 9980;
        const totalLimit = 10000;

        renderAndScroll({
            fetchMore,
            count,
            loadLimit,
            totalLimit,
            loading: false,
        });

        await waitFor(
            () => {
                // Should fetch only 20 items to respect the limit
                expect(fetchMore).toHaveBeenCalledWith(20, count);
            },
            { timeout: 500 }
        );
    });

    test('shows "too many results" message when count >= totalLimit', () => {
        const { container } = renderComponent({
            count: 10000,
            totalLimit: 10000,
        });
        expect(container.querySelector('.text-muted')).toBeInTheDocument();
    });

    test('uses custom scrollContainerClassName', async () => {
        const fetchMore = jest.fn();
        const customClassName = 'CustomScrollContainer';

        const { container } = render(
            <div className={customClassName}>
                <InfiniteScroll
                    fetchMore={fetchMore}
                    count={0}
                    loading={false}
                    scrollContainerClassName={customClassName}
                >
                    <div>{CONTENT}</div>
                </InfiniteScroll>
            </div>
        );

        const customContainer = container.querySelector(`.${customClassName}`);
        if (customContainer) {
            fireEvent.scroll(customContainer);
        }

        await waitFor(() => expect(fetchMore).toHaveBeenCalled(), {
            timeout: 500,
        });
    });

    test('cleans up event listener on unmount', () => {
        const { unmount } = renderComponent();
        const removeEventListenerSpy = jest.spyOn(
            EventTarget.prototype,
            'removeEventListener'
        );

        unmount();

        expect(removeEventListenerSpy).toHaveBeenCalledWith(
            'scroll',
            expect.any(Function)
        );
        removeEventListenerSpy.mockRestore();
    });
});
