import React from 'react'
import { renderHook, act } from '@testing-library/react'
import { useRangeCopy } from './use-range-copy'
import { GridProvider } from '../context/grid-context'
import type { GridRangeSelection, GridRowData } from '../types'
import type { SpreadsheetRangeCopy } from '../grids/spreadsheet-grid/types'
import type { UseGridReturn } from './use-grid'
import { createGrid } from './use-grid'

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

    const mockContainer = document.createElement('div')
    const mockRange: GridRangeSelection<GridRowData> = {
        from: { rowId: 'row1', columnId: 'col1' },
        to: { rowId: 'row2', columnId: 'col2' },
    }
    const mockRangeData = {
        rowIds: ['row1', 'row2'],
        columnIds: ['col1', 'col2'],
    }

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

        grid.dispatch({
            type: 'applyProps',
            payload: {
                actionsMenuPresent: false,
                columns: [
                    {
                        id: 'col1',
                        label: 'Column 1',
                        cell: {
                            label({ value }) {
                                return `Label: ${value}`
                            },
                        },
                    },
                    {
                        id: 'col2',
                        label: 'Column 2',
                        cell: {
                            label({ value }) {
                                return `Label: ${value}`
                            },
                        },
                    },
                ],
                rows: [
                    { id: 'row1', col1: 'R1C1', col2: 'R1C2' },
                    { id: 'row2', col1: 'R2C1', col2: 'R2C2' },
                ],
                footerEnabled: false,
                rowDrag: { enabled: false },
            },
        })

        jest.spyOn(grid.selectors, 'selectRangeSelection').mockReturnValue(
            mockRange
        )
        jest.spyOn(grid.selectors, 'selectRangeIds').mockReturnValue(
            mockRangeData
        )
        jest.spyOn(grid.selectors, 'selectIsSpreadsheet').mockReturnValue(true)
    })

    it('should return undefined when not a spreadsheet', () => {
        jest.mocked(grid.selectors.selectIsSpreadsheet).mockReturnValue(false)

        const { result } = renderHook(
            () => useRangeCopy('values', mockContainer),
            {
                wrapper: ({ children }) => (
                    <GridProvider {...grid}>{children}</GridProvider>
                ),
            }
        )

        expect(result.current).toBeUndefined()
    })

    it('should return handleCopy function when is a spreadsheet', () => {
        const { result } = renderHook(
            () => useRangeCopy('values', mockContainer),
            {
                wrapper: ({ children }) => (
                    <GridProvider {...grid}>{children}</GridProvider>
                ),
            }
        )

        expect(result.current).toBeInstanceOf(Function)
    })

    it('handleCopy should do nothing when there is no range selection', () => {
        jest.mocked(grid.selectors.selectRangeSelection).mockReturnValue(null)

        const { result } = renderHook(
            () => useRangeCopy('values', mockContainer),
            {
                wrapper: ({ children }) => (
                    <GridProvider {...grid}>{children}</GridProvider>
                ),
            }
        )
        const mockEvent = {
            preventDefault: jest.fn(),
        } as unknown as React.ClipboardEvent

        act(() => {
            result.current?.(mockEvent)
        })

        expect(mockEvent.preventDefault).not.toHaveBeenCalled()
    })

    it('handleCopy should process values when onRangeCopyIncoming is "values"', () => {
        const mockClipboardData = {
            setData: jest.fn(),
        }

        const mockEvent = {
            preventDefault: jest.fn(),
            clipboardData: mockClipboardData,
        } as unknown as React.ClipboardEvent

        const { result } = renderHook(
            () => useRangeCopy('values', mockContainer),
            {
                wrapper: ({ children }) => (
                    <GridProvider {...grid}>{children}</GridProvider>
                ),
            }
        )

        act(() => {
            result.current?.(mockEvent)
        })

        expect(mockEvent.preventDefault).toHaveBeenCalled()
        expect(mockClipboardData.setData).toHaveBeenCalledTimes(3)
        expect(mockClipboardData.setData).toHaveBeenCalledWith(
            'text/plain',
            expect.any(String)
        )
        expect(mockClipboardData.setData.mock.calls[0][1]).toMatchSnapshot()
        expect(mockClipboardData.setData).toHaveBeenCalledWith(
            'text/html',
            expect.any(String)
        )
        expect(mockClipboardData.setData.mock.calls[1][1]).toMatchSnapshot()
        expect(mockClipboardData.setData).toHaveBeenCalledWith(
            'application/x-pvds-grid',
            expect.any(String)
        )
        expect(mockClipboardData.setData.mock.calls[2][1]).toMatchSnapshot()
        expect(mockContainer).toHaveAttribute(
            'data-pvds-grid-range-copied',
            'true'
        )
    })

    it('handleCopy should properly handle column span', () => {
        grid.dispatch({
            type: 'applyProps',
            payload: {
                actionsMenuPresent: false,
                columns: [
                    {
                        id: 'col1',
                        label: 'Column 1',
                        cell: {
                            label({ value }) {
                                return `Label: ${value}`
                            },
                            colSpan({ row }) {
                                if (row.id === 'row1') return []
                                return ['col1', 'col2']
                            },
                        },
                    },
                    {
                        id: 'col2',
                        label: 'Column 2',
                        cell: {
                            label({ value }) {
                                return `Label: ${value}`
                            },
                        },
                    },
                ],
                rows: [
                    { id: 'row1', col1: 'R1C1', col2: 'R1C2' },
                    { id: 'row2', col1: 'R2C1', col2: 'R2C2' },
                ],
                footerEnabled: false,
                rowDrag: { enabled: false },
            },
        })

        const mockClipboardData = {
            setData: jest.fn(),
        }

        const mockEvent = {
            preventDefault: jest.fn(),
            clipboardData: mockClipboardData,
        } as unknown as React.ClipboardEvent

        const { result } = renderHook(
            () => useRangeCopy('values', mockContainer),
            {
                wrapper: ({ children }) => (
                    <GridProvider {...grid}>{children}</GridProvider>
                ),
            }
        )

        act(() => {
            result.current?.(mockEvent)
        })

        expect(mockEvent.preventDefault).toHaveBeenCalled()
        expect(mockClipboardData.setData).toHaveBeenCalledTimes(3)
        expect(mockClipboardData.setData).toHaveBeenCalledWith(
            'text/plain',
            expect.any(String)
        )
        expect(mockClipboardData.setData.mock.calls[0][1]).toMatchSnapshot()
        expect(mockClipboardData.setData).toHaveBeenCalledWith(
            'text/html',
            expect.any(String)
        )
        expect(mockClipboardData.setData.mock.calls[1][1]).toMatchSnapshot()
        expect(mockClipboardData.setData).toHaveBeenCalledWith(
            'application/x-pvds-grid',
            expect.any(String)
        )
        expect(mockClipboardData.setData.mock.calls[2][1]).toMatchSnapshot()
        expect(mockContainer).toHaveAttribute(
            'data-pvds-grid-range-copied',
            'true'
        )
    })

    it('handleCopy should process labels when onRangeCopyIncoming is "labels"', () => {
        const mockClipboardData = {
            setData: jest.fn(),
        }

        const mockEvent = {
            preventDefault: jest.fn(),
            clipboardData: mockClipboardData,
        } as unknown as React.ClipboardEvent

        const { result } = renderHook(
            () => useRangeCopy('labels', mockContainer),
            {
                wrapper: ({ children }) => (
                    <GridProvider {...grid}>{children}</GridProvider>
                ),
            }
        )

        act(() => {
            result.current?.(mockEvent)
        })

        expect(mockEvent.preventDefault).toHaveBeenCalled()
        expect(mockClipboardData.setData).toHaveBeenCalledTimes(3)
        expect(mockClipboardData.setData).toHaveBeenCalledWith(
            'text/plain',
            expect.any(String)
        )
        expect(mockClipboardData.setData.mock.calls[0][1]).toMatchSnapshot()
        expect(mockClipboardData.setData).toHaveBeenCalledWith(
            'text/html',
            expect.any(String)
        )
        expect(mockClipboardData.setData.mock.calls[1][1]).toMatchSnapshot()
        expect(mockClipboardData.setData).toHaveBeenCalledWith(
            'application/x-pvds-grid',
            expect.any(String)
        )
        expect(mockClipboardData.setData.mock.calls[2][1]).toMatchSnapshot()
    })

    it('handleCopy should call custom function when onRangeCopyIncoming is a function', () => {
        const customCopyFn: SpreadsheetRangeCopy<GridRowData> = jest.fn()

        const mockEvent = {
            preventDefault: jest.fn(),
        } as unknown as React.ClipboardEvent

        const { result } = renderHook(
            () => useRangeCopy(customCopyFn, mockContainer),
            {
                wrapper: ({ children }) => (
                    <GridProvider {...grid}>{children}</GridProvider>
                ),
            }
        )

        act(() => {
            result.current?.(mockEvent)
        })

        expect(mockEvent.preventDefault).toHaveBeenCalled()
        expect(customCopyFn).toHaveBeenCalledWith(
            mockRange,
            mockRangeData,
            mockEvent
        )
    })

    it('should remove attribute after animation duration', () => {
        jest.useFakeTimers()

        const mockEvent = {
            preventDefault: jest.fn(),
            clipboardData: {
                setData: jest.fn(),
            },
        } as unknown as React.ClipboardEvent

        const { result } = renderHook(
            () => useRangeCopy('values', mockContainer),
            {
                wrapper: ({ children }) => (
                    <GridProvider {...grid}>{children}</GridProvider>
                ),
            }
        )

        act(() => {
            result.current?.(mockEvent)
        })

        expect(mockContainer).toHaveAttribute(
            'data-pvds-grid-range-copied',
            'true'
        )

        act(() => {
            jest.advanceTimersByTime(300)
        })

        expect(mockContainer).not.toHaveAttribute('data-pvds-grid-range-copied')

        jest.useRealTimers()
    })
})
