/**
 * Tests for App.vue root component.
 *
 * Verifies that auth initialization happens on app startup and that
 * the loading state is properly displayed during initialization.
 */
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { setActivePinia, createPinia } from 'pinia'
import { defineComponent, h, nextTick } from 'vue'

// Track initialization calls and control resolution
let initializeResolve: ((value: boolean) => void) | null = null
let initializeCalled = false

// Mock useAuth composable
vi.mock('@/composables/useAuth', () => ({
  useAuth: () => ({
    initialize: vi.fn(() => {
      initializeCalled = true
      return new Promise<boolean>((resolve) => {
        initializeResolve = resolve
      })
    }),
    isInitialized: { value: false },
    isAuthenticated: { value: false },
    user: { value: null },
    isLoading: { value: false },
    error: { value: null },
  }),
}))

// Mock vue-router
vi.mock('vue-router', () => ({
  useRoute: () => ({
    meta: { layout: 'default' },
    name: 'Dashboard',
    path: '/',
  }),
  useRouter: () => ({
    push: vi.fn(),
    replace: vi.fn(),
  }),
}))

import App from './App.vue'

// Create stub components for layouts
const DefaultLayoutStub = defineComponent({
  name: 'DefaultLayout',
  setup(_, { slots }) {
    return () => h('div', { class: 'default-layout' }, slots.default?.())
  },
})

const MinimalLayoutStub = defineComponent({
  name: 'MinimalLayout',
  setup(_, { slots }) {
    return () => h('div', { class: 'minimal-layout' }, slots.default?.())
  },
})

const GameLayoutStub = defineComponent({
  name: 'GameLayout',
  setup(_, { slots }) {
    return () => h('div', { class: 'game-layout' }, slots.default?.())
  },
})

const LoadingOverlayStub = defineComponent({
  name: 'LoadingOverlay',
  setup() {
    return () => h('div', { class: 'loading-overlay-mock' })
  },
})

const ToastContainerStub = defineComponent({
  name: 'ToastContainer',
  setup() {
    return () => h('div', { class: 'toast-container-mock' })
  },
})

describe('App.vue', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
    initializeCalled = false
    initializeResolve = null
  })

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

  const mountApp = () => {
    return mount(App, {
      global: {
        stubs: {
          RouterView: true,
          DefaultLayout: DefaultLayoutStub,
          MinimalLayout: MinimalLayoutStub,
          GameLayout: GameLayoutStub,
          LoadingOverlay: LoadingOverlayStub,
          ToastContainer: ToastContainerStub,
          // Also stub async component wrappers
          AsyncComponentWrapper: true,
          Suspense: false,
        },
      },
    })
  }

  describe('auth initialization', () => {
    it('shows loading spinner while auth is initializing', async () => {
      /**
       * Test loading state during auth initialization.
       *
       * When the app starts, it should show a loading spinner while
       * auth tokens are being validated. This prevents navigation guards
       * from running before auth state is known.
       */
      const wrapper = mountApp()

      // Should show loading text initially
      expect(wrapper.text()).toContain('Loading...')

      wrapper.unmount()
    })

    it('calls initialize on mount', async () => {
      /**
       * Test that auth initialization is triggered on mount.
       *
       * The initialize() function must be called to validate tokens,
       * refresh if needed, and fetch the user profile.
       */
      const wrapper = mountApp()

      // Wait for onMounted to run
      await nextTick()

      expect(initializeCalled).toBe(true)

      wrapper.unmount()
    })

    it('hides loading spinner after initialization completes', async () => {
      /**
       * Test transition from loading to content.
       *
       * Once auth initialization completes, the loading spinner should
       * be hidden and the main app content should be rendered.
       */
      const wrapper = mountApp()

      // Wait for onMounted
      await nextTick()

      // Should show loading initially
      expect(wrapper.text()).toContain('Loading...')

      // Resolve initialization (successful)
      expect(initializeResolve).not.toBeNull()
      initializeResolve!(true)

      // Wait for state update
      await flushPromises()
      await nextTick()

      // Loading text should be gone
      expect(wrapper.text()).not.toContain('Loading...')

      wrapper.unmount()
    })
  })

  describe('initialization state machine', () => {
    it('starts in initializing state and transitions to initialized', async () => {
      /**
       * Test the auth initialization state machine.
       *
       * The app should start showing the loading state and transition
       * to the main content after initialization completes.
       */
      const wrapper = mountApp()
      await nextTick()

      // Before initialization completes
      expect(wrapper.find('.bg-gray-900').exists()).toBe(true)

      // Complete initialization (successful)
      initializeResolve!(true)
      await flushPromises()
      await nextTick()

      // After initialization - the fixed loading div should be gone
      // (checking that the loading screen specific class combo is gone)
      const loadingScreen = wrapper.find('.fixed.inset-0.z-50.bg-gray-900')
      expect(loadingScreen.exists()).toBe(false)

      wrapper.unmount()
    })

    it('renders app content even when initialization returns false (auth failed)', async () => {
      /**
       * Test that app renders correctly when auth initialization fails.
       *
       * When initialize() returns false (e.g., tokens invalid, network error
       * during profile fetch), the app should still transition out of the
       * loading state and render the main content. Navigation guards will
       * handle redirecting unauthenticated users to login.
       */
      const wrapper = mountApp()
      await nextTick()

      // Should show loading initially
      expect(wrapper.text()).toContain('Loading...')

      // Complete initialization with failure (auth failed)
      initializeResolve!(false)
      await flushPromises()
      await nextTick()

      // Loading screen should be gone - app renders even on auth failure
      const loadingScreen = wrapper.find('.fixed.inset-0.z-50.bg-gray-900')
      expect(loadingScreen.exists()).toBe(false)

      // Loading text should be gone
      expect(wrapper.text()).not.toContain('Loading...')

      wrapper.unmount()
    })
  })
})
