import { act, waitFor } from '@testing-library/react'
import { renderHookWithNuqs } from '@/test/nuqs-testing'
import { APP_LIST_SEARCH_DEBOUNCE_MS } from '../../constants'
import { useAppsQueryState } from '../use-apps-query-state'

const renderWithAdapter = (searchParams = '') => {
  return renderHookWithNuqs(() => useAppsQueryState(), { searchParams })
}

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

  it('should expose app list query state actions', () => {
    const { result } = renderWithAdapter()

    expect(result.current.query).toEqual({
      tagIDs: [],
      creatorIDs: [],
      keywords: '',
      isCreatedByMe: false,
    })
    expect(typeof result.current.setKeywords).toBe('function')
    expect(typeof result.current.setTagIDs).toBe('function')
    expect(typeof result.current.setCreatorIDs).toBe('function')
    expect(typeof result.current.setIsCreatedByMe).toBe('function')
  })

  it('should parse app list filters from URL', () => {
    const { result } = renderWithAdapter(
      '?tagIDs=tag1;tag2&creatorIDs=user-1;user-2&keywords=search+term&isCreatedByMe=true',
    )

    expect(result.current.query).toEqual({
      tagIDs: ['tag1', 'tag2'],
      creatorIDs: ['user-1', 'user-2'],
      keywords: 'search term',
      isCreatedByMe: true,
    })
  })

  it('should update keywords state immediately while debouncing URL writes', async () => {
    vi.useFakeTimers()
    try {
      const { result, onUrlUpdate } = renderWithAdapter()

      act(() => {
        result.current.setKeywords('search')
      })

      expect(result.current.query.keywords).toBe('search')
      expect(onUrlUpdate).not.toHaveBeenCalled()

      await act(async () => {
        await vi.advanceTimersByTimeAsync(APP_LIST_SEARCH_DEBOUNCE_MS + 100)
      })

      expect(onUrlUpdate).toHaveBeenCalled()
      const update = onUrlUpdate.mock.calls.at(-1)![0]
      expect(update.searchParams.get('keywords')).toBe('search')
      expect(update.options.history).toBe('replace')
      expect(update.options.shallow).toBe(false)
    }
    finally {
      vi.useRealTimers()
    }
  })

  it('should remove keywords from URL when cleared', async () => {
    vi.useFakeTimers()
    try {
      const { result, onUrlUpdate } = renderWithAdapter('?keywords=existing')

      act(() => {
        result.current.setKeywords('')
      })

      expect(result.current.query.keywords).toBe('')

      await act(async () => {
        await vi.advanceTimersByTimeAsync(APP_LIST_SEARCH_DEBOUNCE_MS + 100)
      })

      expect(onUrlUpdate).toHaveBeenCalled()
      const update = onUrlUpdate.mock.calls.at(-1)![0]
      expect(update.searchParams.has('keywords')).toBe(false)
    }
    finally {
      vi.useRealTimers()
    }
  })

  it('should update tag filter URL state', async () => {
    const { result, onUrlUpdate } = renderWithAdapter()

    act(() => {
      result.current.setTagIDs(['tag1', 'tag2'])
    })

    await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
    const update = onUrlUpdate.mock.calls.at(-1)![0]
    expect(result.current.query.tagIDs).toEqual(['tag1', 'tag2'])
    expect(update.searchParams.get('tagIDs')).toBe('tag1;tag2')
    expect(update.options.history).toBe('push')
  })

  it('should remove tagIDs from URL when empty', async () => {
    const { result, onUrlUpdate } = renderWithAdapter('?tagIDs=tag1;tag2')

    act(() => {
      result.current.setTagIDs([])
    })

    await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
    const update = onUrlUpdate.mock.calls.at(-1)![0]
    expect(result.current.query.tagIDs).toEqual([])
    expect(update.searchParams.has('tagIDs')).toBe(false)
  })

  it('should update creator filter URL state', async () => {
    const { result, onUrlUpdate } = renderWithAdapter()

    act(() => {
      result.current.setCreatorIDs(['user-1', 'user-2'])
    })

    await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
    const update = onUrlUpdate.mock.calls.at(-1)![0]
    expect(result.current.query.creatorIDs).toEqual(['user-1', 'user-2'])
    expect(update.searchParams.get('creatorIDs')).toBe('user-1;user-2')
    expect(update.options.history).toBe('push')
  })

  it('should remove creatorIDs from URL when empty', async () => {
    const { result, onUrlUpdate } = renderWithAdapter('?creatorIDs=user-1;user-2')

    act(() => {
      result.current.setCreatorIDs([])
    })

    await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
    const update = onUrlUpdate.mock.calls.at(-1)![0]
    expect(result.current.query.creatorIDs).toEqual([])
    expect(update.searchParams.has('creatorIDs')).toBe(false)
  })

  it('should update created-by-me URL state', async () => {
    const { result, onUrlUpdate } = renderWithAdapter()

    act(() => {
      result.current.setIsCreatedByMe(true)
    })

    await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
    const update = onUrlUpdate.mock.calls.at(-1)![0]
    expect(result.current.query.isCreatedByMe).toBe(true)
    expect(update.searchParams.get('isCreatedByMe')).toBe('true')
    expect(update.options.history).toBe('push')
  })

  it('should remove isCreatedByMe from URL when disabled', async () => {
    const { result, onUrlUpdate } = renderWithAdapter('?isCreatedByMe=true')

    act(() => {
      result.current.setIsCreatedByMe(false)
    })

    await waitFor(() => expect(onUrlUpdate).toHaveBeenCalled())
    const update = onUrlUpdate.mock.calls.at(-1)![0]
    expect(result.current.query.isCreatedByMe).toBe(false)
    expect(update.searchParams.has('isCreatedByMe')).toBe(false)
  })
})
