import { describe, test, expect, beforeEach, vi } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { Download } from './download'
import { useWidgetStore } from '../../../widgets/stores'
import type { DownloadItem } from './types'

describe('Download', () => {
  const widgetId = 'test-download-widget'

  beforeEach(() => {
    // Clear store before each test
    useWidgetStore.getState().clearWidgets()
  })

  test('disables button when download items array is empty', () => {
    render(<Download id={widgetId} items={[]} />)
    const button = screen.getByRole('button', { name: 'download options' })
    expect(button.hasAttribute('disabled')).toBeTruthy()
  })

  test('renders download button when download options exist', () => {
    const mockDownload: DownloadItem[] = [
      {
        id: 'csv',
        label: 'CSV',
        modifier: vi.fn().mockResolvedValue('data:text/csv;base64,'),
      },
    ]

    useWidgetStore.getState().setWidget(widgetId, {
      data: [['a', 'b']],
    })

    render(<Download id={widgetId} items={mockDownload} />)

    const button = screen.getByRole('button', { name: 'download options' })
    expect(button).toBeTruthy()
  })

  test('uses custom aria label when provided', () => {
    const mockDownload: DownloadItem[] = [
      {
        id: 'csv',
        label: 'CSV',
        modifier: vi.fn().mockResolvedValue('data:text/csv;base64,'),
      },
    ]

    render(
      <Download
        id={widgetId}
        items={mockDownload}
        labels={{ ariaLabel: 'custom download' }}
      />,
    )

    const button = screen.getByRole('button', { name: 'custom download' })
    expect(button).toBeTruthy()
  })

  test('opens menu when button is clicked', async () => {
    const mockDownload: DownloadItem[] = [
      {
        id: 'csv',
        label: 'CSV',
        modifier: vi.fn().mockResolvedValue('data:text/csv;base64,'),
      },
      {
        id: 'png',
        label: 'PNG',
        modifier: vi.fn().mockResolvedValue('data:image/png;base64,'),
      },
    ]

    render(<Download id={widgetId} items={mockDownload} />)

    const button = screen.getByRole('button', { name: 'download options' })
    fireEvent.click(button)

    await waitFor(() => {
      expect(screen.getByText('CSV')).toBeTruthy()
      expect(screen.getByText('PNG')).toBeTruthy()
    })
  })

  test('renders menu items with icons when provided', async () => {
    const mockIcon = <span data-testid='test-icon'>Icon</span>
    const mockDownload: DownloadItem[] = [
      {
        id: 'csv',
        label: 'CSV',
        icon: mockIcon,
        modifier: vi.fn().mockResolvedValue('data:text/csv;base64,'),
      },
    ]

    render(<Download id={widgetId} items={mockDownload} />)

    const button = screen.getByRole('button', { name: 'download options' })
    fireEvent.click(button)

    await waitFor(() => {
      expect(screen.getByTestId('test-icon')).toBeTruthy()
    })
  })

  test('calls modifier and triggers download when menu item is clicked', async () => {
    const mockModifier = vi.fn().mockResolvedValue('data:text/csv;base64,test')
    const mockCallback = vi.fn()
    const mockDownload: DownloadItem[] = [
      {
        id: 'csv',
        label: 'CSV',
        filename: 'test.csv',
        modifier: mockModifier,
        callback: mockCallback,
      },
    ]

    useWidgetStore.getState().setWidget(widgetId, {
      data: [['test', 'data']],
    })

    // Mock document methods
    const createElementSpy = vi.spyOn(document, 'createElement')
    const appendChildSpy = vi.spyOn(document.body, 'appendChild')
    const removeChildSpy = vi.spyOn(document.body, 'removeChild')

    render(<Download id={widgetId} items={mockDownload} />)

    const button = screen.getByRole('button', { name: 'download options' })
    fireEvent.click(button)

    const csvMenuItem = await screen.findByText('CSV')
    fireEvent.click(csvMenuItem)

    await waitFor(() => {
      expect(mockModifier).toHaveBeenCalledWith([['test', 'data']])
      expect(createElementSpy).toHaveBeenCalledWith('a')
      expect(mockCallback).toHaveBeenCalled()
    })

    createElementSpy.mockRestore()
    appendChildSpy.mockRestore()
    removeChildSpy.mockRestore()
  })

  test('shows loading spinner during download', async () => {
    const mockModifier = vi.fn().mockImplementation(
      () =>
        new Promise((resolve) => {
          setTimeout(() => resolve('data:text/csv;base64,test'), 100)
        }),
    )

    const mockDownload: DownloadItem[] = [
      {
        id: 'csv',
        label: 'CSV',
        modifier: mockModifier,
      },
    ]

    render(<Download id={widgetId} items={mockDownload} />)

    const button = screen.getByRole('button', { name: 'download options' })
    fireEvent.click(button)

    const csvMenuItem = await screen.findByText('CSV')
    fireEvent.click(csvMenuItem)

    // Check that loading spinner is shown during download
    await waitFor(() => {
      expect(screen.getByRole('progressbar')).toBeTruthy()
    })

    // Wait for download to complete and spinner to disappear
    await waitFor(
      () => {
        expect(screen.queryByRole('progressbar')).toBeNull()
      },
      { timeout: 200 },
    )
  })

  test('handles disabled menu items', async () => {
    const mockDownload: DownloadItem[] = [
      {
        id: 'csv',
        label: 'CSV',
        disabled: true,
        modifier: vi.fn().mockResolvedValue('data:text/csv;base64,'),
      },
    ]

    render(<Download id={widgetId} items={mockDownload} />)

    const button = screen.getByRole('button', { name: 'download options' })
    fireEvent.click(button)

    const csvMenuItem = await screen.findByText('CSV')
    expect(csvMenuItem.closest('li')?.getAttribute('aria-disabled')).toBe(
      'true',
    )
  })

  test('closes menu after clicking a menu item', async () => {
    const mockDownload: DownloadItem[] = [
      {
        id: 'csv',
        label: 'CSV',
        modifier: vi.fn().mockResolvedValue('data:text/csv;base64,'),
      },
    ]

    render(<Download id={widgetId} items={mockDownload} />)

    const button = screen.getByRole('button', { name: 'download options' })
    fireEvent.click(button)

    const csvMenuItem = await screen.findByText('CSV')
    fireEvent.click(csvMenuItem)

    await waitFor(() => {
      expect(screen.queryByRole('menu')).toBeNull()
    })
  })

  test('does not download if modifier returns undefined', async () => {
    const mockModifier = vi.fn().mockResolvedValue(undefined)
    const mockCallback = vi.fn()
    const mockDownload: DownloadItem[] = [
      {
        id: 'csv',
        label: 'CSV',
        modifier: mockModifier,
        callback: mockCallback,
      },
    ]

    const createElementSpy = vi.spyOn(document, 'createElement')

    render(<Download id={widgetId} items={mockDownload} />)

    const button = screen.getByRole('button', { name: 'download options' })
    fireEvent.click(button)

    const csvMenuItem = await screen.findByText('CSV')
    fireEvent.click(csvMenuItem)

    await waitFor(() => {
      expect(mockModifier).toHaveBeenCalled()
    })

    // Should not create download link (check for 'a' element specifically)
    expect(createElementSpy).not.toHaveBeenCalledWith('a')
    expect(mockCallback).not.toHaveBeenCalled()

    createElementSpy.mockRestore()
  })

  test('stops event propagation on button click', () => {
    const mockDownload: DownloadItem[] = [
      {
        id: 'csv',
        label: 'CSV',
        modifier: vi.fn().mockResolvedValue('data:text/csv;base64,'),
      },
    ]

    const parentClickHandler = vi.fn()

    render(
      <div
        role='button'
        tabIndex={0}
        onClick={parentClickHandler}
        onKeyDown={parentClickHandler}
      >
        <Download id={widgetId} items={mockDownload} />
      </div>,
    )

    const button = screen.getByRole('button', { name: 'download options' })
    fireEvent.click(button)

    expect(parentClickHandler).not.toHaveBeenCalled()
  })

  test('uses widget id as default filename when filename is not provided', async () => {
    const mockModifier = vi.fn().mockResolvedValue('data:text/csv;base64,test')
    const mockDownload: DownloadItem[] = [
      {
        id: 'csv',
        label: 'CSV',
        modifier: mockModifier,
      },
    ]

    let capturedLink: HTMLAnchorElement | null = null
    const originalCreateElement = document.createElement.bind(document)
    const createElementSpy = vi
      .spyOn(document, 'createElement')
      .mockImplementation((tagName: string) => {
        const element = originalCreateElement(tagName)
        if (tagName === 'a') {
          capturedLink = element as HTMLAnchorElement
        }
        return element
      })

    render(<Download id={widgetId} items={mockDownload} />)

    const button = screen.getByRole('button', { name: 'download options' })
    fireEvent.click(button)

    const csvMenuItem = await screen.findByText('CSV')
    fireEvent.click(csvMenuItem)

    await waitFor(() => {
      expect(capturedLink?.download).toBe(widgetId)
    })

    createElementSpy.mockRestore()
  })
})
