import { describe, it, expect, beforeEach, vi } from 'vitest'
import { renderHook, act } from '@testing-library/react'
import { AuthProvider, useAuth } from '@/auth/AuthContext'
import { ReactNode } from 'react'

// Мокаем localStorage
const localStorageMock = (() => {
  let store: Record<string, string> = {}
  return {
    getItem: vi.fn((key: string) => store[key] || null),
    setItem: vi.fn((key: string, value: string) => {
      store[key] = value
    }),
    removeItem: vi.fn((key: string) => {
      delete store[key]
    }),
    clear: vi.fn(() => {
      store = {}
    }),
  }
})()

Object.defineProperty(window, 'localStorage', {
  value: localStorageMock,
})

const wrapper = ({ children }: { children: ReactNode }) => (
  <AuthProvider>{children}</AuthProvider>
)

describe('AuthContext', () => {
  beforeEach(() => {
    localStorageMock.clear()
    vi.clearAllMocks()
  })

  describe('useAuth', () => {
    it('должен выбрасывать ошибку при использовании вне AuthProvider', () => {
      // Отключаем console.error для этого теста
      const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
      
      expect(() => {
        renderHook(() => useAuth())
      }).toThrow('useAuth must be used within an AuthProvider')
      
      consoleSpy.mockRestore()
    })
  })

  describe('initial state', () => {
    it('должен инициализироваться с token из localStorage', () => {
      localStorageMock.getItem.mockReturnValue('test-token-123')
      
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      expect(result.current.token).toBe('test-token-123')
      expect(result.current.isAuthenticated).toBe(true)
      expect(localStorageMock.getItem).toHaveBeenCalledWith('token')
    })

    it('должен инициализироваться с null если token отсутствует в localStorage', () => {
      localStorageMock.getItem.mockReturnValue(null)
      
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      expect(result.current.token).toBe(null)
      expect(result.current.isAuthenticated).toBe(false)
    })
  })

  describe('login', () => {
    it('должен сохранять токен в localStorage и state', () => {
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      act(() => {
        result.current.login('new-token-456')
      })
      
      expect(result.current.token).toBe('new-token-456')
      expect(result.current.isAuthenticated).toBe(true)
      expect(localStorageMock.setItem).toHaveBeenCalledWith('token', 'new-token-456')
    })

    it('должен обновлять isAuthenticated после login', () => {
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      expect(result.current.isAuthenticated).toBe(false)
      
      act(() => {
        result.current.login('another-token')
      })
      
      expect(result.current.isAuthenticated).toBe(true)
    })
  })

  describe('logout', () => {
    it('должен удалять токен из localStorage и state', () => {
      localStorageMock.getItem.mockReturnValue('existing-token')
      
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      expect(result.current.token).toBe('existing-token')
      
      act(() => {
        result.current.logout()
      })
      
      expect(result.current.token).toBe(null)
      expect(result.current.isAuthenticated).toBe(false)
      expect(localStorageMock.removeItem).toHaveBeenCalledWith('token')
    })

    it('должен корректно работать logout когда token уже null', () => {
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      act(() => {
        result.current.logout()
      })
      
      expect(result.current.token).toBe(null)
      expect(result.current.isAuthenticated).toBe(false)
      expect(localStorageMock.removeItem).toHaveBeenCalledWith('token')
    })
  })

  describe('isAuthenticated', () => {
    it('должен возвращать true когда token существует', () => {
      localStorageMock.getItem.mockReturnValue('valid-token')
      
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      expect(result.current.isAuthenticated).toBe(true)
    })

    it('должен возвращать false когда token null', () => {
      localStorageMock.getItem.mockReturnValue(null)
      
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      expect(result.current.isAuthenticated).toBe(false)
    })

    it('должен возвращать false когда token пустая строка', () => {
      localStorageMock.getItem.mockReturnValue('')
      
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      expect(result.current.isAuthenticated).toBe(false)
    })
  })

  describe('context methods', () => {
    it('должен предоставлять метод login', () => {
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      expect(result.current.login).toBeDefined()
      expect(typeof result.current.login).toBe('function')
    })

    it('должен предоставлять метод logout', () => {
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      expect(result.current.logout).toBeDefined()
      expect(typeof result.current.logout).toBe('function')
    })
  })

  describe('multiple login/logout cycles', () => {
    it('должен корректно обрабатывать несколько циклов login/logout', () => {
      const { result } = renderHook(() => useAuth(), { wrapper })
      
      // Первый цикл
      act(() => {
        result.current.login('token-1')
      })
      expect(result.current.token).toBe('token-1')
      
      act(() => {
        result.current.logout()
      })
      expect(result.current.token).toBe(null)
      
      // Второй цикл
      act(() => {
        result.current.login('token-2')
      })
      expect(result.current.token).toBe('token-2')
      
      act(() => {
        result.current.logout()
      })
      expect(result.current.token).toBe(null)
      
      // Третий цикл
      act(() => {
        result.current.login('token-3')
      })
      expect(result.current.token).toBe('token-3')
    })
  })
})
