import { describe, it, expect, beforeEach } from 'vitest'
import { renderHook } from '@testing-library/react'
import { useQueryKey } from './use-query-key'
import { useSourceStore } from '../../providers/sources/store'
import { wrapper } from '../../../tests/providers'
import type {
  AllExternalSources,
  QuerySource,
} from '../../providers/sources/types'

describe('useQueryKey', () => {
  const queryKeyPrefix = ['category', 'widget']

  beforeEach(() => {
    useSourceStore.setState({
      sources: {},
      filters: {},
    })
  })

  describe('single source', () => {
    it('should generate query key for single source', () => {
      const store = useSourceStore.getState()
      store.setSource<AllExternalSources>('source1', {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
      })

      const { result } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourceId: 'source1',
          }),
        { wrapper },
      )

      expect(Array.isArray(result.current)).toBe(true)
      expect(result.current[0]).toBe('source1')
      expect(result.current[1]).toBe('category')
      expect(result.current[2]).toBe('widget')
    })

    it('should include query parameters when present', () => {
      const store = useSourceStore.getState()
      store.setSource<QuerySource>('source1', {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
        queryParameters: { limit: 100, offset: 0 },
      })

      const { result } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourceId: 'source1',
          }),
        { wrapper },
      )

      const lastItem = result.current[result.current.length - 1]
      expect(typeof lastItem).toBe('string')
      expect(lastItem).toContain('limit')
      expect(lastItem).toContain('offset')
    })

    it('should include relevant headers when present', () => {
      const store = useSourceStore.getState()
      store.setSource<AllExternalSources>('source1', {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
        headers: {
          'X-Custom-Header': 'value',
          Authorization: 'Bearer token',
        },
      })

      const { result } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourceId: 'source1',
          }),
        { wrapper },
      )

      // Should have headers included
      expect(result.current.length).toBeGreaterThan(3)
    })

    it('should handle source without query parameters', () => {
      const store = useSourceStore.getState()
      store.setSource<QuerySource>('source1', {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
      })

      const { result } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourceId: 'source1',
          }),
        { wrapper },
      )

      expect(result.current[0]).toBe('source1')
      expect(result.current[1]).toBe('category')
      expect(result.current[2]).toBe('widget')
    })
  })

  describe('multiple sources', () => {
    it('should generate query keys for multiple sources', () => {
      const store = useSourceStore.getState()
      store.setSource<AllExternalSources>('source1', {
        type: 'query',
        data: 'SELECT * FROM table1',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
      })
      store.setSource<AllExternalSources>('source2', {
        type: 'query',
        data: 'SELECT * FROM table2',
        connectionName: 'connection2',
        apiBaseUrl: 'http://api',
      })

      const { result } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourcesId: ['source1', 'source2'],
          }),
        { wrapper },
      )

      expect(Array.isArray(result.current)).toBe(true)
      expect(result.current).toHaveLength(2)
      expect(result.current[0]?.[0]).toBe('source1')
      expect(result.current[1]?.[0]).toBe('source2')
    })

    it('should handle different query parameters per source', () => {
      const store = useSourceStore.getState()
      store.setSource<QuerySource>('source1', {
        type: 'query',
        data: 'SELECT * FROM table1',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
        queryParameters: { limit: 100 },
      })
      store.setSource<QuerySource>('source2', {
        type: 'query',
        data: 'SELECT * FROM table2',
        connectionName: 'connection2',
        apiBaseUrl: 'http://api',
        queryParameters: { limit: 200 },
      })

      const { result } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourcesId: ['source1', 'source2'],
          }),
        { wrapper },
      )

      const params1 = result.current[0]?.[
        result.current[0].length - 1
      ] as string
      const params2 = result.current[1]?.[
        result.current[1].length - 1
      ] as string

      expect(params1).toContain('100')
      expect(params2).toContain('200')
    })

    it('should handle mix of sources with and without parameters', () => {
      const store = useSourceStore.getState()
      store.setSource<QuerySource>('source1', {
        type: 'query',
        data: 'SELECT * FROM table1',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
        queryParameters: { limit: 100 },
      })
      store.setSource<QuerySource>('source2', {
        type: 'query',
        data: 'SELECT * FROM table2',
        connectionName: 'connection2',
        apiBaseUrl: 'http://api',
      })

      const { result } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourcesId: ['source1', 'source2'],
          }),
        { wrapper },
      )

      expect(result.current).toHaveLength(2)
      expect(result.current[0]?.length).toBeGreaterThan(
        result.current[1]?.length ?? 0,
      )
    })
  })

  describe('memoization', () => {
    it('should return same reference when inputs do not change', () => {
      const store = useSourceStore.getState()
      store.setSource<AllExternalSources>('source1', {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
      })

      const { result, rerender } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourceId: 'source1',
          }),
        { wrapper },
      )

      const firstResult = result.current
      rerender()
      const secondResult = result.current

      expect(firstResult).toBe(secondResult)
    })

    it('should return new reference when source changes', () => {
      const store = useSourceStore.getState()
      store.setSource<AllExternalSources>('source1', {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
      })

      const { result, rerender } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourceId: 'source1',
          }),
        { wrapper },
      )

      const firstResult = result.current

      // Update source
      store.setSource<AllExternalSources>('source1', {
        type: 'query',
        data: 'SELECT * FROM updated_table',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
      })

      rerender()
      const secondResult = result.current

      expect(firstResult).not.toBe(secondResult)
    })
  })

  describe('edge cases', () => {
    it('should handle non-existent source', () => {
      const { result } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourceId: 'non-existent',
          }),
        { wrapper },
      )

      expect(result.current[0]).toBe('non-existent')
      expect(result.current.length).toBe(3) // Just source, category, widget
    })

    it('should handle empty prefix', () => {
      const store = useSourceStore.getState()
      store.setSource<AllExternalSources>('source1', {
        type: 'query',
        data: 'SELECT * FROM table',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
      })

      const { result } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix: [],
            sourceId: 'source1',
          }),
        { wrapper },
      )

      expect(result.current[0]).toBe('source1')
      expect(result.current.length).toBeGreaterThanOrEqual(1)
    })

    it('should handle multi-source with missing earlier source', () => {
      const store = useSourceStore.getState()
      // Only register source2, not source1
      store.setSource<QuerySource>('source2', {
        type: 'query',
        data: 'SELECT * FROM table2',
        connectionName: 'connection2',
        apiBaseUrl: 'http://api',
        queryParameters: { limit: 50 },
      })

      const { result } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourcesId: ['source1', 'source2'],
          }),
        { wrapper },
      )

      expect(Array.isArray(result.current)).toBe(true)
      expect(result.current).toHaveLength(2)

      // First query key should be for source1 (missing)
      const firstKey = result.current[0]!
      expect(firstKey[0]).toBe('source1')
      expect(firstKey.length).toBe(3) // Just source, category, widget (no data/params)

      // Second query key should be for source2 (exists)
      const secondKey = result.current[1]!
      expect(secondKey[0]).toBe('source2')
      expect(secondKey.length).toBeGreaterThan(3) // Should include data and params
    })

    it('should handle multi-source progressive registration', () => {
      const store = useSourceStore.getState()

      // Initially only source2 is registered
      store.setSource<AllExternalSources>('source2', {
        type: 'query',
        data: 'SELECT * FROM table2',
        connectionName: 'connection2',
        apiBaseUrl: 'http://api',
      })

      const { result, rerender } = renderHook(
        () =>
          useQueryKey({
            queryKeyPrefix,
            sourcesId: ['source1', 'source2'],
          }),
        { wrapper },
      )

      const initialKeys = result.current

      // Now register source1
      store.setSource<AllExternalSources>('source1', {
        type: 'query',
        data: 'SELECT * FROM table1',
        connectionName: 'connection1',
        apiBaseUrl: 'http://api',
      })

      rerender()
      const updatedKeys = result.current

      // Keys should have changed after source1 was added
      expect(initialKeys).not.toBe(updatedKeys)

      // First key should now include source1 data
      const firstKey = updatedKeys[0]!
      expect(firstKey[0]).toBe('source1')
      expect(firstKey.length).toBeGreaterThan(3)

      // Second key should still be for source2
      const secondKey = updatedKeys[1]!
      expect(secondKey[0]).toBe('source2')
      expect(secondKey.length).toBeGreaterThan(3)
    })
  })
})
