import { describe, test, expect, beforeEach, vi } from 'vitest'
import { render } from '@testing-library/react'
import { Echart } from './echart'
import { useWidgetStore } from '../stores/widget-store'
import type { EchartWidgetState } from './types'
import type { EChartsOption } from 'echarts'
import * as echarts from 'echarts'

// Mock echarts module
vi.mock('echarts', () => {
  const mockChart = {
    setOption: vi.fn(),
    dispose: vi.fn(),
    resize: vi.fn(),
    on: vi.fn(),
    off: vi.fn(),
    getZr: vi.fn(() => ({
      setCursorStyle: vi.fn(),
    })),
  }

  return {
    init: vi.fn(() => mockChart),
  }
})

// Mock MUI theme
vi.mock('@mui/material', async () => {
  const actual = await vi.importActual('@mui/material')
  return {
    ...actual,
    useTheme: () => ({
      spacing: (value: number) => `${value * 8}px`,
      palette: {
        secondary: {
          main: '#47db99',
        },
        grey: {
          400: '#bdbdbd',
          900: '#212121',
        },
        common: {
          white: '#ffffff',
          black: '#000000',
        },
        qualitative: {
          bold: {
            1: '#7F3C8D',
            2: '#11A579',
            3: '#3969AC',
            4: '#F2B701',
            5: '#E73F74',
            6: '#80BA5A',
            7: '#E68310',
            8: '#008695',
            9: '#CF1C90',
            10: '#F97B72',
          },
        },
      },
      typography: {
        caption: {
          fontFamily: 'Roboto, sans-serif',
        },
      },
    }),
  }
})

