import {
  describe,
  test,
  expect,
  vi,
  beforeEach,
  afterEach,
  type MockInstance,
} from 'vitest'
import { downloadToCSV, downloadToPNG } from './exports'
import { createRef } from 'react'
import type { RefObject } from 'react'

// Mock html2canvas
vi.mock('html2canvas', () => ({
  default: vi.fn(),
}))

describe('downloadToCSV', () => {
  test('has correct label', () => {
    expect(downloadToCSV.label).toBe('CSV')
  })

  test('has icon', () => {
    expect(downloadToCSV.icon).toBeTruthy()
  })

  test('converts data to CSV format', async () => {
    const data: string[][] = [
      ['Name', 'Age', 'City'],
      ['John', '30', 'New York'],
      ['Jane', '25', 'Los Angeles'],
    ]

    const result = await downloadToCSV.modifier(data)

    expect(result).toBeTruthy()
    expect(result).toContain('blob:')
  })

  test('creates blob URL from CSV data', async () => {
    const data: string[][] = [
      ['a', 'b'],
      ['c', 'd'],
    ]

    const result = await downloadToCSV.modifier(data)

    expect(result).toBeTruthy()
    expect(typeof result).toBe('string')
    expect(result!.startsWith('blob:')).toBe(true)
  })

  test('callback revokes object URL', () => {
    const revokeObjectURLSpy = vi.spyOn(URL, 'revokeObjectURL')
    const testUrl = 'blob:test-url'

    downloadToCSV.callback?.(testUrl)

    expect(revokeObjectURLSpy).toHaveBeenCalledWith(testUrl)

    revokeObjectURLSpy.mockRestore()
  })

  test('handles empty data array', async () => {
    const data: unknown[][] = []

    const result = await downloadToCSV.modifier(data)

    expect(result).toBeTruthy()
  })

  test('handles single row data', async () => {
    const data: string[][] = [['single', 'row']]

    const result = await downloadToCSV.modifier(data)

    expect(result).toBeTruthy()
    expect(result).toContain('blob:')
  })

  test('handles data with special characters', async () => {
    const data: string[][] = [
      ['Name', 'Description'],
      ['Test', 'Hello, World!'],
    ]

    const result = await downloadToCSV.modifier(data)

    expect(result).toBeTruthy()
  })
})

