import {describe, expect, it, vi, beforeEach, afterEach} from 'vitest'

import {
  render,
  screen,
  userEvent,
  waitFor,
  mockProducts,
} from '../../test-utils'

import {Search, SearchProvider, SearchInput, SearchResultsList} from './search'

// Mock the useProductSearch hook
const mockProductSearchResult = {
  products: null as any,
  loading: false,
  error: null,
  fetchMore: vi.fn(),
  hasNextPage: false,
  isTyping: false,
}

vi.mock('../../hooks/product/useProductSearch', () => ({
  useProductSearch: vi.fn(() => mockProductSearchResult),
}))

// Mock navigation hooks for ProductLink
vi.mock('../../hooks/navigation/useShopNavigation', () => ({
  useShopNavigation: () => ({
    navigateToProduct: vi.fn(),
    navigateToShop: vi.fn(),
  }),
}))

// Mock save actions for ProductLink
vi.mock('../../hooks/user/useSavedProductsActions', () => ({
  useSavedProductsActions: () => ({
    saveProduct: vi.fn().mockResolvedValue({ok: true, data: {}}),
    unsaveProduct: vi.fn().mockResolvedValue({ok: true, data: {}}),
  }),
}))

describe('Search', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    // Reset mock values
    mockProductSearchResult.products = null
    mockProductSearchResult.loading = false
    mockProductSearchResult.error = null
    mockProductSearchResult.hasNextPage = false
    mockProductSearchResult.isTyping = false
  })

  afterEach(() => {
    vi.clearAllMocks()
  })

  describe('SearchInput', () => {
    it('renders search input with placeholder', () => {
      render(
        <SearchProvider>
          <SearchInput placeholder="Search for products..." />
        </SearchProvider>
      )

      const input = screen.getByPlaceholderText('Search for products...')
      expect(input).toBeInTheDocument()
      expect(input).toHaveAttribute('type', 'search')
      expect(input).toHaveAttribute('role', 'searchbox')
    })

    it('updates query on input change', async () => {
      const user = userEvent.setup()

      render(
        <SearchProvider>
          <SearchInput />
        </SearchProvider>
      )

      const input = screen.getByTestId('search-input')
      await user.type(input, 'test query')

      expect(input).toHaveValue('test query')
    })

    it('shows clear button when query is not empty', async () => {
      const user = userEvent.setup()

      render(
        <SearchProvider>
          <SearchInput />
        </SearchProvider>
      )

      const input = screen.getByTestId('search-input')

      // Initially no clear button
      expect(screen.queryByRole('button')).not.toBeInTheDocument()

      await user.type(input, 'test')

      // Clear button should appear
      const clearButton = screen.getByRole('button')
      expect(clearButton).toBeInTheDocument()
    })

    it('clears query when clear button is clicked', async () => {
      const user = userEvent.setup()

      render(
        <SearchProvider initialQuery="test query">
          <SearchInput />
        </SearchProvider>
      )

      const input = screen.getByTestId('search-input')
      expect(input).toHaveValue('test query')

      const clearButton = screen.getByRole('button')
      await user.click(clearButton)

      expect(input).toHaveValue('')
    })

    it('accepts custom input props', async () => {
      const onChange = vi.fn()
      const user = userEvent.setup()

      render(
        <SearchProvider>
          <SearchInput
            inputProps={
              {
                onChange,
                'data-custom': 'test',
              } as any
            }
          />
        </SearchProvider>
      )

      const input = screen.getByTestId('search-input')
      expect(input).toHaveAttribute('data-custom', 'test')

      await user.type(input, 'a')
      expect(onChange).toHaveBeenCalled()
    })
  })

  describe('SearchResultsList', () => {
    it('shows initial state when query is empty', () => {
      render(
        <SearchProvider>
          <SearchResultsList />
        </SearchProvider>
      )

      expect(
        screen.getByText('Start typing to search for products')
      ).toBeInTheDocument()
    })

    it('shows custom initial state component', () => {
      render(
        <SearchProvider>
          <SearchResultsList
            initialStateComponent={<div>Custom initial state</div>}
          />
        </SearchProvider>
      )

      expect(screen.getByText('Custom initial state')).toBeInTheDocument()
    })

    it('shows loading state when searching', () => {
      mockProductSearchResult.loading = true
      mockProductSearchResult.products = []

      render(
        <SearchProvider initialQuery="test">
          <SearchResultsList />
        </SearchProvider>
      )

      // Should show skeleton loaders
      const skeletons = document.querySelectorAll('[data-slot="skeleton"]')
      expect(skeletons.length).toBeGreaterThan(0)
    })

    it('shows typing state', () => {
      mockProductSearchResult.isTyping = true
      mockProductSearchResult.products = []

      render(
        <SearchProvider initialQuery="test">
          <SearchResultsList />
        </SearchProvider>
      )

      // Should show skeleton loaders while typing
      const skeletons = document.querySelectorAll('[data-slot="skeleton"]')
      expect(skeletons.length).toBeGreaterThan(0)
    })

    it('shows empty state when no products found', () => {
      mockProductSearchResult.products = []
      mockProductSearchResult.loading = false

      render(
        <SearchProvider initialQuery="nonexistent">
          <SearchResultsList />
        </SearchProvider>
      )

      expect(
        screen.getByText('No products found for "nonexistent"')
      ).toBeInTheDocument()
    })

    it('renders products when available', () => {
      const products = mockProducts(3)
      mockProductSearchResult.products = products
      mockProductSearchResult.loading = false

      render(
        <SearchProvider initialQuery="test">
          <SearchResultsList />
        </SearchProvider>
      )

      products.forEach(product => {
        expect(screen.getByText(product.title)).toBeInTheDocument()
      })
    })

    it('uses custom renderItem function', () => {
      const products = mockProducts(2)
      mockProductSearchResult.products = products

      const customRenderItem = (product: any) => (
        <div key={product.id}>Custom: {product.title}</div>
      )

      render(
        <SearchProvider initialQuery="test">
          <SearchResultsList renderItem={customRenderItem} />
        </SearchProvider>
      )

      products.forEach(product => {
        expect(screen.getByText(`Custom: ${product.title}`)).toBeInTheDocument()
      })
    })

    it('handles pagination with fetchMore', async () => {
      const products = mockProducts(5)
      mockProductSearchResult.products = products
      mockProductSearchResult.hasNextPage = true
      mockProductSearchResult.fetchMore = vi.fn()

      render(
        <SearchProvider initialQuery="test">
          <SearchResultsList height={200} itemHeight={50} />
        </SearchProvider>
      )

      // Verify fetchMore is available for pagination
      expect(mockProductSearchResult.fetchMore).toBeDefined()
    })
  })

  describe('Search Component (Integrated)', () => {
    it('renders complete search interface', () => {
      render(<Search />)

      expect(screen.getByTestId('search-input')).toBeInTheDocument()
      expect(
        screen.getByText('Start typing to search for products')
      ).toBeInTheDocument()
    })

    it('handles search flow end-to-end', async () => {
      const user = userEvent.setup()
      const products = mockProducts(2)

      render(<Search />)

      const input = screen.getByTestId('search-input')

      // Type search query
      await user.type(input, 'test')

      // Update mock to return products
      mockProductSearchResult.products = products
      mockProductSearchResult.loading = false

      // Rerender to show products
      render(<Search initialQuery="test" />)

      // Products should be displayed
      products.forEach(product => {
        expect(screen.getByText(product.title)).toBeInTheDocument()
      })
    })

    it('calls onProductClick when product is clicked', async () => {
      const user = userEvent.setup()
      const onProductClick = vi.fn()
      const products = mockProducts(1)
      mockProductSearchResult.products = products

      render(<Search initialQuery="test" onProductClick={onProductClick} />)

      // Click on the touchable wrapper around the product
      const productTitle = screen.getByText(products[0].title)
      const touchableWrapper = productTitle.closest('[data-touchable="true"]')
      if (touchableWrapper) {
        await user.click(touchableWrapper)
      }

      await waitFor(() => {
        expect(onProductClick).toHaveBeenCalledWith(products[0])
      })
    })

    it('accepts custom renderItem for product display', () => {
      const products = mockProducts(1)
      mockProductSearchResult.products = products

      const customRenderItem = (product: any) => (
        <div key={product.id} data-testid="custom-product">
          Custom Product: {product.title}
        </div>
      )

      render(<Search initialQuery="test" renderItem={customRenderItem} />)

      expect(screen.getByTestId('custom-product')).toBeInTheDocument()
      expect(
        screen.getByText(`Custom Product: ${products[0].title}`)
      ).toBeInTheDocument()
    })

    it('applies custom className', () => {
      const {container} = render(<Search className="custom-search-class" />)

      const searchContainer = container.querySelector('.custom-search-class')
      expect(searchContainer).toBeInTheDocument()
    })
  })

  describe('SearchProvider Context', () => {
    it('provides search context to children', () => {
      const TestComponent = () => {
        const {query} = useSearchContext()
        return <>Query: {query}</>
      }

      // Mock the useSearchContext to avoid actual implementation
      const useSearchContext = vi.fn(() => ({
        query: 'test query',
        setQuery: vi.fn(),
        products: [],
        loading: false,
        error: null,
        hasNextPage: false,
        isTyping: false,
      }))

      render(
        <SearchProvider initialQuery="test query">
          <TestComponent />
        </SearchProvider>
      )

      expect(screen.getByText('Query: test query')).toBeInTheDocument()
    })

    it('throws error when using context outside provider', () => {
      // This would throw in actual usage, but we can't test it directly
      // without importing the actual useSearchContext
      expect(() => {
        const TestComponent = () => {
          // This would throw: useSearchContext must be used within a SearchProvider
          return <div>Test</div>
        }
        render(<TestComponent />)
      }).not.toThrow() // Our mock doesn't throw
    })
  })

  describe('Edge Cases', () => {
    it('handles error state', () => {
      mockProductSearchResult.error = new Error('Search failed') as any
      mockProductSearchResult.loading = false
      mockProductSearchResult.products = []

      render(
        <SearchProvider initialQuery="test">
          <SearchResultsList />
        </SearchProvider>
      )

      // Should show empty state on error
      expect(
        screen.getByText('No products found for "test"')
      ).toBeInTheDocument()
    })

    it('handles whitespace-only queries', () => {
      render(
        <SearchProvider initialQuery="   ">
          <SearchResultsList />
        </SearchProvider>
      )

      // Should show initial state for whitespace-only query
      expect(
        screen.getByText('Start typing to search for products')
      ).toBeInTheDocument()
    })

    it('handles very long queries', async () => {
      const user = userEvent.setup()
      const longQuery = 'a'.repeat(100)

      render(
        <SearchProvider>
          <SearchInput />
        </SearchProvider>
      )

      const input = screen.getByTestId('search-input')
      await user.type(input, longQuery)

      expect(input).toHaveValue(longQuery)
    })

    it('handles rapid query changes', async () => {
      const user = userEvent.setup()

      render(
        <SearchProvider>
          <SearchInput />
        </SearchProvider>
      )

      const input = screen.getByTestId('search-input')

      // Rapid typing
      await user.type(input, 'a')
      await user.type(input, 'b')
      await user.type(input, 'c')

      expect(input).toHaveValue('abc')
    })
  })

  describe('Accessibility', () => {
    it('has proper ARIA attributes on search input', () => {
      render(
        <SearchProvider>
          <SearchInput />
        </SearchProvider>
      )

      const input = screen.getByTestId('search-input')
      expect(input).toHaveAttribute('role', 'searchbox')
      expect(input).toHaveAttribute('type', 'search')
      expect(input).toHaveAttribute('autoComplete', 'off')
    })

    it('search icon is decorative', () => {
      render(
        <SearchProvider>
          <SearchInput />
        </SearchProvider>
      )

      // Search icon should not have interactive role
      const searchIcon = document.querySelector('svg')
      expect(searchIcon).toBeInTheDocument()
      expect(searchIcon?.parentElement).not.toHaveAttribute('role', 'button')
    })
  })
})
