import { describe, it, expect, beforeEach, vi } from 'vitest'
import { renderHook, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import type { TableRequestOptions, TableResponse } from '@carto/api-client'
import { useSourceStore } from '../providers/sources/store'
import type { QuerySource, Source } from '../providers/sources/types'
import { getTable, useTable } from './table'
import type { TableWidgetProps } from './types'

type GetTableFn = (opts: TableRequestOptions) => Promise<TableResponse>

describe('getTable ↔ useTable parity', () => {
  beforeEach(() => {
    useSourceStore.setState({ sources: {}, filters: {} })
  })

  it('useTable returns prefetched data on first render without re-fetching', async () => {
    const queryClient = new QueryClient({
      defaultOptions: { queries: { retry: false, staleTime: Infinity } },
    })

    const expected = { rows: [{ name: 'A' }, { name: 'B' }], totalCount: 2 }
    const widgetSpy = vi.fn().mockResolvedValue(expected)

    const widgetProps: TableWidgetProps = {
      columns: ['name'],
      sortBy: 'name',
      sortDirection: 'asc',
    } as unknown as TableWidgetProps

    const sourceConfig: Omit<Source<QuerySource>, 'id'> = {
      type: 'query',
      data: 'SELECT 1',
      connectionName: 'conn',
      apiBaseUrl: 'http://api',
    }
    useSourceStore.getState().setSource<QuerySource>('demo', sourceConfig)

    const source = useSourceStore.getState().sources.demo
    if (source?.widgets) {
      source.widgets.getTable = widgetSpy
    }

    await queryClient.prefetchQuery(
      getTable({
        accessToken: 'tok',
        sourceId: 'demo',
        widgetProps,
      }),
    )

    expect(widgetSpy).toHaveBeenCalledOnce()

    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    )

    const { result } = renderHook(
      () =>
        useTable({
          accessToken: 'tok',
          sourceId: 'demo',
          widgetProps,
        }),
      { wrapper },
    )

    await waitFor(() => {
      expect(result.current.data).toEqual(expected)
    })

    expect(widgetSpy).toHaveBeenCalledOnce()
  })
})

describe('getTable limit=0 paging', () => {
  beforeEach(() => {
    useSourceStore.setState({ sources: {}, filters: {} })
  })

  function setupSource(getTableImpl: GetTableFn) {
    const sourceConfig: Omit<Source<QuerySource>, 'id'> = {
      type: 'query',
      data: 'SELECT 1',
      connectionName: 'conn',
      apiBaseUrl: 'http://api',
    }
    useSourceStore.getState().setSource<QuerySource>('demo', sourceConfig)
    const source = useSourceStore.getState().sources.demo
    if (source?.widgets) {
      source.widgets.getTable = getTableImpl
    }
  }

  function makeRows(n: number) {
    return Array.from({ length: n }, (_, i) => ({ id: i }))
  }

  it('pages through all rows and combines them when limit=0', async () => {
    const queryClient = new QueryClient({
      defaultOptions: { queries: { retry: false, staleTime: Infinity } },
    })

    const getTableSpy = vi
      .fn<GetTableFn>()
      .mockResolvedValueOnce({ totalCount: 2500, rows: makeRows(1000) })
      .mockResolvedValueOnce({ totalCount: 2500, rows: makeRows(1000) })
      .mockResolvedValueOnce({ totalCount: 2500, rows: makeRows(500) })
    setupSource(getTableSpy)

    await queryClient.prefetchQuery(
      getTable({
        accessToken: 'tok',
        sourceId: 'demo',
        widgetProps: {
          columns: ['id'],
          limit: 0,
        } as unknown as TableWidgetProps,
      }),
    )

    expect(getTableSpy).toHaveBeenCalledTimes(3)
    const offsets = getTableSpy.mock.calls.map((c) => c[0].offset)
    expect(offsets).toEqual([0, 1000, 2000])
    for (const call of getTableSpy.mock.calls) {
      expect(call[0].limit).toBe(1000)
      // request params (filters/signal) forwarded to every page
      expect(call[0]).toHaveProperty('signal')
      expect(call[0]).toHaveProperty('filters')
    }

    const data = queryClient.getQueryData<{
      rows: unknown[]
      totalCount: number
    }>(
      getTable({
        accessToken: 'tok',
        sourceId: 'demo',
        widgetProps: {
          columns: ['id'],
          limit: 0,
        } as unknown as TableWidgetProps,
      }).queryKey,
    )

    expect(data!.rows).toHaveLength(2500)
    expect(data!.totalCount).toBe(2500)
  })

  it('short-circuits to a single call when totalCount fits in one page', async () => {
    const queryClient = new QueryClient({
      defaultOptions: { queries: { retry: false, staleTime: Infinity } },
    })

    const initial = { totalCount: 3, rows: makeRows(3) }
    const getTableSpy = vi.fn<GetTableFn>().mockResolvedValue(initial)
    setupSource(getTableSpy)

    await queryClient.prefetchQuery(
      getTable({
        accessToken: 'tok',
        sourceId: 'demo',
        widgetProps: {
          columns: ['id'],
          limit: 0,
        } as unknown as TableWidgetProps,
      }),
    )

    expect(getTableSpy).toHaveBeenCalledOnce()
    expect(getTableSpy.mock.calls[0]![0].limit).toBe(1000)
    expect(getTableSpy.mock.calls[0]![0].offset).toBe(0)
  })

  it('passes through without paging when limit is non-zero', async () => {
    const queryClient = new QueryClient({
      defaultOptions: { queries: { retry: false, staleTime: Infinity } },
    })

    const getTableSpy = vi
      .fn<GetTableFn>()
      .mockResolvedValue({ totalCount: 5000, rows: makeRows(10) })
    setupSource(getTableSpy)

    await queryClient.prefetchQuery(
      getTable({
        accessToken: 'tok',
        sourceId: 'demo',
        widgetProps: {
          columns: ['id'],
          limit: 10,
        } as unknown as TableWidgetProps,
      }),
    )

    expect(getTableSpy).toHaveBeenCalledOnce()
    expect(getTableSpy.mock.calls[0]![0].limit).toBe(10)
    // offset not rewritten by the paging path
    expect(getTableSpy.mock.calls[0]![0].offset).toBeUndefined()
  })
})
