import { QueryClientProvider } from '@tanstack/react-query'
import { renderHook, waitFor, act } from '@testing-library/react'
import { createTestQueryClient } from '../test/utils'
import { usePokemonList, useCreateGame, useUpdateGame, useDeleteGame } from './useAdmin'

vi.mock('../api/admin')
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }))

import * as adminApi from '../api/admin'
import { toast } from 'sonner'

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

describe('usePokemonList', () => {
  it('calls listPokemon with defaults', async () => {
    vi.mocked(adminApi.listPokemon).mockResolvedValue({ results: [], total: 0 } as never)
    const { wrapper } = createWrapper()

    const { result } = renderHook(() => usePokemonList(), { wrapper })
    await waitFor(() => expect(result.current.isSuccess).toBe(true))

    expect(adminApi.listPokemon).toHaveBeenCalledWith(undefined, 50, 0, undefined)
  })

  it('passes search and filter params to listPokemon', async () => {
    vi.mocked(adminApi.listPokemon).mockResolvedValue({ results: [], total: 0 } as never)
    const { wrapper } = createWrapper()

    renderHook(() => usePokemonList('pika', 10, 20, 'electric'), { wrapper })
    await waitFor(() =>
      expect(adminApi.listPokemon).toHaveBeenCalledWith('pika', 10, 20, 'electric')
    )
  })
})

describe('useCreateGame', () => {
  it('calls createGame with the provided input', async () => {
    vi.mocked(adminApi.createGame).mockResolvedValue({ id: 1 } as never)
    const { wrapper } = createWrapper()

    const { result } = renderHook(() => useCreateGame(), { wrapper })
    const input = { name: 'FireRed', slug: 'firered', generation: 3, region: 'kanto', vgId: 1 }
    await act(async () => {
      await result.current.mutateAsync(input as never)
    })

    expect(adminApi.createGame).toHaveBeenCalledWith(input)
  })

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

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

    expect(spy).toHaveBeenCalledWith({ queryKey: ['games'] })
  })

  it('shows a success toast after creating a game', async () => {
    vi.mocked(adminApi.createGame).mockResolvedValue({} as never)
    const { wrapper } = createWrapper()

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

    expect(toast.success).toHaveBeenCalledWith('Game created')
  })

  it('shows an error toast on failure', async () => {
    vi.mocked(adminApi.createGame).mockRejectedValue(new Error('Conflict'))
    const { wrapper } = createWrapper()

    const { result } = renderHook(() => useCreateGame(), { wrapper })
    await act(async () => {
      result.current.mutate({} as never)
    })

    await waitFor(() => expect(toast.error).toHaveBeenCalledWith('Failed to create game: Conflict'))
  })
})

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

    const { result } = renderHook(() => useUpdateGame(), { wrapper })
    await act(async () => {
      await result.current.mutateAsync({ id: 7, data: { name: 'Renamed' } } as never)
    })

    expect(adminApi.updateGame).toHaveBeenCalledWith(7, { name: 'Renamed' })
  })

  it('invalidates games and shows a toast on success', async () => {
    vi.mocked(adminApi.updateGame).mockResolvedValue({} as never)
    const { queryClient, wrapper } = createWrapper()
    const spy = vi.spyOn(queryClient, 'invalidateQueries')

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

    expect(spy).toHaveBeenCalledWith({ queryKey: ['games'] })
    expect(toast.success).toHaveBeenCalledWith('Game updated')
  })
})

describe('useDeleteGame', () => {
  it('calls deleteGame with the given id', async () => {
    vi.mocked(adminApi.deleteGame).mockResolvedValue(undefined as never)
    const { wrapper } = createWrapper()

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

    expect(adminApi.deleteGame).toHaveBeenCalledWith(3)
  })

  it('invalidates games and shows a toast on success', async () => {
    vi.mocked(adminApi.deleteGame).mockResolvedValue(undefined as never)
    const { queryClient, wrapper } = createWrapper()
    const spy = vi.spyOn(queryClient, 'invalidateQueries')

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

    expect(spy).toHaveBeenCalledWith({ queryKey: ['games'] })
    expect(toast.success).toHaveBeenCalledWith('Game deleted')
  })
})
