import React from 'react'
import type { GridPreferencesAdapter } from '../types'
import { render } from '../utils/test-utils'
import type { UseGridReturn } from './use-grid'
import { createGrid } from './use-grid'
import { usePreferencesAdapter } from './use-preferences-adapter'

type UsesPreferencesProps = {
    grid: UseGridReturn
    preferencesAdapter?: GridPreferencesAdapter
}

const UsesPreferences = ({
    grid,
    preferencesAdapter,
}: UsesPreferencesProps) => {
    const initialized = usePreferencesAdapter({ grid, preferencesAdapter })
    return <div>{initialized.toString()}</div>
}

describe('usePreferencesAdapter', () => {
    let grid: UseGridReturn

    beforeEach(() => {
        grid = createGrid()

        jest.spyOn(
            grid.selectors,
            'selectColumnIdsWithoutInternal'
        ).mockReturnValue(['123', '234'])
    })

    describe('initialization', () => {
        it('should render as initialized if no preferencesAdapter is provided', () => {
            const { getByText } = render(<UsesPreferences grid={grid} />)

            expect(getByText('true')).toBeInTheDocument()
        })

        it('should render as not initialized while loading preferences', () => {
            const adapter: GridPreferencesAdapter = {
                load: jest.fn().mockReturnValue(
                    new Promise(() => {
                        /* never resolve */
                    })
                ),
                save: jest.fn(),
            }
            const { getByText } = render(
                <UsesPreferences grid={grid} preferencesAdapter={adapter} />
            )

            expect(getByText('false')).toBeInTheDocument()
        })

        it('should render as initialized once preferences are loaded', async () => {
            const adapter: GridPreferencesAdapter = {
                load: jest.fn().mockResolvedValue({}),
                save: jest.fn(),
            }
            const { findByText } = render(
                <UsesPreferences grid={grid} preferencesAdapter={adapter} />
            )

            expect(await findByText('true')).toBeInTheDocument()
        })
    })

    describe('when loading data', () => {
        it("should not apply changes if column ids don't match (different count)", async () => {
            const dispatch = jest.spyOn(grid, 'dispatch')

            const adapter: GridPreferencesAdapter = {
                load: jest.fn().mockResolvedValue({
                    columns: [{ id: '123', width: null }],
                }),
                save: jest.fn(),
            }

            const { findByText } = render(
                <UsesPreferences grid={grid} preferencesAdapter={adapter} />
            )

            await findByText('true')

            expect(dispatch).not.toHaveBeenCalled()
        })

        it("should not apply changes if column ids don't match (different ids)", async () => {
            const dispatch = jest.spyOn(grid, 'dispatch')

            const adapter: GridPreferencesAdapter = {
                load: jest.fn().mockResolvedValue({
                    columns: [
                        { id: '123', width: null },
                        { id: '345', width: null },
                    ],
                }),
                save: jest.fn(),
            }

            const { findByText } = render(
                <UsesPreferences grid={grid} preferencesAdapter={adapter} />
            )

            await findByText('true')

            expect(dispatch).not.toHaveBeenCalled()
        })

        it('should apply changes if column ids match', async () => {
            const dispatch = jest.spyOn(grid, 'dispatch')

            const adapter: GridPreferencesAdapter = {
                load: jest.fn().mockResolvedValue({
                    columns: [
                        { id: '123', width: null },
                        { id: '234', width: 245 },
                    ],
                }),
                save: jest.fn(),
            }

            const { findByText } = render(
                <UsesPreferences grid={grid} preferencesAdapter={adapter} />
            )

            await findByText('true')

            expect(dispatch).toHaveBeenCalledWith({
                type: 'applyPreferences',
                payload: {
                    preferences: {
                        columns: [
                            { id: '123', width: null },
                            { id: '234', width: 245 },
                        ],
                    },
                    multiSelection: false,
                },
            })
        })

        it('should apply changes if column ids match (multi-select)', async () => {
            const dispatch = jest.spyOn(grid, 'dispatch')
            jest.spyOn(grid.selectors, 'selectSelectionMode').mockReturnValue(
                'multi'
            )

            const adapter: GridPreferencesAdapter = {
                load: jest.fn().mockResolvedValue({
                    columns: [
                        { id: '123', width: null },
                        { id: '234', width: 245 },
                    ],
                }),
                save: jest.fn(),
            }

            const { findByText } = render(
                <UsesPreferences grid={grid} preferencesAdapter={adapter} />
            )

            await findByText('true')

            expect(dispatch).toHaveBeenCalledWith({
                type: 'applyPreferences',
                payload: {
                    preferences: {
                        columns: [
                            { id: '123', width: null },
                            { id: '234', width: 245 },
                        ],
                    },
                    multiSelection: true,
                },
            })
        })

        it('should load changes if adapter changes', async () => {
            const dispatch = jest.spyOn(grid, 'dispatch')

            const adapterOne: GridPreferencesAdapter = {
                load: jest.fn().mockResolvedValue({
                    columns: [
                        { id: '123', width: null },
                        { id: '234', width: 245 },
                    ],
                }),
                save: jest.fn(),
            }

            const adapterTwo: GridPreferencesAdapter = {
                load: jest.fn().mockResolvedValue({
                    columns: [
                        { id: '123', width: 123 },
                        { id: '234', width: 245 },
                    ],
                }),
                save: jest.fn(),
            }

            const { findByText, rerender } = render(
                <UsesPreferences grid={grid} preferencesAdapter={adapterOne} />
            )

            await findByText('true')

            rerender(
                <UsesPreferences grid={grid} preferencesAdapter={adapterTwo} />
            )

            await findByText('true')

            expect(dispatch).toHaveBeenCalledWith({
                type: 'applyPreferences',
                payload: {
                    preferences: {
                        columns: [
                            { id: '123', width: 123 },
                            { id: '234', width: 245 },
                        ],
                    },
                    multiSelection: false,
                },
            })
        })

        it('should not attempt to apply changes if component is unmounted', async () => {
            const dispatch = jest.spyOn(grid, 'dispatch')

            const adapter: GridPreferencesAdapter = {
                load: jest.fn().mockResolvedValue({
                    columns: [
                        { id: '123', width: null },
                        { id: '234', width: 245 },
                    ],
                }),
                save: jest.fn(),
            }

            const { unmount } = render(
                <UsesPreferences grid={grid} preferencesAdapter={adapter} />
            )

            unmount()

            /* Give load promise time to resolve */
            await Promise.resolve()

            expect(dispatch).not.toHaveBeenCalled()
        })
    })

    describe('when a change happens', () => {
        it('should call save with the column state', async () => {
            jest.spyOn(
                grid.selectors,
                'selectColumnPreferences'
            ).mockReturnValue([{ id: '123', width: 25 }])

            const adapter: GridPreferencesAdapter = {
                load: jest.fn().mockResolvedValue({}),
                save: jest.fn(),
            }

            const { findByText } = render(
                <UsesPreferences grid={grid} preferencesAdapter={adapter} />
            )

            await findByText('true')

            grid.events.emit('onColumnStateChange')

            expect(adapter.save).toHaveBeenCalledWith({
                columns: [{ id: '123', width: 25 }],
            })
        })
    })
})
