import { QueryClientProvider } from '@tanstack/react-query'
import { renderHook, waitFor, act } from '@testing-library/react'
import { createTestQueryClient } from '../test/utils'
import {
  useCreateEncounter,
  useUpdateEncounter,
  useDeleteEncounter,
  useEvolutions,
  useForms,
  useBulkRandomize,
} from './useEncounters'

vi.mock('../api/encounters')

import {
  createEncounter,
  updateEncounter,
  deleteEncounter,
  fetchEvolutions,
  fetchForms,
  bulkRandomizeEncounters,
} from '../api/encounters'

function createWrapper() {
  const queryClient = createTestQueryClient()
  const wrapper = ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  )
  return { queryClient, wrapper }
}

describe('useCreateEncounter', () => {
  it('calls createEncounter with the run id and input', async () => {
    vi.mocked(createEncounter).mockResolvedValue({} as never)
    const { wrapper } = createWrapper()

    const { result } = renderHook(() => useCreateEncounter(3), { wrapper })
    const input = { routeId: 1, pokemonId: 25, status: 'caught' }
    await act(async () => {
      await result.current.mutateAsync(input as never)
    })

    expect(createEncounter).toHaveBeenCalledWith(3, input)
  })

  it('invalidates the run query on success', async () => {
    vi.mocked(createEncounter).mockResolvedValue({} as never)
    const { queryClient, wrapper } = createWrapper()
    const spy = vi.spyOn(queryClient, 'invalidateQueries')

    const { result } = renderHook(() => useCreateEncounter(3), { wrapper })
    await act(async () => {
      await result.current.mutateAsync({} as never)
    })

    expect(spy).toHaveBeenCalledWith({ queryKey: ['runs', 3] })
  })
})

describe('useUpdateEncounter', () => {
  it('calls updateEncounter with id and data', async () => {
    vi.mocked(updateEncounter).mockResolvedValue({} as never)
    const { wrapper } = createWrapper()

    const { result } = renderHook(() => useUpdateEncounter(3), { wrapper })
    await act(async () => {
      await result.current.mutateAsync({ id: 42, data: { status: 'dead' } } as never)
    })

    expect(updateEncounter).toHaveBeenCalledWith(42, { status: 'dead' })
  })

  it('invalidates the run query on success', async () => {
    vi.mocked(updateEncounter).mockResolvedValue({} as never)
    const { queryClient, wrapper } = createWrapper()
    const spy = vi.spyOn(queryClient, 'invalidateQueries')

    const { result } = renderHook(() => useUpdateEncounter(3), { wrapper })
    await act(async () => {
      await result.current.mutateAsync({ id: 1, data: {} } as never)
    })

    expect(spy).toHaveBeenCalledWith({ queryKey: ['runs', 3] })
  })
})

describe('useDeleteEncounter', () => {
  it('calls deleteEncounter with the encounter id', async () => {
    vi.mocked(deleteEncounter).mockResolvedValue(undefined as never)
    const { wrapper } = createWrapper()

    const { result } = renderHook(() => useDeleteEncounter(3), { wrapper })
    await act(async () => {
      await result.current.mutateAsync(55)
    })

    expect(deleteEncounter).toHaveBeenCalledWith(55)
  })

  it('invalidates the run query on success', async () => {
    vi.mocked(deleteEncounter).mockResolvedValue(undefined as never)
    const { queryClient, wrapper } = createWrapper()
    const spy = vi.spyOn(queryClient, 'invalidateQueries')

    const { result } = renderHook(() => useDeleteEncounter(3), { wrapper })
    await act(async () => {
      await result.current.mutateAsync(55)
    })

    expect(spy).toHaveBeenCalledWith({ queryKey: ['runs', 3] })
  })
})

describe('useEvolutions', () => {
  it('is disabled when pokemonId is null', () => {
    const { wrapper } = createWrapper()
    const { result } = renderHook(() => useEvolutions(null), { wrapper })
    expect(result.current.fetchStatus).toBe('idle')
    expect(fetchEvolutions).not.toHaveBeenCalled()
  })

  it('fetches evolutions for a given pokemon', async () => {
    vi.mocked(fetchEvolutions).mockResolvedValue([] as never)
    const { wrapper } = createWrapper()

    renderHook(() => useEvolutions(25, 'kanto'), { wrapper })
    await waitFor(() => expect(fetchEvolutions).toHaveBeenCalledWith(25, 'kanto'))
  })
})

describe('useForms', () => {
  it('is disabled when pokemonId is null', () => {
    const { wrapper } = createWrapper()
    const { result } = renderHook(() => useForms(null), { wrapper })
    expect(result.current.fetchStatus).toBe('idle')
  })

  it('fetches forms for a given pokemon', async () => {
    vi.mocked(fetchForms).mockResolvedValue([] as never)
    const { wrapper } = createWrapper()

    renderHook(() => useForms(133), { wrapper })
    await waitFor(() => expect(fetchForms).toHaveBeenCalledWith(133))
  })
})

describe('useBulkRandomize', () => {
  it('calls bulkRandomizeEncounters and invalidates the run', async () => {
    vi.mocked(bulkRandomizeEncounters).mockResolvedValue([] as never)
    const { queryClient, wrapper } = createWrapper()
    const spy = vi.spyOn(queryClient, 'invalidateQueries')

    const { result } = renderHook(() => useBulkRandomize(4), { wrapper })
    await act(async () => {
      await result.current.mutateAsync()
    })

    expect(bulkRandomizeEncounters).toHaveBeenCalledWith(4)
    expect(spy).toHaveBeenCalledWith({ queryKey: ['runs', 4] })
  })
})