describe('Echart', () => {
  let mockChart: {
    setOption: ReturnType<typeof vi.fn>
    dispose: ReturnType<typeof vi.fn>
    resize: ReturnType<typeof vi.fn>
    on: ReturnType<typeof vi.fn>
    off: ReturnType<typeof vi.fn>
    getZr: ReturnType<typeof vi.fn>
  }
  let mockResizeObserver: {
    observe: ReturnType<typeof vi.fn>
    disconnect: ReturnType<typeof vi.fn>
    unobserve: ReturnType<typeof vi.fn>
  }

  const basicOption: EChartsOption = {
    title: {
      text: 'Test Chart',
    },
    series: [
      {
        type: 'bar',
        data: [1, 2, 3, 4, 5],
      },
    ],
  }

  beforeEach(() => {
    vi.clearAllMocks()
    useWidgetStore.getState().clearWidgets()

    // Create a fresh mock chart instance for each test
    mockChart = {
      setOption: vi.fn(),
      dispose: vi.fn(),
      resize: vi.fn(),
      on: vi.fn(),
      off: vi.fn(),
      getZr: vi.fn(() => ({
        setCursorStyle: vi.fn(),
      })),
    }

    // Make echarts.init return our mock chart
    vi.mocked(echarts.init).mockReturnValue(
      mockChart as unknown as echarts.ECharts,
    )

    // Mock ResizeObserver
    mockResizeObserver = {
      observe: vi.fn(),
      disconnect: vi.fn(),
      unobserve: vi.fn(),
    }

    global.ResizeObserver = class {
      observe = mockResizeObserver.observe
      disconnect = mockResizeObserver.disconnect
      unobserve = mockResizeObserver.unobserve
    } as unknown as typeof ResizeObserver
  })

  test('returns null when widget does not exist in store', () => {
    const { container } = render(<Echart id='non-existent-widget' />)
    expect(container.firstChild).toBeNull()
  })

  test('renders EchartUI when widget exists in store', () => {
    // Set up widget in store
    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: basicOption,
    } as EchartWidgetState)

    const { container } = render(<Echart id='test-echart' />)

    const div = container.querySelector('div')
    expect(div).toBeTruthy()
    expect(echarts.init).toHaveBeenCalled()
  })

  test('passes option from widget data to EchartUI', () => {
    const customOption: EChartsOption = {
      title: {
        text: 'Custom Chart',
      },
      series: [
        {
          type: 'line',
          data: [10, 20, 30],
        },
      ],
    }

    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: customOption,
    } as EchartWidgetState)

    render(<Echart id='test-echart' />)

    expect(mockChart.setOption).toHaveBeenCalledWith(
      expect.objectContaining({
        title: {
          text: 'Custom Chart',
        },
        series: [
          {
            type: 'line',
            data: [10, 20, 30],
          },
        ],
      }),
      {
        lazyUpdate: true,
        notMerge: true,
      },
    )
  })

  test('passes onEvents from widget to EchartUI', () => {
    const clickHandler = vi.fn()
    const hoverHandler = vi.fn()

    useWidgetStore.getState().setWidget<EchartWidgetState>('test-echart', {
      type: 'echart',
      option: basicOption,
      onEvents: {
        click: clickHandler,
        mouseover: hoverHandler,
      },
    })

    render(<Echart id='test-echart' />)

    expect(mockChart.on).toHaveBeenCalledWith('click', clickHandler)
    expect(mockChart.on).toHaveBeenCalledWith('mouseover', hoverHandler)
  })

  test('passes init options from widget to EchartUI', () => {
    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: basicOption,
      init: {
        renderer: 'canvas',
        height: 500,
        width: 800,
      },
    } as EchartWidgetState)

    render(<Echart id='test-echart' />)

    expect(echarts.init).toHaveBeenCalledWith(
      expect.any(HTMLDivElement),
      null,
      expect.objectContaining({
        renderer: 'canvas',
        height: 500,
        width: 800,
      }),
    )
  })

  test('handles widget with undefined onEvents', () => {
    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: basicOption,
      onEvents: undefined,
    } as EchartWidgetState)

    expect(() => {
      render(<Echart id='test-echart' />)
    }).not.toThrow()
  })

  test('handles widget with undefined init', () => {
    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: basicOption,
      init: undefined,
    } as EchartWidgetState)

    render(<Echart id='test-echart' />)

    expect(echarts.init).toHaveBeenCalledWith(
      expect.any(HTMLDivElement),
      null,
      expect.objectContaining({
        renderer: 'svg',
        height: 304,
      }),
    )
  })

  test('re-renders when widget data changes', () => {
    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: basicOption,
    } as EchartWidgetState)

    const { rerender } = render(<Echart id='test-echart' />)

    expect(mockChart.setOption).toHaveBeenCalledWith(basicOption, {
      lazyUpdate: true,
      notMerge: true,
    })

    const newOption: EChartsOption = {
      series: [
        {
          type: 'pie',
          data: [{ value: 100 }, { value: 200 }],
        },
      ],
    }

    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: newOption,
    } as EchartWidgetState)

    rerender(<Echart id='test-echart' />)

    expect(mockChart.setOption).toHaveBeenCalledWith(newOption, {
      lazyUpdate: true,
      notMerge: true,
    })
  })

  test('returns null when widget is removed from store', () => {
    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: basicOption,
    } as EchartWidgetState)

    const { container, rerender } = render(<Echart id='test-echart' />)

    expect(container.querySelector('div')).toBeTruthy()

    useWidgetStore.getState().removeWidget('test-echart')
    rerender(<Echart id='test-echart' />)

    expect(container.firstChild).toBeNull()
  })

  test('handles complex chart options with multiple series', () => {
    const complexOption: EChartsOption = {
      title: {
        text: 'Multi-Series Chart',
      },
      tooltip: {
        trigger: 'axis',
      },
      legend: {
        data: ['Series 1', 'Series 2', 'Series 3'],
      },
      xAxis: {
        type: 'category',
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
      },
      yAxis: {
        type: 'value',
      },
      series: [
        {
          name: 'Series 1',
          type: 'line',
          data: [10, 20, 30, 40, 50],
        },
        {
          name: 'Series 2',
          type: 'bar',
          data: [15, 25, 35, 45, 55],
        },
        {
          name: 'Series 3',
          type: 'scatter',
          data: [5, 15, 25, 35, 45],
        },
      ],
    }

    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: complexOption,
    } as EchartWidgetState)

    render(<Echart id='test-echart' />)

    expect(mockChart.setOption).toHaveBeenCalledWith(complexOption, {
      lazyUpdate: true,
      notMerge: true,
    })
  })

  test('handles widget with all optional properties', () => {
    const clickHandler = vi.fn()
    const fullWidget: EchartWidgetState = {
      id: 'test-echart',
      type: 'echart',
      data: [],
      isFetching: false,
      option: basicOption,
      isLoading: false,
      visible: true,
      onEvents: {
        click: clickHandler,
      },
      init: {
        renderer: 'canvas',
        height: 600,
      },
    }

    useWidgetStore.getState().setWidget('test-echart', fullWidget)

    render(<Echart id='test-echart' />)

    expect(echarts.init).toHaveBeenCalledWith(
      expect.any(HTMLDivElement),
      null,
      expect.objectContaining({
        renderer: 'canvas',
        height: 600,
      }),
    )
    expect(mockChart.setOption).toHaveBeenCalledWith(basicOption, {
      lazyUpdate: true,
      notMerge: true,
    })
    expect(mockChart.on).toHaveBeenCalledWith('click', clickHandler)
  })

  test('handles multiple Echart instances with different IDs', () => {
    useWidgetStore.getState().setWidget('chart-1', {
      id: 'chart-1',
      type: 'echart',
      option: basicOption,
    } as EchartWidgetState)

    useWidgetStore.getState().setWidget('chart-2', {
      id: 'chart-2',
      type: 'echart',
      option: {
        series: [{ type: 'line', data: [5, 10, 15] }],
      },
    } as EchartWidgetState)

    const { container: container1 } = render(<Echart id='chart-1' />)
    const { container: container2 } = render(<Echart id='chart-2' />)

    expect(container1.querySelector('div')).toBeTruthy()
    expect(container2.querySelector('div')).toBeTruthy()
    expect(echarts.init).toHaveBeenCalledTimes(2)
  })

  test('handles empty series data', () => {
    const emptyOption: EChartsOption = {
      series: [],
    }

    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: emptyOption,
    } as EchartWidgetState)

    render(<Echart id='test-echart' />)

    expect(mockChart.setOption).toHaveBeenCalledWith(emptyOption, {
      lazyUpdate: true,
      notMerge: true,
    })
  })

  test('handles widget data as unknown type (runtime typing)', () => {
    // widget.data is typed as unknown at compile time but EChartsOption at runtime
    const widgetData: unknown = basicOption

    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: widgetData as EChartsOption,
    } as EchartWidgetState)

    render(<Echart id='test-echart' />)

    expect(mockChart.setOption).toHaveBeenCalledWith(
      expect.objectContaining({
        title: basicOption.title,
        series: basicOption.series,
      }),
      {
        lazyUpdate: true,
        notMerge: true,
      },
    )
  })

  test('passes through all event handlers from widget', () => {
    const handlers = {
      click: vi.fn(),
      dblclick: vi.fn(),
      mousedown: vi.fn(),
      mouseup: vi.fn(),
      mouseover: vi.fn(),
      mouseout: vi.fn(),
      mousemove: vi.fn(),
    }

    useWidgetStore.getState().setWidget<EchartWidgetState>('test-echart', {
      type: 'echart',
      option: basicOption,
      onEvents: handlers,
    })

    render(<Echart id='test-echart' />)

    Object.entries(handlers).forEach(([eventName, handler]) => {
      expect(mockChart.on).toHaveBeenCalledWith(eventName, handler)
    })
  })

  test('component updates when switching between different widget IDs', () => {
    useWidgetStore.getState().setWidget('chart-a', {
      id: 'chart-a',
      type: 'echart',
      option: basicOption,
    } as EchartWidgetState)

    useWidgetStore.getState().setWidget('chart-b', {
      id: 'chart-b',
      type: 'echart',
      option: { series: [{ type: 'line', data: [1, 2, 3] }] },
    } as EchartWidgetState)

    const { rerender } = render(<Echart id='chart-a' />)

    expect(mockChart.setOption).toHaveBeenCalledWith(basicOption, {
      lazyUpdate: true,
      notMerge: true,
    })

    rerender(<Echart id='chart-b' />)

    expect(mockChart.setOption).toHaveBeenCalledWith(
      { series: [{ type: 'line', data: [1, 2, 3] }] },
      {
        lazyUpdate: true,
        notMerge: true,
      },
    )
  })

  test('disposes chart when component unmounts', () => {
    useWidgetStore.getState().setWidget('test-echart', {
      id: 'test-echart',
      type: 'echart',
      option: basicOption,
    } as EchartWidgetState)

    const { unmount } = render(<Echart id='test-echart' />)

    unmount()

    expect(mockChart.dispose).toHaveBeenCalled()
  })
})
