import '@testing-library/jest-dom/extend-expect';
import { renderHook, act, cleanup } from '@testing-library/react-hooks';
import executeHook from 'lib/testing/hooks';
import { mockQuery } from 'lib/testing/mocks';
import { DEFAULT_FILTERS } from 'src/constants';
import { PAST28, HIGHWATER_MARK } from 'src/constants/periods';
import { DEFAULT_PLAYLISTS_COUNT, DEFAULT_PLAYLISTS_TOTAL_COUNT } from 'src/constants/playlists';
import { STORE_SPOTIFY } from 'src/constants/stores';
import { SoundRecordingMetadata, ComparisonSong, QueryPagination } from 'src/definitions';
import {
    createSoundRecordingMetadata,
    createSoundRecordingStreams,
    createSoundRecordingStreamsBreakdown,
    createSoundRecordingDemographics,
    createSoundRecordingPlaylists,
} from 'lib/factories/soundRecording';
import { createAnalyticsFeedsData } from 'lib/factories/analyticsFeeds';
import { selectStreamRecords, selectPlaylistRecords } from 'src/selectors';
import {
    useComparisonFilters, useComparisonData,
    useBatchStreamsQuery, useBatchStreamsBreakdownQuery,
    useBatchDemographicsBreakdownQuery, useBatchPlaylistsQuery,
    useFetchMore, useDisplayData,
    useDisplayLimitedData, useAnalyticsFeeds,
} from '..';

const CODE1 = 'CODE1';
const CODE2 = 'CODE2';
const selectedSongIds = [CODE1, CODE2];
const selectedStoreIds = [STORE_SPOTIFY];

describe('useComparisonFilters', () => {
    test('returns default comparison filters', async () => {
        const expectedResult = DEFAULT_FILTERS;
        const result = await executeHook(useComparisonFilters, {
            waitUntilMatch: expectedResult
        });

        expect(result).toEqual(expectedResult);
    });

    describe('with selected songs', () => {
        test('returns modified comparison filters', async () => {
            const expectedResult = {
                ...DEFAULT_FILTERS,
                selectedSongIds
            };

            const result = await executeHook(useComparisonFilters, {
                filter: { selectedSongIds },
                waitUntilMatch: expectedResult,
                mocks: [
                    mockQuery('SoundRecordingMetadataQuery', {
                        variables: {}
                    })
                ]
            });

            expect(result).toEqual(expectedResult);
        });
    });
});

describe('useDisplayData', () => {
    type DisplayDataProps = ({ data: string, isLoading: boolean });
    type DisplayDataResult = ({ data: string });

    afterEach(cleanup);

    test('returns previous data if loading', () => {
        const initialProps = { data: 'prev', isLoading: false };
        const { result, rerender } = renderHook<DisplayDataProps, DisplayDataResult>(
            ({ data, isLoading }) => useDisplayData(data, isLoading),
            { initialProps }
        );

        rerender({ data: 'empty', isLoading: true });

        expect(result.current).toEqual({
            data: initialProps.data
        });
    });

    test('returns current data if not loading', () => {
        const initialProps = { data: 'prev', isLoading: false };
        const nextProps = { data: 'next', isLoading: false };
        const { result, rerender } = renderHook<DisplayDataProps, DisplayDataResult>(
            ({ data, isLoading }) => useDisplayData(data, isLoading),
            { initialProps }
        );

        rerender({ data: 'empty', isLoading: true });
        rerender(nextProps);

        expect(result.current).toEqual({
            data: nextProps.data
        });
    });
});

describe('useFetchMore', () => {
    type CommonVariable = { value: string };
    type FetchMoreProps = {
        commonVariables: CommonVariable[],
        initalState?: QueryPagination,
    };
    type FetchMoreResult = ({
        variables: (CommonVariable & QueryPagination)[],
        fetchMore: (next?: Partial<QueryPagination>) => void
    });

    const initialProps = { commonVariables: [{ value: 'commonVar' }] };

    afterEach(cleanup);

    test('returns extended with Initial Pagination variables', () => {
        const testInitialProps = { ...initialProps, initalState: { offset: 20, limit: 10 } };
        const { result } = renderHook<FetchMoreProps, FetchMoreResult>(
            ({ commonVariables, initalState }) => useFetchMore(commonVariables, initalState),
            { initialProps: testInitialProps },
        );

        expect(result.current.variables).toEqual([{
            value: 'commonVar',
            limit: 10,
            offset: 20,
        }]);
    });

    test('returns extended with Default Pagination variables', () => {
        const { result } = renderHook<FetchMoreProps, FetchMoreResult>(
            ({ commonVariables }) => useFetchMore(commonVariables),
            { initialProps },
        );

        act(() => {
            result.current.fetchMore();
        });

        expect(result.current.variables).toEqual([{
            value: 'commonVar',
            limit: 5,
            offset: 5,
        }]);
    });

    test('returns extended with Custom Pagination variables', () => {
        const { result } = renderHook<FetchMoreProps, FetchMoreResult>(
            ({ commonVariables }) => useFetchMore(commonVariables),
            { initialProps }
        );

        act(() => {
            result.current.fetchMore({ limit: 10, offset: 20 });
        });

        expect(result.current.variables).toEqual([{
            value: 'commonVar',
            limit: 10,
            offset: 20,
        }]);
    });
});

