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

// Must mock before importing router (which imports useAuthStore)
vi.mock('../../stores/auth.js', () => ({
  useAuthStore: vi.fn(),
}))

// Mock all lazily-imported view components so the router can be instantiated.
// These mocks also exercise the lazy-load code paths: because the router uses
// () => import(...) for these modules, Vitest's vi.mock() intercepts the dynamic
// import and resolves synchronously — verifying that guard logic is not broken
// by the switch from static to lazy imports (PERF-03 / Plan 11-02).
vi.mock('../../views/auth/LoginView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/auth/RegisterView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/auth/PasswordResetView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/auth/NewPasswordView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../layouts/AdminLayout.vue', () => ({ default: { template: '<div><router-view /></div>' } }))
vi.mock('../../views/admin/AdminOverviewView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/admin/AdminUsersView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/admin/AdminQuotasView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/admin/AdminAiView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/admin/AdminAuditView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/SharedView.vue', () => ({ default: { template: '<div />' } }))

// FileManagerView is the only statically-imported component (kept synchronous per D-10).
vi.mock('../../views/FileManagerView.vue', () => ({ default: { template: '<div />' } }))

// Lazy-loaded route components (now use () => import(...) in router/index.js).
vi.mock('../../views/TopicsView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/DocumentView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/SettingsView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/CloudFolderView.vue', () => ({ default: { template: '<div />' } }))
vi.mock('../../views/CloudStorageView.vue', () => ({ default: { template: '<div />' } }))

import { useAuthStore } from '../../stores/auth.js'
import router from '../index.js'

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

// ---------------------------------------------------------------------------
// Auth route meta
// ---------------------------------------------------------------------------

describe('router — auth route meta (SEC-07, AUTH-01)', () => {
  const authPaths = ['/login', '/register', '/password-reset', '/password-reset/confirm']

  it.each(authPaths)(
    'route %s has meta.public = true so guard skips auth check',
    (path) => {
      const route = router.getRoutes().find(r => r.path === path)
      expect(route, `route "${path}" should exist`).toBeDefined()
      expect(route.meta.public).toBe(true)
    }
  )

  it.each(authPaths)(
    'route %s has meta.layout = "auth" so sidebar is never rendered',
    (path) => {
      const route = router.getRoutes().find(r => r.path === path)
      expect(route, `route "${path}" should exist`).toBeDefined()
      expect(route.meta.layout).toBe('auth')
    }
  )
})

// ---------------------------------------------------------------------------
// Admin guard — non-admin blocked, admin redirected from user routes
// ---------------------------------------------------------------------------

describe('router — admin guard (SEC-07)', () => {
  it('redirects a non-admin user away from /admin to /', async () => {
    // Arrange: authenticated but role = 'viewer'
    useAuthStore.mockReturnValue({
      accessToken: 'fake-token',
      user: { id: '1', role: 'viewer' },
      refresh: vi.fn().mockResolvedValue(undefined),
    })

    // Act: attempt to navigate to /admin
    const result = await router.push('/admin')
    // The guard returns { path: '/' }, so the router resolves to '/'
    expect(router.currentRoute.value.path).toBe('/')
  })

  it('allows an admin user to reach /admin', async () => {
    // Arrange: authenticated as admin
    useAuthStore.mockReturnValue({
      accessToken: 'fake-token',
      user: { id: '1', role: 'admin' },
      refresh: vi.fn().mockResolvedValue(undefined),
    })

    await router.push('/admin')
    expect(router.currentRoute.value.path).toBe('/admin')
  })

  it('redirects an admin user away from / to /admin (D-09)', async () => {
    // Arrange: admin trying to navigate to a regular user route
    useAuthStore.mockReturnValue({
      accessToken: 'fake-token',
      user: { id: '1', role: 'admin' },
      refresh: vi.fn().mockResolvedValue(undefined),
    })

    // Act: admin attempts to visit the regular user home
    await router.push('/')
    // The D-09 guard branch returns { path: '/admin' }
    expect(router.currentRoute.value.path).toBe('/admin')
  })

  it('non-admin user is blocked from /admin/users (admin child route)', async () => {
    useAuthStore.mockReturnValue({
      accessToken: 'fake-token',
      user: { id: '2', role: 'viewer' },
      refresh: vi.fn().mockResolvedValue(undefined),
    })

    await router.push('/admin/users')
    // Guard returns { path: '/' } because to.matched.some(r => r.meta.requiresAdmin) is true
    expect(router.currentRoute.value.path).toBe('/')
  })

  it('non-admin user is blocked from /admin/quotas (admin child route)', async () => {
    useAuthStore.mockReturnValue({
      accessToken: 'fake-token',
      user: { id: '2', role: 'viewer' },
      refresh: vi.fn().mockResolvedValue(undefined),
    })

    await router.push('/admin/quotas')
    expect(router.currentRoute.value.path).toBe('/')
  })

  it('admin is redirected away from /settings to /admin (D-09)', async () => {
    useAuthStore.mockReturnValue({
      accessToken: 'fake-token',
      user: { id: '1', role: 'admin' },
      refresh: vi.fn().mockResolvedValue(undefined),
    })

    await router.push('/settings')
    expect(router.currentRoute.value.path).toBe('/admin')
  })

  it('admin is redirected away from /cloud to /admin (D-09)', async () => {
    useAuthStore.mockReturnValue({
      accessToken: 'fake-token',
      user: { id: '1', role: 'admin' },
      refresh: vi.fn().mockResolvedValue(undefined),
    })

    await router.push('/cloud')
    expect(router.currentRoute.value.path).toBe('/admin')
  })
})

