/**
 * Tests for AuthContext - auth-context.tsx
 *
 * TDD RED phase: Write failing tests first.
 */

import { describe, it, expect, vi } from 'vitest'
import { render, screen, waitFor, act } from '@testing-library/react'
import { AuthProvider, useAuth, type AuthContextValue } from '../../context/auth-context'
import type { AuthProvider as AuthProviderInterface } from '../../types'

// Helper component to consume auth context
function AuthConsumer({ onAuth }: { onAuth: (auth: AuthContextValue) => void }) {
  const auth = useAuth()
  onAuth(auth)
  return (
    <div>
      <span data-testid="authenticated">{auth.isAuthenticated.toString()}</span>
      <span data-testid="loading">{auth.isLoading.toString()}</span>
      <span data-testid="identity">{auth.identity?.fullName ?? 'none'}</span>
    </div>
  )
}

function createMockAuthProvider(overrides: Partial<AuthProviderInterface> = {}): AuthProviderInterface {
  return {
    login: vi.fn().mockResolvedValue(undefined),
    logout: vi.fn().mockResolvedValue(undefined),
    checkAuth: vi.fn().mockResolvedValue(undefined),
    checkError: vi.fn().mockResolvedValue(undefined),
    getIdentity: vi.fn().mockResolvedValue({ id: '1', fullName: 'Test User' }),
    getPermissions: vi.fn().mockResolvedValue(['admin']),
    ...overrides,
  }
}

describe('AuthContext', () => {
  describe('AuthProvider', () => {
    it('should check auth on mount and set authenticated state', async () => {
      const mockProvider = createMockAuthProvider()

      render(
        <AuthProvider authProvider={mockProvider}>
          <AuthConsumer onAuth={() => {}} />
        </AuthProvider>
      )

      // Initially loading
      expect(screen.getByTestId('loading').textContent).toBe('true')

      // Wait for auth check to complete
      await waitFor(() => {
        expect(screen.getByTestId('loading').textContent).toBe('false')
      })

      expect(mockProvider.checkAuth).toHaveBeenCalled()
      expect(mockProvider.getIdentity).toHaveBeenCalled()
      expect(mockProvider.getPermissions).toHaveBeenCalled()
      expect(screen.getByTestId('authenticated').textContent).toBe('true')
      expect(screen.getByTestId('identity').textContent).toBe('Test User')
    })

    it('should set unauthenticated state when checkAuth fails', async () => {
      const mockProvider = createMockAuthProvider({
        checkAuth: vi.fn().mockRejectedValue(new Error('Not authenticated')),
      })

      render(
        <AuthProvider authProvider={mockProvider}>
          <AuthConsumer onAuth={() => {}} />
        </AuthProvider>
      )

      await waitFor(() => {
        expect(screen.getByTestId('loading').textContent).toBe('false')
      })

      expect(screen.getByTestId('authenticated').textContent).toBe('false')
      expect(screen.getByTestId('identity').textContent).toBe('none')
    })

    it('should call authProvider.login and refresh auth on login', async () => {
      const mockProvider = createMockAuthProvider()
      let _authValue: AuthContextValue | undefined

      render(
        <AuthProvider authProvider={mockProvider}>
          <AuthConsumer onAuth={(auth) => { _authValue = auth }} />
        </AuthProvider>
      )

      await waitFor(() => {
        expect(screen.getByTestId('loading').textContent).toBe('false')
      })

      // Call login
      await act(async () => {
        await _authValue!.login({ username: 'test', password: 'pass' })
      })

      expect(mockProvider.login).toHaveBeenCalledWith({ username: 'test', password: 'pass' })
      // checkAuth called twice: once on mount, once after login
      expect(mockProvider.checkAuth).toHaveBeenCalledTimes(2)
    })

    it('should call authProvider.logout and clear state on logout', async () => {
      const mockProvider = createMockAuthProvider()
      let _authValue: AuthContextValue | undefined

      render(
        <AuthProvider authProvider={mockProvider}>
          <AuthConsumer onAuth={(auth) => { _authValue = auth }} />
        </AuthProvider>
      )

      await waitFor(() => {
        expect(screen.getByTestId('authenticated').textContent).toBe('true')
      })

      // Call logout
      await act(async () => {
        await _authValue!.logout()
      })

      expect(mockProvider.logout).toHaveBeenCalled()
      expect(screen.getByTestId('authenticated').textContent).toBe('false')
      expect(screen.getByTestId('identity').textContent).toBe('none')
    })

    it('should provide permissions from authProvider', async () => {
      const mockProvider = createMockAuthProvider({
        getPermissions: vi.fn().mockResolvedValue(['admin', 'user', 'editor']),
      })
      let _authValue: AuthContextValue | undefined

      render(
        <AuthProvider authProvider={mockProvider}>
          <AuthConsumer onAuth={(auth) => { _authValue = auth }} />
        </AuthProvider>
      )

      await waitFor(() => {
        expect(screen.getByTestId('loading').textContent).toBe('false')
      })

      expect(_authValue!.permissions).toEqual(['admin', 'user', 'editor'])
    })
  })

  describe('useAuth', () => {
    it('should throw error when used outside AuthProvider', () => {
      const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})

      expect(() => {
        render(<AuthConsumer onAuth={() => {}} />)
      }).toThrow('useAuth must be used within an AuthProvider')

      consoleError.mockRestore()
    })
  })
})