describe('useDisplayLimitedData', () => {
    type DisplayLimitedDataProps = number | undefined;
    type DisplayLimitedDataResult = ({
        count?: number;
        showCount: (nextCount?: number) => void
    });

    afterEach(cleanup);

    test('returns default count', () => {
        const defaultCount = 5;
        const { result } = renderHook<DisplayLimitedDataProps, DisplayLimitedDataResult>(
            () => useDisplayLimitedData(defaultCount)
        );
        expect(result.current.count).toBe(defaultCount);
    });

    test('returns custom count', () => {
        const nextCount = 100;
        const { result } = renderHook<DisplayLimitedDataProps, DisplayLimitedDataResult>(
            count => useDisplayLimitedData(count)
        );

        act(() => {
            result.current.showCount(nextCount);
        });

        expect(result.current.count).toBe(nextCount);
    });
});

describe('useComparisonData', () => {
    test('returns default comparison filters and empty songs', async () => {
        const expectedResult = {
            ...DEFAULT_FILTERS,
            songs: []
        };
        const result = await executeHook(useComparisonData, {
            waitUntilMatch: expectedResult
        });

        expect(result).toEqual(expectedResult);
    });

    describe('with selected songs', () => {
        test('returns modified comparison filters and songs', async () => {
            const songs: Record<string, SoundRecordingMetadata> = {
                [CODE1]: createSoundRecordingMetadata({ isrc: CODE1 }),
                [CODE2]: createSoundRecordingMetadata({ isrc: CODE2 }),
            };

            const expectedResult = {
                ...DEFAULT_FILTERS,
                selectedSongIds,
                songs: selectedSongIds.map(id => ({
                    id,
                    error: false,
                    loading: false,
                    data: songs[id].soundRecording
                }))
            };

            const result = await executeHook(useComparisonData, {
                filter: { selectedSongIds },
                waitUntilMatch: expectedResult,
                mocks: selectedSongIds.map(isrc =>
                    mockQuery('SoundRecordingMetadataQuery', {
                        variables: { isrc },
                        result: songs[isrc]
                    }))
            });

            expect(result).toEqual(expectedResult);
        });
    });
});

describe('useAnalyticsFeeds', () => {
    test('returns list of stores', async () => {
        const expectedResult = {
            storesList: []
        };

        const result = await executeHook(useAnalyticsFeeds, {
            waitUntilMatch: expectedResult
        });

        expect(result).toEqual(expectedResult);
    });


    test('returns list of stores without duplicate values', async () => {
        const analyticsFeeds = createAnalyticsFeedsData();
        const storesList = [
            {
                value: 1,
                label: 'Apple Music',
            },
            {
                value: 708,
                label: 'Pandora',
            },
            {
                value: 286,
                label: 'Spotify',
            }
        ];

        const expectedResult = {
            storesList
        };

        const result = await executeHook(useAnalyticsFeeds, {
            waitUntilMatch: expectedResult,
            mocks: [
                mockQuery('AnalyticsFeedsDataQuery', {
                    result: analyticsFeeds
                })
            ]
        });

        expect(result).toEqual(expectedResult);
    });

});

describe('useBatchStreamsQuery', () => {
    test('returns empty data', async () => {
        const expectedResult = {
            loading: false,
            data: []
        };

        const result = await executeHook(useBatchStreamsQuery, {
            arguments: {},
            waitUntilMatch: expectedResult
        });

        expect(result).toEqual(expectedResult);
    });

    describe('with selected songs', () => {
        test('returns stream data for each song', async () => {
            const songs = selectedSongIds.map(id => ({
                id,
                data: createSoundRecordingMetadata({
                    isrc: id,
                    products: [
                        { genreId: null, releaseDate: '01-01-2019', deletions: 'N' }
                    ]
                }).soundRecording
            })) as ComparisonSong[];

            const expectedResult = {
                loading: false,
                data: selectStreamRecords(songs, songs.map(() => ({
                    data: createSoundRecordingStreams(),
                    loading: false,
                    networkStatus: 7,
                    stale: false
                })))
            };

            const result = await executeHook(useBatchStreamsQuery, {
                filter: { selectedSongIds },
                arguments: { songs, dateFilter: PAST28, storeFilter: selectedStoreIds },
                waitUntilMatch: expectedResult,
                mocks: selectedSongIds.map(isrc =>
                    mockQuery('SoundRecordingStreamsQuery', {
                        variables: { isrc, startDate: HIGHWATER_MARK, days: -28, storeIds: selectedStoreIds }
                    }))
            });

            expect(result).toEqual(expectedResult);
        });
    });
});