// ---------------------------------------------------------------------------
// Refresh-before-guard — runs when access token is absent on reload
// ---------------------------------------------------------------------------

describe('router — refresh-before-guard (PERF-03, AUTH-01)', () => {
  it('calls authStore.refresh() when access token is absent for a protected route', async () => {
    const mockRefresh = vi.fn().mockResolvedValue(undefined)
    useAuthStore.mockReturnValue({
      accessToken: null, // simulates page reload (token is memory-only)
      user: { id: '3', role: 'viewer' },
      refresh: mockRefresh,
    })

    await router.push('/topics')
    expect(mockRefresh).toHaveBeenCalledOnce()
  })

  it('redirects to /login when refresh fails and no token exists', async () => {
    useAuthStore.mockReturnValue({
      accessToken: null,
      user: null,
      refresh: vi.fn().mockRejectedValue(new Error('session expired')),
    })

    await router.push('/settings')
    expect(router.currentRoute.value.path).toBe('/login')
  })

  it('does NOT call refresh() for public routes', async () => {
    const mockRefresh = vi.fn()
    useAuthStore.mockReturnValue({
      accessToken: null,
      user: null,
      refresh: mockRefresh,
    })

    await router.push('/login')
    expect(mockRefresh).not.toHaveBeenCalled()
    expect(router.currentRoute.value.path).toBe('/login')
  })
})

// ---------------------------------------------------------------------------
// Lazy-loaded routes resolve correctly for authenticated regular users
// (verifies that switching from static to () => import() did not break routing)
// ---------------------------------------------------------------------------

describe('router — lazy-loaded routes resolve (PERF-03)', () => {
  const regularUserRoutes = [
    '/topics',
    '/topics/finance',
    '/document/some-uuid',
    '/settings',
    '/cloud',
    '/cloud/onedrive/root',
  ]

  it.each(regularUserRoutes)(
    'route %s resolves for an authenticated regular user',
    async (path) => {
      useAuthStore.mockReturnValue({
        accessToken: 'fake-token',
        user: { id: '4', role: 'viewer' },
        refresh: vi.fn().mockResolvedValue(undefined),
      })

      await router.push(path)
      // The guard does not redirect regular users away from their own routes.
      // Because router.push() resolves after guards run and the component is
      // loaded, currentRoute.path should match the navigation target.
      expect(router.currentRoute.value.path).toBe(path)
    }
  )

  it('/folders/:folderId reuses synchronous FileManagerView (D-10)', async () => {
    useAuthStore.mockReturnValue({
      accessToken: 'fake-token',
      user: { id: '4', role: 'viewer' },
      refresh: vi.fn().mockResolvedValue(undefined),
    })

    await router.push('/folders/abc-123')
    expect(router.currentRoute.value.path).toBe('/folders/abc-123')
  })

  it('/shared resolves for an authenticated regular user', async () => {
    useAuthStore.mockReturnValue({
      accessToken: 'fake-token',
      user: { id: '4', role: 'viewer' },
      refresh: vi.fn().mockResolvedValue(undefined),
    })

    await router.push('/shared')
    expect(router.currentRoute.value.path).toBe('/shared')
  })
})
