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

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

import {QuantitySelector} from './quantity-selector'

describe('QuantitySelector', () => {
  const defaultProps = {
    quantity: 1,
    onQuantityChange: vi.fn(),
    maxQuantity: 10,
  }

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

  describe('Rendering', () => {
    it('renders with initial quantity', () => {
      render(<QuantitySelector {...defaultProps} quantity={3} />)

      expect(screen.getByText('3')).toBeInTheDocument()
    })

    it('renders increment and decrement buttons', () => {
      render(<QuantitySelector {...defaultProps} />)

      const buttons = screen.getAllByRole('button')
      expect(buttons).toHaveLength(2)
    })

    it('displays correct quantity value', () => {
      render(<QuantitySelector {...defaultProps} quantity={5} />)

      expect(screen.getByText('5')).toBeInTheDocument()
    })
  })

  describe('Increment Functionality', () => {
    it('increments quantity on plus button click', async () => {
      const onQuantityChange = vi.fn()
      const user = userEvent.setup()

      render(
        <QuantitySelector
          {...defaultProps}
          quantity={1}
          onQuantityChange={onQuantityChange}
        />
      )

      const buttons = screen.getAllByRole('button')
      const incrementButton = buttons[1] // Plus button is second

      await user.click(incrementButton)

      await waitFor(() => {
        expect(onQuantityChange).toHaveBeenCalledWith(2)
      })
    })

    it('does not increment beyond maxQuantity', async () => {
      const onQuantityChange = vi.fn()
      const user = userEvent.setup()

      render(
        <QuantitySelector
          {...defaultProps}
          quantity={10}
          maxQuantity={10}
          onQuantityChange={onQuantityChange}
        />
      )

      const buttons = screen.getAllByRole('button')
      const incrementButton = buttons[1]

      await user.click(incrementButton)

      // Should not call onQuantityChange with value > maxQuantity
      await waitFor(() => {
        expect(onQuantityChange).toHaveBeenCalledWith(10)
      })
    })

    it('disables increment button at maxQuantity', async () => {
      const onQuantityChange = vi.fn()
      const user = userEvent.setup()

      render(
        <QuantitySelector
          {...defaultProps}
          quantity={10}
          maxQuantity={10}
          onQuantityChange={onQuantityChange}
        />
      )

      const buttons = screen.getAllByRole('button')
      const incrementButton = buttons[1]

      // Clear previous calls
      onQuantityChange.mockClear()

      // Try to increment at max - quantity should not change
      await user.click(incrementButton)

      // Should not call onQuantityChange with a value greater than max
      expect(onQuantityChange).not.toHaveBeenCalledWith(11)
      expect(screen.getByText('10')).toBeInTheDocument()
    })
  })

  describe('Decrement Functionality', () => {
    it('decrements quantity on minus button click', async () => {
      const onQuantityChange = vi.fn()
      const user = userEvent.setup()

      render(
        <QuantitySelector
          {...defaultProps}
          quantity={3}
          onQuantityChange={onQuantityChange}
        />
      )

      const buttons = screen.getAllByRole('button')
      const decrementButton = buttons[0] // Minus button is first

      await user.click(decrementButton)

      await waitFor(() => {
        expect(onQuantityChange).toHaveBeenCalledWith(2)
      })
    })

    it('does not decrement below minQuantity', async () => {
      const onQuantityChange = vi.fn()
      const user = userEvent.setup()

      render(
        <QuantitySelector
          {...defaultProps}
          quantity={1}
          minQuantity={1}
          onQuantityChange={onQuantityChange}
        />
      )

      const buttons = screen.getAllByRole('button')
      const decrementButton = buttons[0]

      await user.click(decrementButton)

      // Should not call onQuantityChange with value < minQuantity
      await waitFor(() => {
        expect(onQuantityChange).toHaveBeenCalledWith(1)
      })
    })

    it('respects custom minQuantity', async () => {
      const onQuantityChange = vi.fn()
      const user = userEvent.setup()

      render(
        <QuantitySelector
          {...defaultProps}
          quantity={5}
          minQuantity={3}
          onQuantityChange={onQuantityChange}
        />
      )

      const buttons = screen.getAllByRole('button')
      const decrementButton = buttons[0]

      // Click multiple times to try to go below minQuantity
      await user.click(decrementButton)
      await user.click(decrementButton)
      await user.click(decrementButton)

      await waitFor(() => {
        // Should not go below 3
        expect(onQuantityChange).toHaveBeenLastCalledWith(3)
      })
    })

    it('disables decrement button at minQuantity', async () => {
      const onQuantityChange = vi.fn()
      const user = userEvent.setup()

      render(
        <QuantitySelector
          {...defaultProps}
          quantity={1}
          minQuantity={1}
          onQuantityChange={onQuantityChange}
        />
      )

      const buttons = screen.getAllByRole('button')
      const decrementButton = buttons[0]

      // Clear previous calls
      onQuantityChange.mockClear()

      // Try to decrement at min - quantity should not change
      await user.click(decrementButton)

      // Should not call onQuantityChange with a value less than min
      expect(onQuantityChange).not.toHaveBeenCalledWith(0)
      expect(screen.getByText('1')).toBeInTheDocument()
    })
  })

  describe('Disabled State', () => {
    it('disables both buttons when disabled prop is true', async () => {
      const onQuantityChange = vi.fn()
      const user = userEvent.setup()

      render(
        <QuantitySelector
          {...defaultProps}
          quantity={5}
          disabled
          onQuantityChange={onQuantityChange}
        />
      )

      const buttons = screen.getAllByRole('button')

      await user.click(buttons[0]) // Try decrement
      await user.click(buttons[1]) // Try increment

      // onQuantityChange should still be called due to useEffect
      // but the buttons shouldn't change the internal state
      expect(screen.getByText('5')).toBeInTheDocument()
    })
  })

  describe('Props Updates', () => {
    it('updates quantity when prop changes', () => {
      const {rerender} = render(
        <QuantitySelector {...defaultProps} quantity={1} />
      )

      expect(screen.getByText('1')).toBeInTheDocument()

      rerender(<QuantitySelector {...defaultProps} quantity={5} />)

      expect(screen.getByText('5')).toBeInTheDocument()
    })

    it('calls onQuantityChange when quantity changes', async () => {
      const onQuantityChange = vi.fn()

      const {rerender} = render(
        <QuantitySelector
          {...defaultProps}
          quantity={1}
          onQuantityChange={onQuantityChange}
        />
      )

      // Initial render calls onQuantityChange
      expect(onQuantityChange).toHaveBeenCalledWith(1)

      rerender(
        <QuantitySelector
          {...defaultProps}
          quantity={3}
          onQuantityChange={onQuantityChange}
        />
      )

      await waitFor(() => {
        expect(onQuantityChange).toHaveBeenCalledWith(3)
      })
    })
  })

  describe('Edge Cases', () => {
    it('handles maxQuantity of 0', () => {
      render(
        <QuantitySelector
          {...defaultProps}
          quantity={0}
          maxQuantity={0}
          minQuantity={0}
        />
      )

      expect(screen.getByText('0')).toBeInTheDocument()

      // Both buttons should be disabled at 0
      // Verify the quantity stays at 0
      expect(screen.getByText('0')).toBeInTheDocument()
    })

    it('handles rapid clicking', async () => {
      const onQuantityChange = vi.fn()
      const user = userEvent.setup()

      render(
        <QuantitySelector
          {...defaultProps}
          quantity={5}
          onQuantityChange={onQuantityChange}
        />
      )

      const buttons = screen.getAllByRole('button')
      const incrementButton = buttons[1]

      // Rapid clicks
      await user.click(incrementButton)
      await user.click(incrementButton)
      await user.click(incrementButton)

      // Should handle all clicks properly
      await waitFor(() => {
        expect(onQuantityChange).toHaveBeenCalled()
      })
    })

    it('maintains bounds when quantity prop exceeds maxQuantity', () => {
      render(
        <QuantitySelector {...defaultProps} quantity={15} maxQuantity={10} />
      )

      // Should display the exceeded value
      expect(screen.getByText('15')).toBeInTheDocument()

      // Increment should be disabled when exceeding max
      // Verify the quantity is above max
      expect(screen.getByText('15')).toBeInTheDocument()
    })

    it('maintains bounds when quantity prop is below minQuantity', () => {
      render(
        <QuantitySelector {...defaultProps} quantity={0} minQuantity={1} />
      )

      // Should display the value below minimum
      expect(screen.getByText('0')).toBeInTheDocument()

      // Decrement should be disabled when below min
      // Verify the quantity is below min
      expect(screen.getByText('0')).toBeInTheDocument()
    })
  })

  describe('Visual Feedback', () => {
    it('applies correct styles to container', () => {
      render(<QuantitySelector {...defaultProps} />)

      const container = screen.getByTestId('QuantitySelector')
      expect(container).toHaveClass(
        'inline-flex',
        'bg-tertiary',
        'rounded-[8px]'
      )
    })

    it('shows different icon styles for enabled/disabled states', () => {
      const {rerender} = render(
        <QuantitySelector {...defaultProps} quantity={1} minQuantity={1} />
      )

      // At minimum, quantity is 1
      expect(screen.getByText('1')).toBeInTheDocument()

      // Change quantity to enable decrement
      rerender(
        <QuantitySelector {...defaultProps} quantity={2} minQuantity={1} />
      )

      // Should now show quantity 2
      expect(screen.getByText('2')).toBeInTheDocument()
    })
  })
})
