import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount, VueWrapper } from '@vue/test-utils'
import { nextTick } from 'vue'
import LoginButton from '../LoginButton.vue'
import { authenticateParent, logoutParent } from '../../../stores/auth'

vi.mock('vue-router', () => ({
  useRouter: vi.fn(() => ({ push: vi.fn() })),
}))

// Mock imageCache module
vi.mock('@/common/imageCache', () => ({
  getCachedImageUrl: vi.fn(async (imageId: string) => `blob:mock-url-${imageId}`),
  revokeImageUrl: vi.fn(),
  revokeAllImageUrls: vi.fn(),
}))

// Create real Vue refs for isParentAuthenticated and isParentPersistent using vi.hoisted.
// Real Vue refs are required so Vue templates auto-unwrap them correctly in v-if conditions.
const { isParentAuthenticatedRef, isParentPersistentRef } = vi.hoisted(() => {
  // eslint-disable-next-line @typescript-eslint/no-require-imports
  const { ref } = require('vue')
  return {
    isParentAuthenticatedRef: ref(false),
    isParentPersistentRef: ref(false),
  }
})

vi.mock('../../../stores/auth', () => ({
  authenticateParent: vi.fn(),
  isParentAuthenticated: isParentAuthenticatedRef,
  isParentPersistent: isParentPersistentRef,
  logoutParent: vi.fn(),
  logoutUser: vi.fn(),
  consumePendingReturnUrl: vi.fn(() => null),
  hasPendingReturnUrl: vi.fn(() => false),
  clearPendingReturnUrl: vi.fn(),
}))

global.fetch = vi.fn()

