import type { NavItem } from '../nav-selector'
import type { AppContextValue } from '@/context/app-context'
import {
  act,
  fireEvent,
  render,
  screen,
  waitFor,
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as React from 'react'
import { vi } from 'vitest'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useAppContext } from '@/context/app-context'
import { useRouter, useSelectedLayoutSegment } from '@/next/navigation'
import { AppModeEnum } from '@/types/app'
import Nav from '../index'

// Mock next/navigation
vi.mock('@/next/navigation', () => ({
  useSelectedLayoutSegment: vi.fn(),
  useRouter: vi.fn(),
}))

// Mock app store
vi.mock('@/app/components/app/store', () => ({
  useStore: vi.fn(),
}))

// Mock app context
vi.mock('@/context/app-context', () => ({
  useAppContext: vi.fn(),
}))

vi.mock('@/next/link', () => ({
  default: ({
    href,
    children,
    onClick,
    ...props
  }: React.AnchorHTMLAttributes<HTMLAnchorElement> & { href: string, children?: React.ReactNode }) => (
    <a
      href={href}
      onClick={(event) => {
        event.preventDefault()
        onClick?.(event)
      }}
      {...props}
    >
      {children}
    </a>
  ),
}))

describe('Nav Component', () => {
  const mockSetAppDetail = vi.fn()
  const mockOnCreate = vi.fn()
  const mockOnLoadMore = vi.fn()
  const mockPush = vi.fn()

  const navigationItems: NavItem[] = [
    {
      id: '1',
      name: 'Item 1',
      link: '/item1',
      icon_type: 'image',
      icon: 'icon1',
      icon_background: '#fff',
      icon_url: '/url1',
      mode: AppModeEnum.CHAT,
    },
    {
      id: '2',
      name: 'Item 2',
      link: '/item2',
      icon_type: 'image',
      icon: 'icon2',
      icon_background: '#000',
      icon_url: '/url2',
    },
  ]

  const defaultProps = {
    icon: <span data-testid="default-icon">Icon</span>,
    activeIcon: <span data-testid="active-icon">Active Icon</span>,
    text: 'Nav Text',
    activeSegment: 'explore',
    link: '/explore',
    isApp: false,
    navigationItems,
    createText: 'Create New',
    onCreate: mockOnCreate,
    onLoadMore: mockOnLoadMore,
  }

  beforeEach(() => {
    vi.clearAllMocks()
    vi.mocked(useSelectedLayoutSegment).mockReturnValue('explore')
    vi.mocked(useAppStore).mockReturnValue(mockSetAppDetail)
    vi.mocked(useAppContext).mockReturnValue({
      isCurrentWorkspaceEditor: true,
    } as unknown as AppContextValue)
    vi.mocked(useRouter).mockReturnValue({
      push: mockPush,
    } as unknown as ReturnType<typeof useRouter>)
  })

  describe('Rendering', () => {
    it('should render correctly when activated', () => {
      render(<Nav {...defaultProps} />)
      expect(screen.getByText('Nav Text')).toBeInTheDocument()
      expect(screen.getByTestId('active-icon')).toBeInTheDocument()
    })

    it('should render correctly when not activated', () => {
      vi.mocked(useSelectedLayoutSegment).mockReturnValue('other')
      render(<Nav {...defaultProps} />)
      expect(screen.getByTestId('default-icon')).toBeInTheDocument()
    })

    it('should handle array activeSegment', () => {
      render(<Nav {...defaultProps} activeSegment={['explore', 'apps']} />)
      expect(screen.getByTestId('active-icon')).toBeInTheDocument()
    })

    it('should not show hover background if not activated', () => {
      vi.mocked(useSelectedLayoutSegment).mockReturnValue('other')
      const { container } = render(<Nav {...defaultProps} />)
      const navDiv = container.firstChild as HTMLElement
      expect(navDiv.className).toContain(
        'hover:bg-components-main-nav-nav-button-bg-hover',
      )
    })
  })

  describe('User Interactions', () => {
    it('should call setAppDetail when clicked', () => {
      render(<Nav {...defaultProps} />)
      const link = screen.getByRole('link')
      fireEvent.click(link.firstChild!)
      expect(mockSetAppDetail).toHaveBeenCalled()
    })

    it('should not call setAppDetail when clicked with modifier keys', () => {
      render(<Nav {...defaultProps} />)
      const link = screen.getByRole('link')
      fireEvent.click(link.firstChild!, { metaKey: true })
      expect(mockSetAppDetail).not.toHaveBeenCalled()
    })

    it('should show ArrowNarrowLeft on hover when curNav is provided and activated', () => {
      const curNav = navigationItems[0]
      render(<Nav {...defaultProps} curNav={curNav} />)

      const navItem = screen.getByText('Nav Text').parentElement!
      fireEvent.mouseEnter(navItem)

      expect(screen.queryByTestId('active-icon')).not.toBeInTheDocument()

      fireEvent.mouseLeave(navItem)
      expect(screen.getByTestId('active-icon')).toBeInTheDocument()
    })
  })

  describe('NavSelector', () => {
    const curNav = navigationItems[0]

    it('should render NavSelector when activated and curNav is provided', () => {
      render(<Nav {...defaultProps} curNav={curNav} />)
      expect(screen.getByText('/')).toBeInTheDocument()
      expect(screen.getByText('Item 1')).toBeInTheDocument()
    })

    it('should open menu and show items when clicked', async () => {
      render(<Nav {...defaultProps} curNav={curNav} />)
      const selectorButton = screen.getByRole('button', { name: /Item 1/i })

      await act(async () => {
        fireEvent.click(selectorButton)
      })

      await waitFor(() => {
        expect(screen.getByText('Item 2')).toBeInTheDocument()
      })
    })

    it('should navigate when an item is selected', async () => {
      render(<Nav {...defaultProps} curNav={curNav} />)
      const selectorButton = screen.getByRole('button', { name: /Item 1/i })

      await act(async () => {
        fireEvent.click(selectorButton)
      })

      const item2 = await screen.findByText('Item 2')
      await act(async () => {
        fireEvent.click(item2)
      })

      expect(mockSetAppDetail).toHaveBeenCalled()
      expect(mockPush).toHaveBeenCalledWith('/item2')
    })

    it('should not navigate if selecting current nav item', async () => {
      render(<Nav {...defaultProps} curNav={curNav} />)
      const selectorButton = screen.getByRole('button', { name: /Item 1/i })

      await act(async () => {
        fireEvent.click(selectorButton)
      })

      const listItems = await screen.findAllByText('Item 1')
      const listItem = listItems.find(el => el.closest('[role="menuitem"]'))

      if (listItem) {
        await act(async () => {
          fireEvent.click(listItem)
        })
      }

      expect(mockPush).not.toHaveBeenCalled()
    })

    it('should call onCreate when create button is clicked', async () => {
      render(<Nav {...defaultProps} curNav={curNav} />)
      const selectorButton = screen.getByRole('button', { name: /Item 1/i })

      await act(async () => {
        fireEvent.click(selectorButton)
      })

      const createButton = await screen.findByText('Create New')
      await act(async () => {
        fireEvent.click(createButton)
      })

      expect(mockOnCreate).toHaveBeenCalledWith('')
    })

    it('should show sub-menu and call onCreate with types when isApp is true', async () => {
      const user = userEvent.setup()
      const clickCreateBranch = async (optionName: RegExp) => {
        const { unmount } = render(<Nav {...defaultProps} curNav={curNav} isApp />)
        await user.click(screen.getByRole('button', { name: /Item 1/i }))
        const createButton = await screen.findByRole('menuitem', { name: /Create New/i })
        await user.hover(createButton)
        fireEvent.click(await screen.findByRole('menuitem', { name: optionName }))
        unmount()
      }

      await clickCreateBranch(/app\.newApp\.startFromBlank/i)
      await clickCreateBranch(/app\.newApp\.startFromTemplate/i)
      await clickCreateBranch(/app\.importDSL/i)

      expect(mockOnCreate).toHaveBeenNthCalledWith(1, 'blank')
      expect(mockOnCreate).toHaveBeenNthCalledWith(2, 'template')
      expect(mockOnCreate).toHaveBeenNthCalledWith(3, 'dsl')
      expect(mockOnCreate).toHaveBeenCalledTimes(3)
    })

    it('should not show create button if NOT an editor', async () => {
      vi.mocked(useAppContext).mockReturnValue({
        isCurrentWorkspaceEditor: false,
      } as unknown as AppContextValue)
      render(<Nav {...defaultProps} curNav={curNav} />)
      const selectorButton = screen.getByRole('button', { name: /Item 1/i })

      await act(async () => {
        fireEvent.click(selectorButton)
      })

      await waitFor(() => {
        expect(screen.queryByText('Create New')).not.toBeInTheDocument()
      })
    })

    it('should show loading state in selector when isLoadingMore is true', async () => {
      render(<Nav {...defaultProps} curNav={curNav} isLoadingMore />)
      const selectorButton = screen.getByRole('button', { name: /Item 1/i })

      await act(async () => {
        fireEvent.click(selectorButton)
      })

      const status = await screen.findByRole('status')
      expect(status).toBeInTheDocument()
    })

    it('should call onLoadMore when scrolling reaches bottom', async () => {
      render(<Nav {...defaultProps} curNav={curNav} />)
      const selectorButton = screen.getByRole('button', { name: /Item 1/i })

      await act(async () => {
        fireEvent.click(selectorButton)
      })

      const scrollContainer = await screen.findByRole('menu').then((menu) => {
        const container = menu.querySelector('.overflow-auto')
        if (!container)
          throw new Error('Not found')
        return container as HTMLElement
      })

      vi.useFakeTimers()

      Object.defineProperty(scrollContainer, 'scrollHeight', {
        value: 600,
        configurable: true,
      })
      Object.defineProperty(scrollContainer, 'clientHeight', {
        value: 150,
        configurable: true,
      })
      Object.defineProperty(scrollContainer, 'scrollTop', {
        value: 500,
        configurable: true,
      })

      fireEvent.scroll(scrollContainer)

      act(() => {
        vi.runAllTimers()
      })

      expect(mockOnLoadMore).toHaveBeenCalled()
      vi.useRealTimers()
    })
  })
})