describe('downloadToPNG', () => {
  let mockRef: RefObject<HTMLDivElement>
  let mockElement: HTMLDivElement
  let warnSpy: MockInstance<typeof console.warn>

  beforeEach(() => {
    warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
    // Create a real DOM element
    mockElement = document.createElement('div')
    mockElement.style.width = '800px'
    mockElement.style.height = '600px'
    document.body.appendChild(mockElement)

    mockRef = createRef() as RefObject<HTMLDivElement>
    // Manually set the current property
    Object.defineProperty(mockRef, 'current', {
      writable: true,
      value: mockElement,
    })
  })

  afterEach(() => {
    document.body.removeChild(mockElement)
    warnSpy.mockRestore()
    vi.clearAllMocks()
  })

  test('returns download item with correct label', () => {
    expect(downloadToPNG.label).toBe('PNG')
  })

  test('has icon', () => {
    expect(downloadToPNG.icon).toBeTruthy()
  })

  test('modifier returns undefined when ref is null', async () => {
    const nullRef = createRef() as RefObject<HTMLDivElement>

    const result = await downloadToPNG.modifier(nullRef)

    expect(result).toBeUndefined()
    expect(warnSpy).toHaveBeenCalled()
  })

  test('modifier returns undefined when ref is a function', async () => {
    const functionRef = vi.fn() as unknown as RefObject<HTMLDivElement>

    const result = await downloadToPNG.modifier(functionRef)

    expect(result).toBeUndefined()
    expect(warnSpy).toHaveBeenCalled()
  })

  test('removes toolbar and actions containers from clone', async () => {
    const html2canvas = (await import('html2canvas')).default as ReturnType<
      typeof vi.fn
    >

    const toolbar = document.createElement('div')
    toolbar.className = 'widget-toolbar-container'
    mockElement.appendChild(toolbar)

    const actions = document.createElement('div')
    actions.className = 'widget-wrapper-actions'
    mockElement.appendChild(actions)

    const mockCanvas = document.createElement('canvas')
    mockCanvas.toDataURL = vi.fn().mockReturnValue('data:image/png;base64,test')

    let cloneElement: HTMLElement | undefined

    html2canvas.mockImplementation((element: HTMLElement) => {
      cloneElement = element
      return mockCanvas
    })

    await downloadToPNG.modifier(mockRef)

    // The clone should not have toolbar or actions
    expect(cloneElement?.querySelector('.widget-toolbar-container')).toBeNull()
    expect(cloneElement?.querySelector('.widget-wrapper-actions')).toBeNull()
  })

  test('does not modify original element', async () => {
    const html2canvas = (await import('html2canvas')).default as ReturnType<
      typeof vi.fn
    >

    const toolbar = document.createElement('div')
    toolbar.className = 'widget-toolbar-container'
    mockElement.appendChild(toolbar)

    const actions = document.createElement('div')
    actions.className = 'widget-wrapper-actions'
    mockElement.appendChild(actions)

    const mockCanvas = document.createElement('canvas')
    mockCanvas.toDataURL = vi.fn().mockReturnValue('data:image/png;base64,test')

    html2canvas.mockResolvedValue(mockCanvas)

    await downloadToPNG.modifier(mockRef)

    // Original element should still have toolbar and actions
    expect(mockElement.querySelector('.widget-toolbar-container')).toBe(toolbar)
    expect(mockElement.querySelector('.widget-wrapper-actions')).toBe(actions)
  })

  test('calls html2canvas with correct options', async () => {
    const html2canvas = (await import('html2canvas')).default as ReturnType<
      typeof vi.fn
    >

    const mockCanvas = document.createElement('canvas')
    mockCanvas.toDataURL = vi.fn().mockReturnValue('data:image/png;base64,test')

    html2canvas.mockResolvedValue(mockCanvas)

    await downloadToPNG.modifier(mockRef)

    expect(html2canvas).toHaveBeenCalledWith(
      mockElement,
      expect.objectContaining({
        useCORS: true,
        scale: 2,
        backgroundColor: '#fff',
      }),
    )
  })

  test('returns PNG data URL', async () => {
    const html2canvas = (await import('html2canvas')).default as ReturnType<
      typeof vi.fn
    >

    const mockCanvas = document.createElement('canvas')
    const testDataUrl = 'data:image/png;base64,testdata'
    mockCanvas.toDataURL = vi.fn().mockReturnValue(testDataUrl)

    html2canvas.mockResolvedValue(mockCanvas)

    const result = await downloadToPNG.modifier(mockRef)

    expect(result).toBe(testDataUrl)
  })

  test('calculates dimensions from clone element', async () => {
    const html2canvas = (await import('html2canvas')).default as ReturnType<
      typeof vi.fn
    >

    const toolbar = document.createElement('div')
    toolbar.className = 'widget-toolbar-container'
    toolbar.style.height = '50px'
    mockElement.appendChild(toolbar)

    const mockCanvas = document.createElement('canvas')
    mockCanvas.toDataURL = vi.fn().mockReturnValue('data:image/png;base64,test')

    let calledElement: HTMLElement | null = null

    html2canvas.mockImplementation((element) => {
      calledElement = element as HTMLElement
      return mockCanvas
    })

    await downloadToPNG.modifier(mockRef)

    // Should be called with a clone (different element)
    expect(calledElement).not.toBe(mockElement)
    expect(html2canvas).toHaveBeenCalledWith(
      calledElement,
      expect.objectContaining({
        height: expect.any(Number) as number,
        width: expect.any(Number) as number,
      }),
    )
  })

  test('handles element without toolbar or actions', async () => {
    const html2canvas = (await import('html2canvas')).default as ReturnType<
      typeof vi.fn
    >

    const mockCanvas = document.createElement('canvas')
    mockCanvas.toDataURL = vi.fn().mockReturnValue('data:image/png;base64,test')

    html2canvas.mockResolvedValue(mockCanvas)

    const result = await downloadToPNG.modifier(mockRef)

    expect(result).toBeTruthy()
    expect(result).toContain('data:image/png')
  })
})
