import { describe, test, expect, beforeEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { StackToggle, STACK_TOGGLE_TOOL_ID } from './stack-toggle'
import { useWidgetStore } from '../../stores/widget-store'
import { DEFAULT_STACK_GROUP } from '../../echart/const'

describe('StackToggle', () => {
  const widgetId = 'test-stack-widget'

  // Helper to set up widget with multiple series (required for component to render)
  function setupMultiSeriesWidget(overrides = {}) {
    useWidgetStore.getState().setWidget(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar' },
          { name: 'Series 2', type: 'bar' },
        ],
      },
      ...overrides,
    })
  }

  beforeEach(() => {
    useWidgetStore.getState().clearWidgets()
    setupMultiSeriesWidget()
  })

  test('renders stack toggle button', () => {
    render(<StackToggle id={widgetId} />)

    const button = screen.getByRole('button')
    expect(button).toBeTruthy()
  })

  test('shows stacked tooltip when in unstacked mode', () => {
    render(<StackToggle id={widgetId} />)

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

  test('toggles to stacked mode and updates tool config', () => {
    render(<StackToggle id={widgetId} />)

    const button = screen.getByRole('button')
    fireEvent.click(button)

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const tool = widget?.registeredTools?.find(
      (t) => t.id === STACK_TOGGLE_TOOL_ID,
    )
    expect(tool?.enabled).toBe(true)
  })

  test('toggles back to unstacked mode', () => {
    render(<StackToggle id={widgetId} />)

    const button = screen.getByRole('button')

    // Toggle to stacked
    fireEvent.click(button)

    // Toggle back to unstacked
    fireEvent.click(button)

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const tool = widget?.registeredTools?.find(
      (t) => t.id === STACK_TOGGLE_TOOL_ID,
    )
    expect(tool?.enabled).toBe(false)
  })

  test('has active state when in stacked mode', () => {
    render(<StackToggle id={widgetId} />)

    const button = screen.getByRole('button')

    expect(button.getAttribute('data-active')).toBe('false')

    fireEvent.click(button)

    expect(button.getAttribute('data-active')).toBe('true')
  })

  test('starts in stacked mode when defaultIsStacked is true', () => {
    render(<StackToggle id={widgetId} defaultIsStacked />)

    const button = screen.getByRole('button')
    expect(button.getAttribute('data-active')).toBe('true')

    // Should show unstacked tooltip when in stacked mode
    expect(button.getAttribute('aria-label')).toBe('Disable stacking')
  })

  test('uses custom labels when provided', () => {
    const customLabels = {
      stacked: 'Stack bars',
      unstacked: 'Unstack bars',
      ariaLabel: 'Toggle bar stacking',
    }

    render(<StackToggle id={widgetId} labels={customLabels} />)

    const button = screen.getByRole('button', { name: 'Toggle bar stacking' })
    expect(button).toBeTruthy()
  })

  test('initializes tool config with default values on mount', () => {
    render(<StackToggle id={widgetId} />)

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const tool = widget?.registeredTools?.find(
      (t) => t.id === STACK_TOGGLE_TOOL_ID,
    )
    expect(tool?.enabled).toBe(false)
  })

  test('initializes tool config with stacked values when defaultIsStacked is true', () => {
    render(<StackToggle id={widgetId} defaultIsStacked />)

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const tool = widget?.registeredTools?.find(
      (t) => t.id === STACK_TOGGLE_TOOL_ID,
    )
    expect(tool?.enabled).toBe(true)
  })

  test('applies stack to EChart series via config pipeline when toggling to stacked', async () => {
    render(<StackToggle id={widgetId} />)
    const button = screen.getByRole('button')
    fireEvent.click(button)

    // Run the config pipeline to apply the stack tool's transformation
    await useWidgetStore.getState().executeConfigPipeline(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar' },
          { name: 'Series 2', type: 'bar' },
        ],
      },
    })

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const series = (widget as { option?: { series?: { stack?: string }[] } })
      ?.option?.series?.[0]
    expect(series?.stack).toBe(DEFAULT_STACK_GROUP)
  })

  test('removes stack from EChart series via config pipeline when toggling to unstacked', async () => {
    useWidgetStore.getState().setWidget(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar', stack: DEFAULT_STACK_GROUP },
          { name: 'Series 2', type: 'bar', stack: DEFAULT_STACK_GROUP },
        ],
      },
    })

    render(<StackToggle id={widgetId} />)
    const button = screen.getByRole('button')

    // First click unstacks (since series has stack, it starts stacked)
    fireEvent.click(button)

    // Run the config pipeline — tool is disabled when unstacked, so stack is removed
    await useWidgetStore.getState().executeConfigPipeline(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar' },
          { name: 'Series 2', type: 'bar' },
        ],
      },
    })

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const series = (widget as { option?: { series?: { stack?: string }[] } })
      ?.option?.series?.[0]
    expect(series?.stack).toBeUndefined()
  })

  test('applies stack to multiple series via config pipeline', async () => {
    useWidgetStore.getState().setWidget(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar' },
          { name: 'Series 2', type: 'bar' },
          { name: 'Series 3', type: 'bar' },
        ],
      },
    })

    render(<StackToggle id={widgetId} />)
    const button = screen.getByRole('button')
    fireEvent.click(button)

    await useWidgetStore.getState().executeConfigPipeline(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar' },
          { name: 'Series 2', type: 'bar' },
          { name: 'Series 3', type: 'bar' },
        ],
      },
    })

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const seriesArray = (
      widget as { option?: { series?: { stack?: string }[] } }
    )?.option?.series

    expect(seriesArray).toHaveLength(3)
    seriesArray?.forEach((s) => {
      expect(s.stack).toBe(DEFAULT_STACK_GROUP)
    })
  })

  test('preserves custom stack group via config pipeline', async () => {
    const customStackGroup = 'custom-group'
    useWidgetStore.getState().setWidget(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar', stack: customStackGroup },
          { name: 'Series 2', type: 'bar', stack: customStackGroup },
        ],
      },
    })

    render(<StackToggle id={widgetId} />)
    const button = screen.getByRole('button')

    // Toggle off then on
    fireEvent.click(button) // unstacked
    fireEvent.click(button) // stacked again

    await useWidgetStore.getState().executeConfigPipeline(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar', stack: customStackGroup },
          { name: 'Series 2', type: 'bar', stack: customStackGroup },
        ],
      },
    })

    await waitFor(() => {
      const widget = useWidgetStore.getState().getWidget(widgetId)
      const series = (widget as { option?: { series?: { stack?: string }[] } })
        ?.option?.series?.[0]
      expect(series?.stack).toBe(customStackGroup)
    })
  })

  test('detects existing stack in series and defaults to stacked mode', () => {
    useWidgetStore.getState().setWidget(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar', stack: 'existing-stack' },
          { name: 'Series 2', type: 'bar', stack: 'existing-stack' },
        ],
      },
    })

    render(<StackToggle id={widgetId} />)

    const button = screen.getByRole('button')
    expect(button.getAttribute('data-active')).toBe('true')
    expect(button.getAttribute('aria-label')).toBe('Disable stacking')
  })

  test('returns null for single series (stacking not applicable)', () => {
    useWidgetStore.getState().setWidget(widgetId, {
      option: {
        series: { name: 'Single Series', type: 'bar' },
      },
    })

    const { container } = render(<StackToggle id={widgetId} />)
    // Component returns null when there's only one series
    expect(container.firstChild).toBeNull()
  })

  test('returns null when widget has no option defined', () => {
    // Clear the widget set up in beforeEach and create one without option
    useWidgetStore.getState().clearWidgets()
    useWidgetStore.getState().setWidget(widgetId, {})

    const { container } = render(<StackToggle id={widgetId} />)
    // Component returns null when there's no series data
    expect(container.firstChild).toBeNull()
  })

  test('config tool re-applies stack when config pipeline re-runs', async () => {
    // Start with stacked widget
    useWidgetStore.getState().setWidget(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar', stack: DEFAULT_STACK_GROUP },
          { name: 'Series 2', type: 'bar', stack: DEFAULT_STACK_GROUP },
        ],
      },
    })

    render(<StackToggle id={widgetId} defaultIsStacked />)

    // Simulate loader running config pipeline with base config (no stack)
    await useWidgetStore.getState().executeConfigPipeline(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar' },
          { name: 'Series 2', type: 'bar' },
        ],
      },
    })

    // Stack should be re-applied by the config tool
    await waitFor(() => {
      const widget = useWidgetStore.getState().getWidget(widgetId)
      const series = (widget as { option?: { series?: { stack?: string }[] } })
        ?.option?.series
      expect(series?.[0]?.stack).toBe(DEFAULT_STACK_GROUP)
      expect(series?.[1]?.stack).toBe(DEFAULT_STACK_GROUP)
    })
  })

  test('config tool does not apply stack when unstacked and pipeline re-runs', async () => {
    // Start with unstacked widget
    useWidgetStore.getState().setWidget(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar' },
          { name: 'Series 2', type: 'bar' },
        ],
      },
    })

    render(<StackToggle id={widgetId} />)

    // Run config pipeline with base config
    await useWidgetStore.getState().executeConfigPipeline(widgetId, {
      option: {
        series: [
          { name: 'Series 1', type: 'bar' },
          { name: 'Series 2', type: 'bar' },
        ],
      },
    })

    // Stack should NOT be applied since isStacked is false (tool disabled)
    await waitFor(() => {
      const widget = useWidgetStore.getState().getWidget(widgetId)
      const series = (widget as { option?: { series?: { stack?: string }[] } })
        ?.option?.series
      expect(series?.[0]?.stack).toBeUndefined()
      expect(series?.[1]?.stack).toBeUndefined()
    })
  })

  test('preserves other option properties via config pipeline', async () => {
    useWidgetStore.getState().setWidget(widgetId, {
      option: {
        title: { text: 'My Chart' },
        xAxis: { type: 'category' },
        series: [
          { name: 'Series 1', type: 'bar' },
          { name: 'Series 2', type: 'bar' },
        ],
      },
    })

    render(<StackToggle id={widgetId} />)
    const button = screen.getByRole('button')
    fireEvent.click(button)

    await useWidgetStore.getState().executeConfigPipeline(widgetId, {
      option: {
        title: { text: 'My Chart' },
        xAxis: { type: 'category' },
        series: [
          { name: 'Series 1', type: 'bar' },
          { name: 'Series 2', type: 'bar' },
        ],
      },
    })

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const option = (
      widget as {
        option?: {
          title?: { text?: string }
          xAxis?: { type?: string }
        }
      }
    )?.option

    expect(option?.title?.text).toBe('My Chart')
    expect(option?.xAxis?.type).toBe('category')
  })

  test('registers config tool on mount and unregisters on unmount', () => {
    const { unmount } = render(<StackToggle id={widgetId} />)

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const tools = widget?.registeredTools ?? []
    const stackTool = tools.find((t) => t.id === STACK_TOGGLE_TOOL_ID)
    expect(stackTool).toBeDefined()
    expect(stackTool?.type).toBe('config')

    unmount()

    const widgetAfter = useWidgetStore.getState().getWidget(widgetId)
    const toolsAfter = widgetAfter?.registeredTools ?? []
    const stackToolAfter = toolsAfter.find((t) => t.id === STACK_TOGGLE_TOOL_ID)
    expect(stackToolAfter).toBeUndefined()
  })
})