describe('useBatchStreamsBreakdownQuery', () => {
    test('returns empty data', async () => {
        const expectedResult = {
            loading: false,
            data: []
        };

        const result = await executeHook(useBatchStreamsBreakdownQuery, {
            arguments: {},
            waitUntilMatch: expectedResult
        });

        expect(result).toEqual(expectedResult);
    });

    describe('with selected songs', () => {
        test('returns sos data for each song', async () => {
            const songs = selectedSongIds.map(id => ({
                id,
                data: createSoundRecordingMetadata({
                    isrc: id,
                    products: [
                        { genreId: null, releaseDate: '01-01-2019', deletions: 'N' }
                    ]
                }).soundRecording
            }));

            const expectedResult = {
                loading: false,
                data: songs.map(() => ({
                    data: createSoundRecordingStreamsBreakdown(),
                    loading: false,
                    networkStatus: 7,
                    stale: false
                }))
            };

            const result = await executeHook(useBatchStreamsBreakdownQuery, {
                filter: { selectedSongIds },
                arguments: { songs, dateFilter: PAST28, storeFilter: selectedStoreIds },
                waitUntilMatch: expectedResult,
                mocks: selectedSongIds.map(isrc =>
                    mockQuery('SoundRecordingStreamsBreakdownQuery', {
                        variables: { isrc, startDate: HIGHWATER_MARK, days: -28, storeIds: selectedStoreIds }
                    }))
            });

            expect(result).toEqual(expectedResult);
        });
    });
});


describe('useBatchDemographicsBreakdownQuery', () => {
    test('returns empty data', async () => {
        const expectedResult = {
            loading: false,
            data: []
        };

        const result = await executeHook(useBatchDemographicsBreakdownQuery, {
            arguments: {},
            waitUntilMatch: expectedResult
        });

        expect(result).toEqual(expectedResult);
    });

    describe('with selected songs', () => {
        test('returns demographics data for each song', async () => {
            const songs = selectedSongIds.map(id => ({
                id,
                data: createSoundRecordingMetadata({
                    isrc: id,
                    products: [
                        { genreId: null, releaseDate: '01-01-2019', deletions: 'N' }
                    ]
                }).soundRecording
            }));

            const expectedResult = {
                loading: false,
                data: songs.map(() => ({
                    data: createSoundRecordingDemographics(),
                    loading: false,
                    networkStatus: 7,
                    stale: false
                }))
            };

            const result = await executeHook(useBatchDemographicsBreakdownQuery, {
                filter: { selectedSongIds },
                arguments: { songs, dateFilter: PAST28, storeFilter: selectedStoreIds },
                waitUntilMatch: expectedResult,
                mocks: selectedSongIds.map(isrc =>
                    mockQuery('SoundRecordingDemographicsQuery', {
                        variables: { isrc, startDate: HIGHWATER_MARK, days: -28, storeIds: selectedStoreIds }
                    }))
            });

            expect(result).toEqual(expectedResult);
        });
    });
});

describe('useBatchPlaylistsQuery', () => {
    test('returns empty data', async () => {
        const expectedResult = {
            loading: false,
            count: DEFAULT_PLAYLISTS_COUNT,
            data: []
        };

        const result = await executeHook(useBatchPlaylistsQuery, {
            arguments: {},
            waitUntilMatch: expectedResult
        });

        expect(result).toEqual(expectedResult);
    });

    describe('with selected songs', () => {
        test('returns playlists data for each song', async () => {
            const songs = selectedSongIds.map(id => ({
                id,
                data: createSoundRecordingMetadata({
                    isrc: id,
                    products: [
                        { genreId: null, releaseDate: '01-01-2019', deletions: 'N' }
                    ]
                }).soundRecording
            })) as ComparisonSong[];

            const expectedResult = {
                loading: false,
                count: 5,
                data: selectPlaylistRecords(songs, songs.map(() => ({
                    data: createSoundRecordingPlaylists(),
                    loading: false,
                    networkStatus: 7,
                    stale: false
                })))
            };

            const result = await executeHook(useBatchPlaylistsQuery, {
                filter: { selectedSongIds },
                arguments: { songs, dateFilter: PAST28, storeFilter: selectedStoreIds, limit: 5, offset: 0 },
                waitUntilMatch: expectedResult,
                mocks: selectedSongIds.map(isrc =>
                    mockQuery('SoundRecordingPlaylistsQuery', {
                        variables: {
                            isrc,
                            startDate: HIGHWATER_MARK,
                            days: -28,
                            storeIds: selectedStoreIds,
                            limit: DEFAULT_PLAYLISTS_TOTAL_COUNT,
                            offset: 0
                        }
                    }))
            });

            expect(result).toEqual(expectedResult);
        });
    });
});
