import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react'

const AuthContext = createContext(null)

const TOKEN_KEY = 'access_token'
const REFRESH_TOKEN_KEY = 'refresh_token'
const USER_KEY = 'user'

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  
  // Ref для синхронизации параллельных refresh-запросов
  const refreshPromiseRef = useRef(null)

  const logout = useCallback(async () => {
    const token = localStorage.getItem(TOKEN_KEY)
    
    if (token) {
      try {
        await fetch('/api/auth/logout', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
          }
        })
      } catch (err) {
        console.error('Logout error:', err)
      }
    }
    
    localStorage.removeItem(TOKEN_KEY)
    localStorage.removeItem(REFRESH_TOKEN_KEY)
    localStorage.removeItem(USER_KEY)
    setUser(null)
  }, [])

  // Внутренняя функция для выполнения refresh
  const doRefreshToken = useCallback(async () => {
    const refresh = localStorage.getItem(REFRESH_TOKEN_KEY)
    
    if (!refresh) {
      console.warn('[Auth] No refresh token in localStorage')
      return { success: false, isNetworkError: false }
    }
    
    console.log('[Auth] Attempting refresh with token:', refresh.substring(0, 10) + '...')
    
    try {
      const controller = new AbortController()
      const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout (increased)
      
      const response = await fetch('/api/auth/refresh', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ refresh_token: refresh }),
        signal: controller.signal
      })
      
      clearTimeout(timeoutId)
      
      if (!response.ok) {
        // Логируем тело ответа для диагностики
        let errorBody = ''
        try {
          errorBody = await response.text()
        } catch (e) {
          errorBody = 'Could not read error body'
        }
        console.error('[Auth] Refresh failed:', response.status, errorBody)
        
        // 401 means invalid token (real auth error)
        // Other errors might be temporary (503, 502, etc.)
        const isAuthError = response.status === 401
        return { success: false, isNetworkError: !isAuthError }
      }
      
      const data = await response.json()
      
      // Проверяем что токены действительно пришли
      if (!data.access_token || !data.refresh_token) {
        console.error('[Auth] Refresh response missing tokens:', Object.keys(data))
        return { success: false, isNetworkError: false }
      }
      
      console.log('[Auth] Refresh successful, saving new tokens')
      localStorage.setItem(TOKEN_KEY, data.access_token)
      localStorage.setItem(REFRESH_TOKEN_KEY, data.refresh_token)
      localStorage.setItem(USER_KEY, JSON.stringify(data.user))
      setUser(data.user)
      
      return { success: true, isNetworkError: false }
    } catch (err) {
      console.error('[Auth] Refresh error:', err.name, err.message)
      // Network errors should be treated as temporary
      if (err.name === 'AbortError' || 
          (err.name === 'TypeError' && (err.message.includes('fetch') || err.message.includes('Failed to fetch')))) {
        console.warn('[Auth] Refresh token network error, keeping session')
        return { success: false, isNetworkError: true }
      }
      // Other errors might be auth related
      return { success: false, isNetworkError: false }
    }
  }, [])

  // Синхронизированная функция refresh - предотвращает race condition
  // Если refresh уже выполняется, все вызовы ждут его завершения
  const refreshToken = useCallback(async () => {
    // Если refresh уже выполняется, ждём его завершения
    if (refreshPromiseRef.current) {
      console.log('[Auth] Refresh already in progress, waiting...')
      return refreshPromiseRef.current
    }
    
    // Создаём promise для refresh и сохраняем его
    console.log('[Auth] Starting token refresh...')
    refreshPromiseRef.current = doRefreshToken().finally(() => {
      // Очищаем ref после завершения (успешного или нет)
      refreshPromiseRef.current = null
    })
    
    return refreshPromiseRef.current
  }, [doRefreshToken])

  // Initialize from localStorage
  useEffect(() => {
    const initAuth = async () => {
      const token = localStorage.getItem(TOKEN_KEY)
      const savedUser = localStorage.getItem(USER_KEY)
      
      console.log('[Auth] Initializing auth, token exists:', !!token, 'user exists:', !!savedUser)
      
      if (token && savedUser) {
        try {
          const parsedUser = JSON.parse(savedUser)
          setUser(parsedUser) // Set user immediately from localStorage
          console.log('[Auth] User restored from localStorage:', parsedUser.email)
          
          // Verify token is still valid with timeout
          const controller = new AbortController()
          const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout
          
          const response = await fetch('/api/auth/me', {
            headers: {
              'Authorization': `Bearer ${token}`,
              'Content-Type': 'application/json'
            },
            signal: controller.signal
          })
          
          clearTimeout(timeoutId)
          
          if (response.ok) {
            const data = await response.json()
            setUser(data.user)
            localStorage.setItem(USER_KEY, JSON.stringify(data.user))
            console.log('[Auth] Token verified successfully')
          } else if (response.status === 401) {
            // Try to refresh token
            console.log('[Auth] Access token expired, attempting refresh...')
            const result = await refreshToken()
            if (!result.success && !result.isNetworkError) {
              // Only logout on real auth errors, not network errors
              console.warn('[Auth] Refresh failed with auth error, logging out')
              logout()
            } else if (!result.success) {
              // Network error - keep session, backend might be starting up
              console.warn('[Auth] Token refresh failed due to network error, keeping session. User remains logged in.')
              // User is already set from localStorage above, so they stay logged in
            } else {
              console.log('[Auth] Token refreshed successfully')
            }
          } else {
            // For other errors (like 503, 502, network errors), don't clear auth
            // Just log the error and keep the user logged in
            console.warn('[Auth] Auth check failed with status:', response.status, 'but keeping session. User remains logged in.')
            // User is already set from localStorage above, so they stay logged in
          }
        } catch (err) {
          // Network errors (e.g., backend not ready) should not clear auth
          // Only clear if it's a real auth error
          if (err.name === 'AbortError') {
            // Timeout - backend might be starting up, keep auth state
            console.warn('[Auth] Auth check timeout, backend might be starting up. Keeping session. User remains logged in.')
            // User is already set from localStorage above, so they stay logged in
          } else if (err.name === 'TypeError' && (err.message.includes('fetch') || err.message.includes('Failed to fetch'))) {
            // Network error - backend might be starting up, keep auth state
            console.warn('[Auth] Network error during auth check, keeping session:', err.message, 'User remains logged in.')
            // User is already set from localStorage above, so they stay logged in
          } else {
            // Other errors - might be auth related
            console.error('[Auth] Auth init error:', err)
            // Don't automatically logout on unknown errors
            // User is already set from localStorage above, so they stay logged in
          }
        }
      } else {
        console.log('[Auth] No saved auth data found')
      }
      setLoading(false)
    }
    
    initAuth()
  }, [refreshToken, logout])

  const login = useCallback(async (email, password) => {
    setError(null)
    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ email, password })
      })
      
      const data = await response.json()
      
      if (!response.ok) {
        throw new Error(data.error || 'Ошибка входа')
      }
      
      localStorage.setItem(TOKEN_KEY, data.access_token)
      localStorage.setItem(REFRESH_TOKEN_KEY, data.refresh_token)
      localStorage.setItem(USER_KEY, JSON.stringify(data.user))
      setUser(data.user)
      
      return true
    } catch (err) {
      setError(err.message)
      return false
    }
  }, [])

  const register = useCallback(async (email, password, name) => {
    setError(null)
    try {
      const response = await fetch('/api/auth/register', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ email, password, name: name || undefined })
      })
      
      const data = await response.json()
      
      if (!response.ok) {
        throw new Error(data.error || 'Ошибка регистрации')
      }
      
      localStorage.setItem(TOKEN_KEY, data.access_token)
      localStorage.setItem(REFRESH_TOKEN_KEY, data.refresh_token)
      localStorage.setItem(USER_KEY, JSON.stringify(data.user))
      setUser(data.user)
      
      return true
    } catch (err) {
      setError(err.message)
      return false
    }
  }, [])

  const getToken = useCallback(() => {
    return localStorage.getItem(TOKEN_KEY)
  }, [])

  // Fetch wrapper that handles auth
  const authFetch = useCallback(async (url, options = {}) => {
    const token = localStorage.getItem(TOKEN_KEY)
    
    // Не устанавливаем Content-Type для FormData - браузер сделает это автоматически
    const isFormData = options.body instanceof FormData
    const headers = {}
    
    if (!isFormData && !options.headers?.['Content-Type']) {
      headers['Content-Type'] = 'application/json'
    }
    
    // Добавляем пользовательские заголовки
    if (options.headers) {
      Object.assign(headers, options.headers)
    }
    
    if (token) {
      headers['Authorization'] = `Bearer ${token}`
    }
    
    try {
      let response = await fetch(url, { ...options, headers })
      
      // If 401, try to refresh token and retry
      if (response.status === 401) {
        console.log('[Auth] Got 401 for', url, '- attempting token refresh')
        const result = await refreshToken()
        if (result.success) {
          console.log('[Auth] Token refreshed, retrying request to', url)
          const newToken = localStorage.getItem(TOKEN_KEY)
          headers['Authorization'] = `Bearer ${newToken}`
          response = await fetch(url, { ...options, headers })
          console.log('[Auth] Retry response status:', response.status)
        } else if (!result.isNetworkError) {
          // Only logout if refresh failed due to auth error (not network error)
          console.warn('[Auth] Refresh failed with auth error, logging out')
          logout()
        } else {
          console.warn('[Auth] Refresh failed with network error, keeping session but request failed')
        }
        // If network error, don't logout - let the caller handle the 401
      }
      
      return response
    } catch (err) {
      // Network errors should not trigger logout
      // Let the caller handle the error
      console.error('[Auth] Fetch error for', url, ':', err.message)
      throw err
    }
  }, [refreshToken, logout])

  const value = {
    user,
    loading,
    error,
    login,
    register,
    logout,
    getToken,
    authFetch,
    isAuthenticated: !!user
  }

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  )
}

export function useAuth() {
  const context = useContext(AuthContext)
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider')
  }
  return context
}

export default AuthContext

