import { QueryClientProvider } from '@tanstack/react-query'
import { renderHook, waitFor, act } from '@testing-library/react'
import { createTestQueryClient } from '../test/utils'
import {
  useRuns,
  useRun,
  useCreateRun,
  useUpdateRun,
  useDeleteRun,
  useNamingCategories,
  useNameSuggestions,
} from './useRuns'

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

import { getRuns, getRun, createRun, updateRun, deleteRun, getNamingCategories } from '../api/runs'
import { toast } from 'sonner'

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

describe('useRuns', () => {
  it('calls getRuns and returns data', async () => {
    const runs = [{ id: 1, name: 'My Run' }]
    vi.mocked(getRuns).mockResolvedValue(runs as never)
    const { wrapper } = createWrapper()

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

    expect(getRuns).toHaveBeenCalledOnce()
    expect(result.current.data).toEqual(runs)
  })
})

describe('useRun', () => {
  it('calls getRun with the given id', async () => {
    const run = { id: 3, name: 'Specific Run' }
    vi.mocked(getRun).mockResolvedValue(run as never)
    const { wrapper } = createWrapper()

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

    expect(getRun).toHaveBeenCalledWith(3)
  })
})

describe('useCreateRun', () => {
  it('calls createRun with the provided input', async () => {
    vi.mocked(createRun).mockResolvedValue({ id: 10 } as never)
    const { wrapper } = createWrapper()

    const { result } = renderHook(() => useCreateRun(), { wrapper })
    await act(async () => {
      await result.current.mutateAsync({ name: 'New Run', gameId: 1, status: 'active' } as never)
    })

    expect(createRun).toHaveBeenCalledWith({ name: 'New Run', gameId: 1, status: 'active' })
  })

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

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

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

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

    const { result } = renderHook(() => useUpdateRun(5), { wrapper })
    await act(async () => {
      await result.current.mutateAsync({ name: 'Updated' } as never)
    })

    expect(updateRun).toHaveBeenCalledWith(5, { name: 'Updated' })
  })

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

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

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

  it('shows a toast when status is set to completed', async () => {
    vi.mocked(updateRun).mockResolvedValue({} as never)
    const { wrapper } = createWrapper()

    const { result } = renderHook(() => useUpdateRun(1), { wrapper })
    await act(async () => {
      await result.current.mutateAsync({ status: 'completed' } as never)
    })

    expect(toast.success).toHaveBeenCalledWith('Run marked as completed!')
  })

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

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

    await waitFor(() =>
      expect(toast.error).toHaveBeenCalledWith('Failed to update run: Network error')
    )
  })
})

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

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

    expect(deleteRun).toHaveBeenCalledWith(7)
  })

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

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

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

describe('useNamingCategories', () => {
  it('calls getNamingCategories', async () => {
    vi.mocked(getNamingCategories).mockResolvedValue([] as never)
    const { wrapper } = createWrapper()

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

    expect(getNamingCategories).toHaveBeenCalledOnce()
  })
})

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