import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { MapComponent } from './map'
import { render } from '../../tests/providers'
import { useMapStore } from '../providers/map/provider'
import { MAP_ID } from '../../tests/config'
import type { MapViewState, ViewStateChangeParameters } from '@deck.gl/core'

// Mock the DeckGLComponent to avoid DeckGL instantiation
vi.mock('./deckgl', () => ({
  DeckGLComponent: ({
    id,
    onLoad,
    onViewStateChange,
    onAfterRender,
    onDragEnd,
  }: {
    id: string
    onLoad?: (value: boolean) => void
    onViewStateChange?: (params: unknown) => void
    onAfterRender?: (context: unknown) => void
    onDragEnd?: (info: unknown, event: unknown) => void
  }) => {
    // Trigger callbacks to test their implementation
    if (onLoad) {
      setTimeout(() => onLoad(true), 0)
    }
    if (onViewStateChange) {
      setTimeout(
        () =>
          onViewStateChange({
            viewState: { longitude: 1, latitude: 1, zoom: 10 },
            interactionState: {},
            oldViewState: { longitude: 0, latitude: 0, zoom: 9 },
          }),
        0,
      )
    }
    if (onAfterRender) {
      setTimeout(() => onAfterRender({ gl: {} }), 0)
    }
    if (onDragEnd) {
      setTimeout(() => onDragEnd({ coordinate: [0, 0] }, {}), 0)
    }

    return (
      <div
        data-testid={`deckgl-${id}`}
        data-onload={onLoad ? 'true' : 'false'}
        data-onviewstatechange={onViewStateChange ? 'true' : 'false'}
        data-onafterrender={onAfterRender ? 'true' : 'false'}
        data-ondragend={onDragEnd ? 'true' : 'false'}
      >
        DeckGL Component Mock
      </div>
    )
  },
}))

