import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { act } from 'react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { IndicatorButton } from './indicator-button'

describe('IndicatorButton', () => {
  beforeEach(() => {
    vi.useFakeTimers()
  })

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

  describe('basic rendering', () => {
    it('renders button with correct index number', () => {
      const mockOnClick = vi.fn()
      render(
        <IndicatorButton
          index={0}
          selectedIndex={0}
          isNextSlide={false}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      expect(screen.getByRole('button')).toBeInTheDocument()
      expect(screen.getByText('01')).toBeInTheDocument()
    })

    it('renders two-digit index numbers', () => {
      const mockOnClick = vi.fn()
      render(
        <IndicatorButton
          index={9}
          selectedIndex={0}
          isNextSlide={false}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

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

    it('pads single digit index numbers with leading zero', () => {
      const mockOnClick = vi.fn()
      render(
        <IndicatorButton
          index={4}
          selectedIndex={0}
          isNextSlide={false}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

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

  describe('active state', () => {
    it('applies active styles when index equals selectedIndex', () => {
      const mockOnClick = vi.fn()
      render(
        <IndicatorButton
          index={2}
          selectedIndex={2}
          isNextSlide={false}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      const button = screen.getByRole('button')
      expect(button).toHaveClass('bg-text-primary')
    })

    it('applies inactive styles when index does not equal selectedIndex', () => {
      const mockOnClick = vi.fn()
      render(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={false}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      const button = screen.getByRole('button')
      expect(button).toHaveClass('bg-components-panel-on-panel-item-bg')
    })
  })

  describe('click handling', () => {
    it('calls onClick when button is clicked', () => {
      const mockOnClick = vi.fn()
      render(
        <IndicatorButton
          index={0}
          selectedIndex={0}
          isNextSlide={false}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      fireEvent.click(screen.getByRole('button'))
      expect(mockOnClick).toHaveBeenCalledTimes(1)
    })

    it('stops event propagation when clicked', () => {
      const mockOnClick = vi.fn()
      const mockParentClick = vi.fn()

      render(
        <div onClick={mockParentClick}>
          <IndicatorButton
            index={0}
            selectedIndex={0}
            isNextSlide={false}
            autoplayDelay={5000}
            resetKey={0}
            isPaused={false}
            onClick={mockOnClick}
          />
        </div>,
      )

      fireEvent.click(screen.getByRole('button'))
      expect(mockOnClick).toHaveBeenCalledTimes(1)
      expect(mockParentClick).not.toHaveBeenCalled()
    })
  })

  describe('progress indicator', () => {
    it('does not show progress indicator when not next slide', () => {
      const mockOnClick = vi.fn()
      const { container } = render(
        <IndicatorButton
          index={0}
          selectedIndex={0}
          isNextSlide={false}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      // Check for conic-gradient style which indicates progress indicator
      const progressIndicator = container.querySelector('[style*="conic-gradient"]')
      expect(progressIndicator).not.toBeInTheDocument()
    })

    it('shows progress indicator when isNextSlide is true and not active', () => {
      const mockOnClick = vi.fn()
      const { container } = render(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={true}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      const progressIndicator = container.querySelector('[style*="conic-gradient"]')
      expect(progressIndicator).toBeInTheDocument()
    })

    it('does not show progress indicator when isNextSlide but also active', () => {
      const mockOnClick = vi.fn()
      const { container } = render(
        <IndicatorButton
          index={0}
          selectedIndex={0}
          isNextSlide={true}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      const progressIndicator = container.querySelector('[style*="conic-gradient"]')
      expect(progressIndicator).not.toBeInTheDocument()
    })
  })

  describe('animation behavior', () => {
    it('starts progress from 0 when isNextSlide becomes true', () => {
      const mockOnClick = vi.fn()
      const { container, rerender } = render(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={false}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      // Initially no progress indicator
      expect(container.querySelector('[style*="conic-gradient"]')).not.toBeInTheDocument()

      // Rerender with isNextSlide=true
      rerender(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={true}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      // Now progress indicator should be visible
      expect(container.querySelector('[style*="conic-gradient"]')).toBeInTheDocument()
    })

    it('resets progress when resetKey changes', () => {
      const mockOnClick = vi.fn()
      const { rerender, container } = render(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={true}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      // Progress indicator should be present
      const progressIndicator = container.querySelector('[style*="conic-gradient"]')
      expect(progressIndicator).toBeInTheDocument()

      // Rerender with new resetKey - this should reset the progress animation
      rerender(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={true}
          autoplayDelay={5000}
          resetKey={1}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      const newProgressIndicator = container.querySelector('[style*="conic-gradient"]')
      // The progress indicator should still be present after reset
      expect(newProgressIndicator).toBeInTheDocument()
    })

    it('stops animation when isPaused is true', () => {
      const mockOnClick = vi.fn()
      const mockRequestAnimationFrame = vi.spyOn(window, 'requestAnimationFrame')

      render(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={true}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={true}
          onClick={mockOnClick}
        />,
      )

      // The component should still render but animation should be paused
      // requestAnimationFrame might still be called for polling but progress won't update
      expect(screen.getByRole('button')).toBeInTheDocument()
      mockRequestAnimationFrame.mockRestore()
    })

    it('cancels animation frame on unmount', () => {
      const mockOnClick = vi.fn()
      const mockCancelAnimationFrame = vi.spyOn(window, 'cancelAnimationFrame')

      const { unmount } = render(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={true}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      // Trigger animation frame
      act(() => {
        vi.advanceTimersToNextTimer()
      })

      unmount()

      expect(mockCancelAnimationFrame).toHaveBeenCalled()
      mockCancelAnimationFrame.mockRestore()
    })

    it('cancels animation frame when isNextSlide becomes false', () => {
      const mockOnClick = vi.fn()
      const mockCancelAnimationFrame = vi.spyOn(window, 'cancelAnimationFrame')

      const { rerender } = render(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={true}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      // Trigger animation frame
      act(() => {
        vi.advanceTimersToNextTimer()
      })

      // Change isNextSlide to false - this should cancel the animation frame
      rerender(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={false}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      expect(mockCancelAnimationFrame).toHaveBeenCalled()
      mockCancelAnimationFrame.mockRestore()
    })

    it('continues polling when document is hidden', () => {
      const mockOnClick = vi.fn()
      const mockRequestAnimationFrame = vi.spyOn(window, 'requestAnimationFrame')

      // Mock document.hidden to be true
      Object.defineProperty(document, 'hidden', {
        writable: true,
        configurable: true,
        value: true,
      })

      render(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={true}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      // Component should still render
      expect(screen.getByRole('button')).toBeInTheDocument()

      // Reset document.hidden
      Object.defineProperty(document, 'hidden', {
        writable: true,
        configurable: true,
        value: false,
      })

      mockRequestAnimationFrame.mockRestore()
    })
  })

  describe('isPaused prop default', () => {
    it('defaults isPaused to false when not provided', () => {
      const mockOnClick = vi.fn()
      const { container } = render(
        <IndicatorButton
          index={1}
          selectedIndex={0}
          isNextSlide={true}
          autoplayDelay={5000}
          resetKey={0}
          onClick={mockOnClick}
        />,
      )

      // Progress indicator should be visible (animation running)
      expect(container.querySelector('[style*="conic-gradient"]')).toBeInTheDocument()
    })
  })

  describe('button styling', () => {
    it('has correct base classes', () => {
      const mockOnClick = vi.fn()
      render(
        <IndicatorButton
          index={0}
          selectedIndex={1}
          isNextSlide={false}
          autoplayDelay={5000}
          resetKey={0}
          isPaused={false}
          onClick={mockOnClick}
        />,
      )

      const button = screen.getByRole('button')
      expect(button).toHaveClass('relative')
      expect(button).toHaveClass('flex')
      expect(button).toHaveClass('items-center')
      expect(button).toHaveClass('justify-center')
      expect(button).toHaveClass('rounded-[7px]')
      expect(button).toHaveClass('border')
      expect(button).toHaveClass('transition-colors')
    })
  })
})
