import { describe, test, expect, beforeEach } from 'vitest'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import {
  RelativeData,
  RELATIVE_DATA_TOOL_ID,
  RELATIVE_DATA_CONFIG_TOOL_ID,
} from './relative-data'
import { useWidgetStore } from '../../stores/widget-store'
import type { EchartWidgetData } from '../../echart/types'
import type { RelativeDataState } from './types'

describe('RelativeData', () => {
  const widgetId = 'test-relative-widget'

  const mockData: EchartWidgetData = [
    [
      { category: 'A', value: 25 },
      { category: 'B', value: 75 },
    ],
  ]

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

  test('renders relative data toggle button', () => {
    render(<RelativeData id={widgetId} />)

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

  test('shows relative tooltip when in absolute mode', () => {
    render(<RelativeData id={widgetId} />)

    const button = screen.getByRole('button', { name: 'Show relative values' })
    expect(button).toBeTruthy()
  })

  test('toggles to relative mode and updates widget store with percentage values', async () => {
    render(<RelativeData id={widgetId} />)

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

    // Execute the pipeline with the source data
    await useWidgetStore.getState().executeToolPipeline(widgetId, mockData)

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const data = widget?.data as EchartWidgetData

    // Total is 100, so 25 becomes 25% and 75 becomes 75%
    expect(data?.[0]?.[0]?.value).toBe(25)
    expect(data?.[0]?.[1]?.value).toBe(75)
    expect(data?.[0]?.[0]?.category).toBe('A')
    expect(data?.[0]?.[1]?.category).toBe('B')
  })

  test('toggles back to absolute mode and restores original values', async () => {
    render(<RelativeData id={widgetId} />)

    const button = screen.getByRole('button')

    // Toggle to relative
    fireEvent.click(button)
    await useWidgetStore.getState().executeToolPipeline(widgetId, mockData)

    // Toggle back to absolute
    fireEvent.click(button)
    await useWidgetStore.getState().executeToolPipeline(widgetId, mockData)

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const data = widget?.data as EchartWidgetData

    expect(data?.[0]?.[0]?.value).toBe(25)
    expect(data?.[0]?.[1]?.value).toBe(75)
  })

  test('has active state when in relative mode', () => {
    render(<RelativeData 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 relative mode when defaultIsRelative is true', () => {
    render(<RelativeData id={widgetId} defaultIsRelative />)

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

    // Should show absolute tooltip when in relative mode
    expect(button.getAttribute('aria-label')).toBe('Show absolute values')
  })

  test('initializes tool state with defaultIsRelative on mount', async () => {
    render(<RelativeData id={widgetId} defaultIsRelative />)

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

  test('initializes tool state with false when defaultIsRelative is false', async () => {
    render(<RelativeData id={widgetId} defaultIsRelative={false} />)

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

  test('uses custom labels when provided', () => {
    const customLabels = {
      relative: 'Switch to %',
      absolute: 'Switch to numbers',
      ariaLabel: 'Toggle data mode',
    }

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

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

  test('handles data with multiple series', async () => {
    const multiSeriesData: EchartWidgetData = [
      [
        { name: 'X', count: 10 },
        { name: 'Y', count: 20 },
      ],
      [
        { name: 'X', count: 30 },
        { name: 'Y', count: 40 },
      ],
    ]

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

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

    await useWidgetStore
      .getState()
      .executeToolPipeline(widgetId, multiSeriesData)

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const data = widget?.data as EchartWidgetData

    // Total is 100, so values become 10%, 20%, 30%, 40%
    expect(data?.[0]?.[0]?.count).toBe(10)
    expect(data?.[0]?.[1]?.count).toBe(20)
    expect(data?.[1]?.[0]?.count).toBe(30)
    expect(data?.[1]?.[1]?.count).toBe(40)
  })

  test('handles zero total gracefully', async () => {
    const zeroData: EchartWidgetData = [
      [
        { category: 'A', value: 0 },
        { category: 'B', value: 0 },
      ],
    ]

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

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

    await useWidgetStore.getState().executeToolPipeline(widgetId, zeroData)

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const data = widget?.data as EchartWidgetData

    // Should preserve original data when total is 0
    expect(data?.[0]?.[0]?.value).toBe(0)
    expect(data?.[0]?.[1]?.value).toBe(0)
  })

  test('preserves string values during transformation', async () => {
    const mixedData: EchartWidgetData = [
      [
        { label: 'First', value: 50, extra: 'text' },
        { label: 'Second', value: 50, extra: 'more' },
      ],
    ]

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

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

    await useWidgetStore.getState().executeToolPipeline(widgetId, mixedData)

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const data = widget?.data as EchartWidgetData

    expect(data?.[0]?.[0]?.label).toBe('First')
    expect(data?.[0]?.[0]?.extra).toBe('text')
    expect(data?.[0]?.[1]?.label).toBe('Second')
    expect(data?.[0]?.[1]?.extra).toBe('more')
  })

  test('renders custom icon when provided', () => {
    const customIcon = <span data-testid='custom-icon'>%</span>

    render(<RelativeData id={widgetId} Icon={customIcon} />)

    expect(screen.getByTestId('custom-icon')).toBeTruthy()
  })

  test('passes IconButtonProps to the button', () => {
    render(<RelativeData id={widgetId} IconButtonProps={{ disabled: true }} />)

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

  test('registers both data and config tools on mount', async () => {
    render(<RelativeData id={widgetId} />)

    await waitFor(() => {
      const widget = useWidgetStore.getState().getWidget(widgetId)
      const dataTool = widget?.registeredTools?.find(
        (t) => t.id === RELATIVE_DATA_TOOL_ID,
      )
      const configTool = widget?.registeredTools?.find(
        (t) => t.id === RELATIVE_DATA_CONFIG_TOOL_ID,
      )
      expect(dataTool).toBeTruthy()
      expect(configTool).toBeTruthy()
      expect(configTool?.type).toBe('config')
      // Data tool disabled by default (isRelative=false)
      expect(dataTool?.enabled).toBe(false)
      // Config tool always enabled — it reads isRelative from store internally
      expect(configTool?.enabled).toBe(true)
    })
  })

  test('data tool enabled when toggled to relative mode', async () => {
    render(<RelativeData id={widgetId} />)

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

    await waitFor(() => {
      const widget = useWidgetStore.getState().getWidget(widgetId)
      const dataTool = widget?.registeredTools?.find(
        (t) => t.id === RELATIVE_DATA_TOOL_ID,
      )
      expect(dataTool?.enabled).toBe(true)
    })
  })

  test('stores isRelative in widget root state', () => {
    render(<RelativeData id={widgetId} />)

    // Default: isRelative=false in store
    let widget = useWidgetStore.getState().getWidget(widgetId)
    expect((widget as RelativeDataState | undefined)?.isRelative).toBe(false)

    // Toggle to relative
    const button = screen.getByRole('button')
    fireEvent.click(button)

    widget = useWidgetStore.getState().getWidget(widgetId)
    expect((widget as RelativeDataState | undefined)?.isRelative).toBe(true)

    // Toggle back
    fireEvent.click(button)

    widget = useWidgetStore.getState().getWidget(widgetId)
    expect((widget as RelativeDataState | undefined)?.isRelative).toBe(false)
  })

  test('config tool does not apply formatter when sourceData is empty', async () => {
    const customFormatter = (value: number) => `$${value}`
    useWidgetStore
      .getState()
      .setWidget(widgetId, { formatter: customFormatter, sourceData: [] })

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

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

    // Execute config pipeline — sourceData is empty, so formatter should pass through
    await useWidgetStore
      .getState()
      .executeConfigPipeline(widgetId, { formatter: customFormatter, max: 200 })

    const widget = useWidgetStore.getState().getWidget(widgetId)
    expect(widget?.formatter).toBe(customFormatter)
    expect((widget as unknown as Record<string, unknown>)?.max).toBe(200)
  })

  test('config pipeline applies percentage formatter when in relative mode', async () => {
    useWidgetStore
      .getState()
      .setWidget(widgetId, { max: 500, sourceData: mockData })

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

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

    // Execute config pipeline
    await useWidgetStore
      .getState()
      .executeConfigPipeline(widgetId, { max: 500 })

    const widget = useWidgetStore.getState().getWidget(widgetId)
    expect((widget as unknown as Record<string, unknown>)?.max).toBe(100)
    expect(widget?.formatter).toBeDefined()
    expect(typeof widget?.formatter).toBe('function')
  })

  test('config tool passes config through unchanged when not in relative mode', async () => {
    const customFormatter = (value: number) => `$${value}`
    useWidgetStore
      .getState()
      .setWidget(widgetId, { formatter: customFormatter, max: 200 })

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

    // Execute config pipeline — config tool is enabled but isRelative=false in store,
    // so it passes through the config unchanged
    await useWidgetStore
      .getState()
      .executeConfigPipeline(widgetId, { formatter: customFormatter, max: 200 })

    const widget = useWidgetStore.getState().getWidget(widgetId)
    expect(widget?.formatter).toBe(customFormatter)
    expect((widget as unknown as Record<string, unknown>)?.max).toBe(200)
  })

  test('restores original formatter/max when toggling back to absolute', async () => {
    const customFormatter = (value: number) => `$${value}`
    useWidgetStore.getState().setWidget(widgetId, {
      formatter: customFormatter,
      max: 200,
      sourceData: mockData,
    })

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

    const button = screen.getByRole('button')

    // Toggle to relative — captures originalFormatter/originalMax in store
    fireEvent.click(button)

    await useWidgetStore
      .getState()
      .executeConfigPipeline(widgetId, { max: 200 })

    let widget = useWidgetStore.getState().getWidget(widgetId)
    expect((widget as unknown as Record<string, unknown>)?.max).toBe(100)

    // Toggle back to absolute — config tool restores originals from store
    fireEvent.click(button)

    await useWidgetStore
      .getState()
      .executeConfigPipeline(widgetId, { max: 200 })

    widget = useWidgetStore.getState().getWidget(widgetId)
    expect(widget?.formatter).toBe(customFormatter)
    expect((widget as unknown as Record<string, unknown>)?.max).toBe(200)
  })

  test('removes percentage formatter when toggling back with no original formatter', async () => {
    // Widget has no formatter — originalFormatter will be captured as undefined
    useWidgetStore.getState().setWidget(widgetId, { sourceData: mockData })

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

    const button = screen.getByRole('button')

    // Toggle to relative — captures originalFormatter=undefined
    fireEvent.click(button)

    await useWidgetStore.getState().executeConfigPipeline(widgetId, {})

    let widget = useWidgetStore.getState().getWidget(widgetId)
    expect(widget?.formatter).toBeDefined()
    expect(typeof widget?.formatter).toBe('function')

    // Toggle back to absolute — should remove percentage formatter
    fireEvent.click(button)

    await useWidgetStore.getState().executeConfigPipeline(widgetId, {})

    widget = useWidgetStore.getState().getWidget(widgetId)
    expect(widget?.formatter).toBeUndefined()
  })

  test('removes percentage formatter when defaultIsRelative=true and toggling to absolute', async () => {
    // Widget starts with defaultIsRelative=true and no formatter
    useWidgetStore.getState().setWidget(widgetId, { sourceData: mockData })

    render(<RelativeData id={widgetId} defaultIsRelative />)

    // Pipeline runs with isRelative=true — applies percentage formatter
    await useWidgetStore.getState().executeConfigPipeline(widgetId, {})

    let widget = useWidgetStore.getState().getWidget(widgetId)
    expect(widget?.formatter).toBeDefined()
    expect(typeof widget?.formatter).toBe('function')

    // Toggle to absolute (first toggle is OFF when defaultIsRelative=true)
    const button = screen.getByRole('button')
    fireEvent.click(button)

    await useWidgetStore.getState().executeConfigPipeline(widgetId, {})

    widget = useWidgetStore.getState().getWidget(widgetId)
    expect(widget?.formatter).toBeUndefined()
  })

  test('stores originalFormatter/originalMax in widget root on toggle', () => {
    const customFormatter = (value: number) => `$${value}`
    useWidgetStore
      .getState()
      .setWidget(widgetId, { formatter: customFormatter, max: 200 })

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

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

    const widget = useWidgetStore
      .getState()
      .getWidget<RelativeDataState>(widgetId)
    expect(widget?.originalFormatter).toBe(customFormatter)
    expect(widget?.originalMax).toBe(200)
  })

  test('restores original formatter/max when unmounting while in relative mode', async () => {
    const customFormatter = (value: number) => `$${value}`
    useWidgetStore
      .getState()
      .setWidget(widgetId, { formatter: customFormatter, max: 200 })

    const { unmount } = render(<RelativeData id={widgetId} />)

    const button = screen.getByRole('button')

    // Toggle to relative — captures originalFormatter/originalMax in store
    fireEvent.click(button)

    await waitFor(() => {
      const widget = useWidgetStore
        .getState()
        .getWidget<RelativeDataState>(widgetId)
      expect(widget?.isRelative).toBe(true)
      expect(widget?.originalFormatter).toBe(customFormatter)
      expect(widget?.originalMax).toBe(200)
    })

    // Unmount while still in relative mode
    unmount()

    const widget = useWidgetStore.getState().getWidget(widgetId)
    expect(widget?.formatter).toBe(customFormatter)
    expect((widget as unknown as Record<string, unknown>)?.max).toBe(200)
  })

  test('unregisters tools on unmount', async () => {
    const { unmount } = render(<RelativeData id={widgetId} />)

    await waitFor(() => {
      const widget = useWidgetStore.getState().getWidget(widgetId)
      expect(widget?.registeredTools?.length).toBeGreaterThan(0)
    })

    unmount()

    const widget = useWidgetStore.getState().getWidget(widgetId)
    const dataTool = widget?.registeredTools?.find(
      (t) => t.id === RELATIVE_DATA_TOOL_ID,
    )
    const configTool = widget?.registeredTools?.find(
      (t) => t.id === RELATIVE_DATA_CONFIG_TOOL_ID,
    )
    expect(dataTool).toBeUndefined()
    expect(configTool).toBeUndefined()
  })

  test('recalculates relative values when store data changes externally while in relative mode', async () => {
    const initialData: EchartWidgetData = [
      [
        { category: 'A', value: 50 },
        { category: 'B', value: 50 },
      ],
    ]

    // Pre-populate the widget in the store with relative data
    useWidgetStore.getState().setWidget(widgetId, {
      data: initialData,
    })

    render(<RelativeData id={widgetId} defaultIsRelative />)

    let widget
    let data
    await waitFor(() => {
      widget = useWidgetStore.getState().getWidget(widgetId)
      data = widget?.data as EchartWidgetData
      // Initial data: 50/100 = 50%, 50/100 = 50%
      expect(data?.[0]?.[0]?.value).toBe(50)
      expect(data?.[0]?.[1]?.value).toBe(50)
    })

    // Simulate external store update with new absolute data
    const newAbsoluteData: EchartWidgetData = [
      [
        { category: 'A', value: 25 },
        { category: 'B', value: 75 },
      ],
    ]
    useWidgetStore.getState().setWidget(widgetId, { data: newAbsoluteData })

    // Component should detect external change and recalculate
    // New data: 25/100 = 25%, 75/100 = 75%
    widget = useWidgetStore.getState().getWidget(widgetId)
    data = widget?.data as EchartWidgetData
    expect(data?.[0]?.[0]?.value).toBe(25)
    expect(data?.[0]?.[1]?.value).toBe(75)
  })
})