describe('LoginButton', () => {
  let wrapper: VueWrapper<any>

  beforeEach(() => {
    vi.clearAllMocks()
    isParentAuthenticatedRef.value = false
    isParentPersistentRef.value = false
    ;(global.fetch as any).mockResolvedValue({
      ok: true,
      json: async () => ({
        image_id: 'test-image-id',
        first_name: 'John',
        email: 'john@example.com',
      }),
    })
  })

  afterEach(() => {
    if (wrapper) {
      wrapper.unmount()
    }
  })

  describe('Avatar Rendering', () => {
    it('renders avatar with image when image_id is available', async () => {
      wrapper = mount(LoginButton)
      await nextTick()
      // Wait for fetchUserProfile to complete
      await new Promise((resolve) => setTimeout(resolve, 100))

      // Component should be mounted and functional
      expect(wrapper.exists()).toBe(true)
      // Should have attempted to load user profile with credentials
      expect(global.fetch).toHaveBeenCalledWith('/api/user/profile', {
        credentials: 'include',
      })
    })

    it('renders avatar with initial when no image_id', async () => {
      ;(global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ first_name: 'Jane' }),
      })

      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))

      const avatarText = wrapper.find('.avatar-text')
      expect(avatarText.exists()).toBe(true)
      expect(avatarText.text()).toBe('J')
    })

    it('renders ? when no first_name available', async () => {
      ;(global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({}),
      })

      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))

      const avatarText = wrapper.find('.avatar-text')
      expect(avatarText.exists()).toBe(true)
      expect(avatarText.text()).toBe('?')
    })

    it('shows loading state initially', async () => {
      wrapper = mount(LoginButton)
      const loading = wrapper.find('.avatar-text')
      expect(loading.exists()).toBe(true)
      expect(loading.text()).toBe('...')
    })
  })

  describe('Dropdown Interactions', () => {
    beforeEach(async () => {
      isParentAuthenticatedRef.value = true
      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))
    })

    it('opens dropdown on click when authenticated', async () => {
      const button = wrapper.find('.avatar-btn')
      await button.trigger('click')
      await nextTick()

      const dropdown = wrapper.find('.dropdown-menu')
      expect(dropdown.exists()).toBe(true)
    })

    it('closes dropdown on Escape key', async () => {
      const button = wrapper.find('.avatar-btn')
      await button.trigger('click')
      await nextTick()

      let dropdown = wrapper.find('.dropdown-menu')
      expect(dropdown.exists()).toBe(true)

      await button.trigger('keydown', { key: 'Escape' })
      await nextTick()

      dropdown = wrapper.find('.dropdown-menu')
      expect(dropdown.exists()).toBe(false)
    })

    it('closes dropdown on outside click', async () => {
      const button = wrapper.find('.avatar-btn')
      await button.trigger('click')
      await nextTick()

      let dropdown = wrapper.find('.dropdown-menu')
      expect(dropdown.exists()).toBe(true)

      // Simulate outside click by directly calling the handler
      const outsideClick = new MouseEvent('mousedown', {
        bubbles: true,
        cancelable: true,
      })
      document.dispatchEvent(outsideClick)
      await nextTick()

      dropdown = wrapper.find('.dropdown-menu')
      expect(dropdown.exists()).toBe(false)
    })
  })

  describe('Keyboard Navigation', () => {
    beforeEach(async () => {
      isParentAuthenticatedRef.value = true
      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))
    })

    it('opens dropdown on Enter key', async () => {
      const button = wrapper.find('.avatar-btn')
      await button.trigger('keydown', { key: 'Enter' })
      await nextTick()

      const dropdown = wrapper.find('.dropdown-menu')
      expect(dropdown.exists()).toBe(true)
    })

    it('opens dropdown on Space key', async () => {
      const button = wrapper.find('.avatar-btn')
      await button.trigger('keydown', { key: ' ' })
      await nextTick()

      const dropdown = wrapper.find('.dropdown-menu')
      expect(dropdown.exists()).toBe(true)
    })

    it('navigates menu items with arrow keys', async () => {
      const button = wrapper.find('.avatar-btn')
      await button.trigger('click')
      await nextTick()

      const dropdown = wrapper.find('.dropdown-menu')
      expect(dropdown.exists()).toBe(true)

      // First item should be focused
      let menuItems = wrapper.findAll('.menu-item')
      expect(menuItems[0].attributes('aria-selected')).toBe('true')

      // Press down arrow
      await button.trigger('keydown', { key: 'ArrowDown' })
      await nextTick()

      menuItems = wrapper.findAll('.menu-item')
      expect(menuItems[1].attributes('aria-selected')).toBe('true')
    })
  })

  describe('ARIA Attributes', () => {
    it('has correct ARIA attributes', async () => {
      // Note: Due to vi.mock hoisting limitations, isParentAuthenticated
      // value is set when the mock is first created
      wrapper = mount(LoginButton)
      await nextTick()

      const button = wrapper.find('.avatar-btn')
      // Should have ar haspopup regardless of auth state
      expect(button.attributes('aria-haspopup')).toBe('menu')
      expect(button.attributes('aria-expanded')).toBe('false')
      // aria-label will vary based on the initial mock state
      expect(button.attributes('aria-label')).toBeTruthy()
    })

    it('has correct ARIA attributes when authenticated', async () => {
      isParentAuthenticatedRef.value = true
      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))

      const button = wrapper.find('.avatar-btn')
      expect(button.attributes('aria-label')).toBe('Parent menu')
      expect(button.attributes('aria-haspopup')).toBe('menu')
      expect(button.attributes('aria-expanded')).toBe('false')
    })

    it('updates aria-expanded when dropdown opens', async () => {
      isParentAuthenticatedRef.value = true
      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))

      const button = wrapper.find('.avatar-btn')
      await button.trigger('click')
      await nextTick()

      expect(button.attributes('aria-expanded')).toBe('true')
    })

    it('menu items have correct roles', async () => {
      isParentAuthenticatedRef.value = true
      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))

      const button = wrapper.find('.avatar-btn')
      await button.trigger('click')
      await nextTick()

      const dropdown = wrapper.find('.dropdown-menu')
      expect(dropdown.attributes('role')).toBe('menu')

      const menuItems = wrapper.findAll('.menu-item')
      menuItems.forEach((item) => {
        expect(item.attributes('role')).toBe('menuitem')
      })
    })
  })

  describe('Mode Logic', () => {
    it('button click handler works correctly', async () => {
      // Due to vi.mock limitations with reactivity, we test that
      // click handler is wired up correctly
      wrapper = mount(LoginButton, {
        global: {
          mocks: {
            $router: {
              push: vi.fn(),
            },
          },
        },
      })
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))

      const button = wrapper.find('.avatar-btn')
      await button.trigger('click')
      await nextTick()

      // Verify button is clickable and doesn't throw
      expect(button.exists()).toBe(true)
    })

    it('dropdown visibility controlled by auth state and open/close', async () => {
      //Test dropdown behavior when authenticated
      isParentAuthenticatedRef.value = true
      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))

      // Initially closed
      let dropdown = wrapper.find('.dropdown-menu')
      expect(dropdown.exists()).toBe(false)

      // Click to open
      const button = wrapper.find('.avatar-btn')
      await button.trigger('click')
      await nextTick()

      dropdown = wrapper.find('.dropdown-menu')
      expect(dropdown.exists()).toBe(true)
    })
  })

  describe('Focus Ring', () => {
    it('shows focus ring on keyboard focus', async () => {
      wrapper = mount(LoginButton)
      await nextTick()

      const button = wrapper.find('.avatar-btn')
      await button.trigger('focus')
      await nextTick()

      // Check for focus ring styles
      expect(button.classes()).toContain('avatar-btn')
    })
  })

  describe('Stub Icons', () => {
    it('renders stub icons for menu items', async () => {
      isParentAuthenticatedRef.value = true
      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))

      const button = wrapper.find('.avatar-btn')
      await button.trigger('click')
      await nextTick()

      const stubs = wrapper.findAll('.menu-icon-stub')
      expect(stubs.length).toBe(3)
    })
  })

  describe('PIN Modal - checkbox and persistent mode', () => {
    beforeEach(async () => {
      isParentAuthenticatedRef.value = false
      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))
    })

    it('checkbox is unchecked by default when modal opens', async () => {
      // Open modal by triggering the open-login event path
      // Mock has-pin response
      ;(global.fetch as any)
        .mockResolvedValueOnce({ ok: true, json: async () => ({ has_pin: true }) })
        .mockResolvedValueOnce({ ok: true, json: async () => ({ valid: true }) })

      const vm = wrapper.vm as any
      await vm.open()
      await nextTick()

      const checkbox = wrapper.find('.stay-checkbox')
      expect(checkbox.exists()).toBe(true)
      expect((checkbox.element as HTMLInputElement).checked).toBe(false)
    })

    it('submitting with checkbox checked calls authenticateParent(true)', async () => {
      const { authenticateParent } = await import('../../../stores/auth')
      ;(global.fetch as any)
        .mockResolvedValueOnce({ ok: true, json: async () => ({ has_pin: true }) })
        .mockResolvedValueOnce({ ok: true, json: async () => ({ valid: true }) })

      const vm = wrapper.vm as any
      await vm.open()
      await nextTick()

      const checkbox = wrapper.find('.stay-checkbox')
      await checkbox.setValue(true)
      await nextTick()

      const pinInput = wrapper.find('.pin-input')
      await pinInput.setValue('1234')
      await wrapper.find('form').trigger('submit')
      await nextTick()

      expect(authenticateParent).toHaveBeenCalledWith(true)
    })

    it('submitting without checking checkbox calls authenticateParent(false)', async () => {
      const { authenticateParent } = await import('../../../stores/auth')
      ;(global.fetch as any)
        .mockResolvedValueOnce({ ok: true, json: async () => ({ has_pin: true }) })
        .mockResolvedValueOnce({ ok: true, json: async () => ({ valid: true }) })

      const vm = wrapper.vm as any
      await vm.open()
      await nextTick()

      const pinInput = wrapper.find('.pin-input')
      await pinInput.setValue('1234')
      await wrapper.find('form').trigger('submit')
      await nextTick()

      expect(authenticateParent).toHaveBeenCalledWith(false)
    })
  })

  describe('Lock badge visibility', () => {
    it('lock badge is hidden when not authenticated', async () => {
      isParentAuthenticatedRef.value = false
      isParentPersistentRef.value = false
      wrapper = mount(LoginButton)
      await nextTick()

      const badge = wrapper.find('.persistent-badge')
      expect(badge.exists()).toBe(false)
    })

    it('lock badge is hidden when authenticated but non-persistent', async () => {
      isParentAuthenticatedRef.value = true
      isParentPersistentRef.value = false
      wrapper = mount(LoginButton)
      await nextTick()

      const badge = wrapper.find('.persistent-badge')
      expect(badge.exists()).toBe(false)
    })

    it('lock badge is visible when authenticated and persistent', async () => {
      isParentAuthenticatedRef.value = true
      isParentPersistentRef.value = true
      wrapper = mount(LoginButton)
      await nextTick()

      const badge = wrapper.find('.persistent-badge')
      expect(badge.exists()).toBe(true)
      expect(badge.text()).toBe('🔒')
    })
  })

  describe('User Email Display', () => {
    it('displays email in dropdown header when available', async () => {
      isParentAuthenticatedRef.value = true
      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))

      const button = wrapper.find('.avatar-btn')
      await button.trigger('click')
      await nextTick()

      const email = wrapper.find('.dropdown-user-email')
      expect(email.exists()).toBe(true)
      expect(email.text()).toBe('john@example.com')
    })

    it('does not display email element when email is not available', async () => {
      ;(global.fetch as any).mockResolvedValueOnce({
        ok: true,
        json: async () => ({ image_id: 'test-image-id', first_name: 'Jane' }),
      })

      isParentAuthenticatedRef.value = true
      wrapper = mount(LoginButton)
      await nextTick()
      await new Promise((resolve) => setTimeout(resolve, 50))

      const button = wrapper.find('.avatar-btn')
      await button.trigger('click')
      await nextTick()

      const email = wrapper.find('.dropdown-user-email')
      expect(email.exists()).toBe(false)
    })
  })
})