describe('MapComponent', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    vi.useFakeTimers()
  })

  afterEach(() => {
    vi.restoreAllMocks()
    vi.useRealTimers()
  })

  it('should render DeckGLComponent with correct props', () => {
    const { container } = render(<MapComponent id={MAP_ID} />)

    const deckgl = container.querySelector(`[data-testid="deckgl-${MAP_ID}"]`)
    expect(deckgl).toBeTruthy()
    expect(deckgl?.textContent).toBe('DeckGL Component Mock')
  })

  it('should pass handlers to DeckGLComponent', () => {
    const { container } = render(<MapComponent id={MAP_ID} />)

    const deckgl = container.querySelector(`[data-testid="deckgl-${MAP_ID}"]`)
    expect(deckgl?.getAttribute('data-onload')).toBe('true')
    expect(deckgl?.getAttribute('data-onviewstatechange')).toBe('true')
    expect(deckgl?.getAttribute('data-onafterrender')).toBe('true')
    expect(deckgl?.getAttribute('data-ondragend')).toBe('true')
  })

  it('should call onLoad callback when map loads', () => {
    const onLoad = vi.fn()

    render(<MapComponent id={MAP_ID} onLoad={onLoad} />)

    // MapComponent is rendered, handlers are set up
    const { container } = render(<MapComponent id={MAP_ID} onLoad={onLoad} />)
    expect(container).toBeTruthy()
  })

  it('should handle onViewStateChange prop', () => {
    const onViewStateChange = vi.fn()

    const { container } = render(
      <MapComponent id={MAP_ID} onViewStateChange={onViewStateChange} />,
    )

    const deckgl = container.querySelector(`[data-testid="deckgl-${MAP_ID}"]`)
    expect(deckgl?.getAttribute('data-onviewstatechange')).toBe('true')
  })

  it('should use layers from useLayers hook', () => {
    const { container } = render(<MapComponent id={MAP_ID} />)

    const deckgl = container.querySelector(`[data-testid="deckgl-${MAP_ID}"]`)
    expect(deckgl).toBeTruthy()
  })

  it('should handle onAfterRender prop', () => {
    const onAfterRender = vi.fn()

    const { container } = render(
      <MapComponent id={MAP_ID} onAfterRender={onAfterRender} />,
    )

    const deckgl = container.querySelector(`[data-testid="deckgl-${MAP_ID}"]`)
    expect(deckgl?.getAttribute('data-onafterrender')).toBe('true')
  })

  it('should handle onDragEnd prop', () => {
    const onDragEnd = vi.fn()

    const { container } = render(
      <MapComponent id={MAP_ID} onDragEnd={onDragEnd} />,
    )

    const deckgl = container.querySelector(`[data-testid="deckgl-${MAP_ID}"]`)
    expect(deckgl?.getAttribute('data-ondragend')).toBe('true')
  })

  it('should handle onDragEnd without custom callback', () => {
    const { container } = render(<MapComponent id={MAP_ID} />)

    const deckgl = container.querySelector(`[data-testid="deckgl-${MAP_ID}"]`)
    expect(deckgl?.getAttribute('data-ondragend')).toBe('true')
  })

  it('should read basemap from store', () => {
    const TestComponent = () => {
      const basemap = useMapStore((state) => state.maps[MAP_ID]?.basemap)
      return (
        <div>
          <div data-testid='basemap'>{basemap}</div>
          <MapComponent id={MAP_ID} />
        </div>
      )
    }

    const { container } = render(<TestComponent />)

    const basemap = container.querySelector('[data-testid="basemap"]')
    expect(basemap).toBeTruthy()
  })

  it('should read viewState from store', () => {
    const TestComponent = () => {
      const viewState = useMapStore((state) => state.viewStates[MAP_ID])
      return (
        <div>
          <div data-testid='viewstate'>{viewState?.zoom}</div>
          <MapComponent id={MAP_ID} />
        </div>
      )
    }

    const { container } = render(<TestComponent />)

    const viewstate = container.querySelector('[data-testid="viewstate"]')
    expect(viewstate).toBeTruthy()
  })

  it('should read controller from store', () => {
    const TestComponent = () => {
      const controller = useMapStore((state) => state.maps[MAP_ID]?.controller)
      return (
        <div>
          <div data-testid='controller'>
            {controller !== undefined ? 'true' : 'false'}
          </div>
          <MapComponent id={MAP_ID} />
        </div>
      )
    }

    const { container } = render(<TestComponent />)

    const controllerEl = container.querySelector('[data-testid="controller"]')
    expect(controllerEl?.textContent).toBe('true')
  })

  it('should handle multiple renders', () => {
    const { container, rerender } = render(<MapComponent id={MAP_ID} />)

    let deckgl = container.querySelector(`[data-testid="deckgl-${MAP_ID}"]`)
    expect(deckgl).toBeTruthy()

    rerender(<MapComponent id={MAP_ID} />)

    deckgl = container.querySelector(`[data-testid="deckgl-${MAP_ID}"]`)
    expect(deckgl).toBeTruthy()
  })

  it('should handle optional props', () => {
    const { container } = render(
      <MapComponent
        id={MAP_ID}
        getCursor={() => 'pointer'}
        getTooltip={() => null}
      />,
    )

    const deckgl = container.querySelector(`[data-testid="deckgl-${MAP_ID}"]`)
    expect(deckgl).toBeTruthy()
  })

  it('should call handleLoad and update store', async () => {
    const onLoad = vi.fn()

    render(<MapComponent id={MAP_ID} onLoad={onLoad} />)

    // Wait for the async callback
    await vi.waitFor(() => {
      expect(onLoad).toHaveBeenCalledWith(true)
    })
  })

  it('should call handleViewStateChange and debounce viewport update', async () => {
    const onViewStateChange = vi.fn()

    render(<MapComponent id={MAP_ID} onViewStateChange={onViewStateChange} />)

    // Wait for the async callback
    await vi.waitFor(() => {
      expect(onViewStateChange).toHaveBeenCalled()
    })

    // Advance timers for debounce
    vi.advanceTimersByTime(200)
  })

  it('should call handleViewStateChange without custom callback', async () => {
    const { container } = render(<MapComponent id={MAP_ID} />)

    expect(container).toBeTruthy()

    // Advance timers for debounce
    await vi.waitFor(() => {
      vi.advanceTimersByTime(200)
    })
  })

  it('should call handleAfterRender', async () => {
    const onAfterRender = vi.fn()

    render(<MapComponent id={MAP_ID} onAfterRender={onAfterRender} />)

    // Wait for the async callback
    await vi.waitFor(() => {
      expect(onAfterRender).toHaveBeenCalledWith({ gl: {} })
    })
  })

  it('should call handleDragEnd with custom callback', async () => {
    const onDragEnd = vi.fn()

    render(<MapComponent id={MAP_ID} onDragEnd={onDragEnd} />)

    // Wait for the async callback
    await vi.waitFor(() => {
      expect(onDragEnd).toHaveBeenCalled()
    })
  })

  it('should call handleDragEnd without custom callback', async () => {
    const { container } = render(<MapComponent id={MAP_ID} />)

    expect(container).toBeTruthy()

    // Wait for the callback to execute
    await vi.waitFor(() => {
      vi.advanceTimersByTime(0)
    })
  })

  it('should clear timer on unmount', () => {
    const { unmount } = render(<MapComponent id={MAP_ID} />)

    unmount()

    // Component should unmount cleanly
    expect(true).toBe(true)
  })

  it('should handle layer sorting', () => {
    const { container } = render(<MapComponent id={MAP_ID} />)

    const deckgl = container.querySelector(`[data-testid="deckgl-${MAP_ID}"]`)
    expect(deckgl).toBeTruthy()
  })

  it('should handle viewState from callback with custom onViewStateChange', async () => {
    const customCallback = vi.fn(
      (
        params: ViewStateChangeParameters<MapViewState>,
        callback: (
          id: string,
          viewState: MapViewState,
          interactionState: unknown,
        ) => void,
      ) => {
        // Call the callback with the new viewState
        callback(MAP_ID, params.viewState, params.interactionState)
      },
    )

    render(<MapComponent id={MAP_ID} onViewStateChange={customCallback} />)

    await vi.waitFor(() => {
      expect(customCallback).toHaveBeenCalled()
    })
  })
})
