// src/contexts/AuthContext.js
import {
  createContext,
  useState,
  useCallback,
  useEffect,
  useContext,
  useRef
} from 'react'
import axios from 'axios'
import {
  message,
  Modal,
  notification,
  Progress,
  Button,
  Space,
  Typography
} from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import PropTypes from 'prop-types'
import ExclamationOctogonIcon from '../../Icons/ExclamationOctagonIcon'
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import config from '../../../config'
import loglevel from 'loglevel'
import { ElectronContext } from './ElectronContext'
import { useLocation, useNavigate } from 'react-router-dom'
import {
  getAuthCookies,
  setAuthCookies,
  clearAuthCookies,
  areCookiesEnabled,
  validateAuthCookies,
  setupCookieSync,
  checkAuthCookiesExpiry
} from '../../../utils/cookies'

const logger = loglevel.getLogger('ApiServerContext')
logger.setLevel(config.logLevel)

const AuthContext = createContext()

const { Text } = Typography

const extractUserFromAuthData = (authData) => {
  if (!authData || typeof authData !== 'object') return null
  if (authData.user && typeof authData.user === 'object') return authData.user

  // Some endpoints may return the "user" fields at the top-level. Only treat it
  // as a user object if it looks like one (avoid confusing token-only payloads).
  const looksLikeUser =
    authData._id || authData.username || authData.email || authData.name
  if (looksLikeUser) return authData

  return null
}

const AuthProvider = ({ children }) => {
  const [messageApi, contextHolder] = message.useMessage()
  const [notificationApi, notificationContextHolder] =
    notification.useNotification()
  const [authenticated, setAuthenticated] = useState(false)
  const [initialized, setInitialized] = useState(false)
  const [retreivedTokenFromCookies, setRetreivedTokenFromCookies] =
    useState(false)
  const [loading, setLoading] = useState(false)
  const [token, setToken] = useState(null)
  const [expiresAt, setExpiresAt] = useState(null)
  const [userProfile, setUserProfile] = useState(null)
  const [profileImageUrl, setProfileImageUrl] = useState(null)
  const profileImageUrlRef = useRef(null)
  const processedAuthCodeRef = useRef(null)
  const [showSessionExpiredModal, setShowSessionExpiredModal] = useState(false)
  const [showUnauthorizedModal, setShowUnauthorizedModal] = useState(false)
  const [showAuthErrorModal, setShowAuthErrorModal] = useState(false)
  const [authError, setAuthError] = useState(null)
  const {
    openExternalUrl,
    isElectron,
    getAuthSession,
    setAuthSession,
    clearAuthSession
  } = useContext(ElectronContext)
  const location = useLocation()
  const navigate = useNavigate()

  var redirectType = 'web'

  if (isElectron == true && import.meta.env.MODE == 'development') {
    redirectType = 'app-localhost'
  }

  if (isElectron == true && import.meta.env.MODE != 'development') {
    redirectType = 'app-scheme'
  }

  const isSessionExpired = (session) => {
    if (!session?.expiresAt) return true
    const now = new Date()
    const expirationDate = new Date(session.expiresAt)
    return expirationDate <= now
  }

  const getUserInfo = useCallback(
    async (token, getIsCancelled = () => false) => {
      try {
        const response = await axios.get(`${config.backendUrl}/auth/user`, {
          headers: { Authorization: `Bearer ${token}` }
        })
        if (!getIsCancelled() && response.status === 200 && response.data) {
          const nextUser = extractUserFromAuthData(response.data)
          if (nextUser) setUserProfile(nextUser)
        }
      } catch (err) {
        if (!getIsCancelled()) {
          logger.debug('Failed to refresh user from API:', err)
          if (err.response?.status === 401) {
            setAuthenticated(false)
            setToken(null)
            setUserProfile(null)
            setExpiresAt(null)
            setShowUnauthorizedModal(true)
          }
        }
      }
    },
    []
  )

  const persistSession = useCallback(
    async ({ token: nextToken, expiresAt: nextExpiresAt, user: nextUser }) => {
      if (isElectron) {
        return await setAuthSession({
          token: nextToken,
          expiresAt: nextExpiresAt,
          user: nextUser
        })
      }
      return setAuthCookies({
        access_token: nextToken,
        expires_at: nextExpiresAt,
        user: nextUser
      })
    },
    [isElectron, setAuthSession]
  )

  const clearPersistedSession = useCallback(async () => {
    if (isElectron) {
      return await clearAuthSession()
    }
    clearAuthCookies()
    return true
  }, [isElectron, clearAuthSession])

  // Check if cookies are enabled and show warning if not (web only)
  useEffect(() => {
    if (isElectron) return
    if (!areCookiesEnabled()) {
      messageApi.warning(
        'Cookies are disabled. Login state may not persist between tabs.'
      )
    }
  }, [messageApi, isElectron])

  // Read token from cookies (web) or electron session storage if present
  useEffect(() => {
    let cancelled = false
    const authCode =
      new URLSearchParams(location.search).get('authCode') || null

    const load = async () => {
      try {
        if (isElectron) {
          const session = await getAuthSession()
          if (
            !cancelled &&
            session &&
            session.token &&
            !isSessionExpired(session)
          ) {
            setToken(session.token)
            setUserProfile(session.user)
            setExpiresAt(session.expiresAt)
            setAuthenticated(true)

            if (session.user) {
              getUserInfo(session.token, () => cancelled)
            }
          } else if (!cancelled) {
            setAuthenticated(false)
            setUserProfile(null)
            setShowUnauthorizedModal(true)
          }
        } else {
          // First validate existing cookies to clean up expired ones
          if (validateAuthCookies()) {
            const {
              token: storedToken,
              expiresAt: storedExpiresAt,
              user: storedUser
            } = getAuthCookies()

            if (!cancelled) {
              setToken(storedToken)
              setUserProfile(storedUser)
              setExpiresAt(storedExpiresAt)
              setAuthenticated(true)

              if (storedToken && storedUser) {
                getUserInfo(storedToken, () => cancelled)
              }
            }
          } else if (!cancelled) {
            setAuthenticated(false)
            setUserProfile(null)
            setShowUnauthorizedModal(true)
          }
        }
      } catch (error) {
        console.error('Error loading persisted auth session:', error)
        await clearPersistedSession()
        if (!cancelled) {
          setAuthenticated(false)
          setUserProfile(null)
          setShowUnauthorizedModal(true)
        }
      } finally {
        if (!cancelled) setRetreivedTokenFromCookies(true)
      }
    }

    // An auth code represents a newly completed login and must take precedence
    // over any existing persisted session.
    if (location.pathname === '/applaunch' || authCode != null) {
      return
    }

    load()
    return () => {
      cancelled = true
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps -- run only on mount to load persisted session; deps are stable in behavior
  }, [])

  // Set up cookie synchronization between tabs
  useEffect(() => {
    if (isElectron) return
    const cleanupCookieSync = setupCookieSync(() => {
      // When cookies change in another tab, re-validate and update state
      try {
        if (validateAuthCookies()) {
          const {
            token: newToken,
            expiresAt: newExpiresAt,
            user: newUser
          } = getAuthCookies()
          if (
            newToken !== token ||
            newExpiresAt !== expiresAt ||
            JSON.stringify(newUser) !== JSON.stringify(userProfile)
          ) {
            setToken(newToken)
            setExpiresAt(newExpiresAt)
            setUserProfile(newUser)
            setAuthenticated(true)
            logger.debug('Auth state synchronized from another tab')
          }
        } else {
          // Cookies are invalid, clear state
          setToken(null)
          setExpiresAt(null)
          setUserProfile(null)
          setAuthenticated(false)
          setShowUnauthorizedModal(true)
          logger.debug(
            'Auth state cleared due to invalid cookies from another tab'
          )
        }
      } catch (error) {
        console.error('Error syncing auth state:', error)
      }
    })

    return cleanupCookieSync
  }, [token, expiresAt, userProfile, isElectron])

  // Persist userProfile changes to cookies/electron storage so updates (e.g. from
  // WebSocket or profile edits) are saved for session restoration
  useEffect(() => {
    if (!authenticated || !token || !expiresAt) return
    persistSession({
      token,
      expiresAt,
      user: userProfile
    })
  }, [authenticated, token, expiresAt, userProfile, persistSession])

  // Fetch and cache profile image when userProfile.profileImage changes
  useEffect(() => {
    const profileImage = userProfile?.profileImage
    const profileImageId =
      profileImage?._id ??
      (typeof profileImage === 'string' ? profileImage : null)

    if (!token) {
      if (profileImageUrlRef.current) {
        URL.revokeObjectURL(profileImageUrlRef.current)
        profileImageUrlRef.current = null
      }
      setProfileImageUrl(null)
      return
    }
    if (!profileImageId) {
      if (profileImageUrlRef.current) {
        URL.revokeObjectURL(profileImageUrlRef.current)
        profileImageUrlRef.current = null
      }
      setProfileImageUrl(null)
      return
    }

    let cancelled = false
    const file =
      typeof profileImage === 'object' && profileImage !== null
        ? profileImage
        : { _id: profileImageId, name: '', extension: '' }

    const fetchProfileImage = async () => {
      try {
        const response = await axios.get(
          `${config.backendUrl}/files/${file._id}/content`,
          {
            headers: {
              Accept: '*/*',
              Authorization: `Bearer ${token}`
            },
            responseType: 'blob'
          }
        )
        const blob = new Blob([response.data], {
          type: response.headers['content-type']
        })
        const fileURL = window.URL.createObjectURL(blob)
        if (!cancelled) {
          if (profileImageUrlRef.current) {
            URL.revokeObjectURL(profileImageUrlRef.current)
          }
          profileImageUrlRef.current = fileURL
          setProfileImageUrl(fileURL)
        } else {
          URL.revokeObjectURL(fileURL)
        }
      } catch (err) {
        logger.debug('Failed to fetch profile image:', err)
        if (!cancelled) {
          setProfileImageUrl(null)
        }
      }
    }

    fetchProfileImage()
    return () => {
      cancelled = true
      if (profileImageUrlRef.current) {
        URL.revokeObjectURL(profileImageUrlRef.current)
        profileImageUrlRef.current = null
      }
      setProfileImageUrl(null)
    }
  }, [userProfile?.profileImage, token])

  const logout = useCallback(
    (redirectUri = '/login') => {
      setAuthenticated(false)
      setToken(null)
      setExpiresAt(null)
      setUserProfile(null)
      clearPersistedSession()
      window.location.href = `${config.backendUrl}/auth/logout?redirect_uri=${encodeURIComponent(redirectUri)}`
    },
    [clearPersistedSession]
  )

  // Login using query parameters
  const loginWithSSO = useCallback(
    (redirectUri = location.pathname + location.search) => {
      messageApi.info('Logging in with tombutcher.work')
      const loginUrl = `${config.backendUrl}/auth/${redirectType}/login?redirect_uri=${encodeURIComponent(redirectUri)}`
      if (isElectron) {
        logger.debug('Opening external url...')
        openExternalUrl(loginUrl)
        setLoading(true)
      } else {
        logger.debug('Redirecting...')
        window.location.href = loginUrl
      }
    },
    [
      redirectType,
      messageApi,
      openExternalUrl,
      isElectron,
      location.search,
      location.pathname
    ]
  )

  const getLoginToken = useCallback(
    async (code) => {
      setLoading(true)
      setShowUnauthorizedModal(false)
      setShowSessionExpiredModal(false)
      setAuthError(null)
      try {
        // Make a call to your backend to check auth status
        const response = await axios.get(
          `${config.backendUrl}/auth/${redirectType}/token?code=${code}`
        )

        if (response.status === 200 && response.data) {
          logger.debug('Got auth token!')
          const authData = response.data

          const nextToken = authData.access_token
          const nextExpiresAt = authData.expires_at
          const nextUser = extractUserFromAuthData(authData)

          setToken(nextToken)
          setExpiresAt(nextExpiresAt)
          setUserProfile(nextUser)
          setAuthenticated(true)
          setShowUnauthorizedModal(false)

          // Persist session (cookies on web, electron storage on desktop)
          const persisted = await persistSession({
            token: nextToken,
            expiresAt: nextExpiresAt,
            user: nextUser
          })
          if (!persisted) {
            messageApi.warning(
              'Authentication successful but failed to save login state. You may need to log in again when you restart the app.'
            )
          }

          const searchParams = new URLSearchParams(location.search)
          searchParams.delete('authCode')
          const newSearch = searchParams.toString()
          const newPath = location.pathname + (newSearch ? `?${newSearch}` : '')
          navigate(newPath, { replace: true })
        } else {
          setAuthenticated(false)
          setAuthError('Failed to authenticate user.')
          setShowAuthErrorModal(true)
        }
      } catch (error) {
        logger.debug('Auth check failed', error)
        if (error.response?.status === 401) {
          setShowUnauthorizedModal(true)
        } else {
          const errorMessage =
            error?.response?.data?.error ||
            'Error connecting to authentication service.'
          const fullStop = errorMessage.endsWith('.')
          setAuthError(`${errorMessage}${!fullStop && '.'}`)
          setShowAuthErrorModal(true)
        }
        setAuthenticated(false)
      } finally {
        setLoading(false)
        setRetreivedTokenFromCookies(true)
      }
    },
    [
      navigate,
      location.search,
      location.pathname,
      messageApi,
      persistSession,
      redirectType
    ]
  )

  // Function to check if the user is logged in
  const checkAuthStatus = useCallback(async () => {
    setLoading(true)
    setAuthError(null)
    try {
      // Make a call to your backend to check auth status
      const response = await axios.get(`${config.backendUrl}/auth/user`, {
        headers: {
          Authorization: `Bearer ${token}`
        }
      })

      if (response.status === 200 && response.data) {
        logger.debug('Got auth token!')
        const authData = response.data

        const nextToken = authData.access_token
        const nextExpiresAt = authData.expires_at
        const nextUser = extractUserFromAuthData(authData)

        setToken(nextToken)
        setExpiresAt(nextExpiresAt)
        setUserProfile(nextUser)

        const persisted = await persistSession({
          token: nextToken,
          expiresAt: nextExpiresAt,
          user: nextUser
        })
        if (!persisted) {
          messageApi.warning(
            'Failed to update login state. You may need to log in again if you close this tab.'
          )
        }
      } else {
        setAuthenticated(false)
        setAuthError('Failed to authenticate user.')
        setShowAuthErrorModal(true)
      }
    } catch (error) {
      logger.debug('Auth check failed', error)
      if (error.response?.status === 401) {
        setShowUnauthorizedModal(true)
      } else {
        setAuthError('Error connecting to authentication service.')
        setShowAuthErrorModal(true)
      }
      setAuthenticated(false)
    } finally {
      setLoading(false)
    }
  }, [token, messageApi, persistSession])

  const setUnauthenticated = () => {
    setToken(null)
    setExpiresAt(null)
    setUserProfile(null)
    clearPersistedSession()
    setAuthenticated(false)
    if (showSessionExpiredModal == false) {
      setShowUnauthorizedModal(true)
    }
  }

  const refreshToken = useCallback(async () => {
    try {
      const response = await axios.get(`${config.backendUrl}/auth/refresh`, {
        headers: {
          Accept: 'application/json',
          Authorization: `Bearer ${token}`
        }
      })
      if (response.status === 200 && response.data) {
        const authData = response.data

        const nextToken = authData.access_token
        const nextExpiresAt = authData.expires_at
        const nextUser = extractUserFromAuthData(authData) || userProfile

        setToken(nextToken)
        setExpiresAt(nextExpiresAt)

        const persisted = await persistSession({
          token: nextToken,
          expiresAt: nextExpiresAt,
          user: nextUser
        })
        if (!persisted) {
          messageApi.warning(
            'Failed to update login state. You may need to log in again if you close this tab.'
          )
        }
      }
    } catch (error) {
      console.error('Token refresh failed', error)
    }
  }, [token, messageApi, persistSession, userProfile])

  const handleSessionExpiredModalOk = () => {
    setShowSessionExpiredModal(false)
    loginWithSSO()
  }

  // Initialize on component mount
  useEffect(() => {
    let intervalId

    const tokenRefresh = () => {
      if (expiresAt) {
        const now = new Date()
        const expirationDate = new Date(expiresAt)
        const timeRemaining = expirationDate - now

        if (timeRemaining <= 0) {
          if (authenticated == true) {
            setAuthenticated(false)
          }
          setShowSessionExpiredModal(true)
          notificationApi.destroy('token-expiration')
        } else {
          if (authenticated == false) {
            setAuthenticated(true)
          }

          const minutes = Math.floor(timeRemaining / 60000)
          const seconds = Math.floor((timeRemaining % 60000) / 1000)

          // Only show notification in the final minute
          if (minutes === 0) {
            const totalSeconds = 60
            const remainingSeconds = totalSeconds - seconds
            const progress = (remainingSeconds / totalSeconds) * 100

            notificationApi.info({
              message: 'Session Expiring Soon',
              description: (
                <div>
                  <div style={{ marginBottom: 8 }}>
                    Your session will expire in {seconds} seconds
                  </div>
                  <Progress
                    percent={progress}
                    size='small'
                    status='active'
                    showInfo={false}
                  />
                </div>
              ),
              duration: 0,
              key: 'token-expiration',
              icon: null,
              placement: 'bottomRight',
              style: {
                width: 360
              },
              className: 'token-expiration-notification',
              closeIcon: null,
              onClose: () => {},
              btn: (
                <Button
                  type='primary'
                  size='small'
                  onClick={() => {
                    notificationApi.destroy('token-expiration')
                    refreshToken()
                  }}
                >
                  Reload Session
                </Button>
              )
            })
          } else if (minutes === 1) {
            // Clear any existing notification when we enter the final minute
            notificationApi.destroy('token-expiration')
          }
        }
      } else {
        // Check cookies directly if expiresAt is not set in state (web only)
        if (isElectron) return
        const expiryInfo = checkAuthCookiesExpiry(5) // Check if expiring within 5 minutes
        if (expiryInfo.isExpiringSoon && expiryInfo.minutesRemaining <= 1) {
          // Show notification for cookies expiring soon
          const seconds = Math.floor((expiryInfo.timeRemaining % 60000) / 1000)
          const totalSeconds = 60
          const remainingSeconds = totalSeconds - seconds
          const progress = (remainingSeconds / totalSeconds) * 100

          notificationApi.info({
            message: 'Session Expiring Soon',
            description: (
              <div>
                <div style={{ marginBottom: 8 }}>
                  Your session will expire in {seconds} seconds
                </div>
                <Progress
                  percent={progress}
                  size='small'
                  status='active'
                  showInfo={false}
                />
              </div>
            ),
            duration: 0,
            key: 'token-expiration',
            icon: null,
            placement: 'bottomRight',
            style: {
              width: 360
            },
            className: 'token-expiration-notification',
            closeIcon: null,
            onClose: () => {},
            btn: (
              <Button
                type='primary'
                size='small'
                onClick={() => {
                  notificationApi.destroy('token-expiration')
                  refreshToken()
                }}
              >
                Reload Session
              </Button>
            )
          })
        }
      }
    }

    intervalId = setInterval(tokenRefresh, 1000)
    tokenRefresh()

    return () => {
      if (intervalId) {
        clearInterval(intervalId)
      }
    }
  }, [expiresAt, authenticated, notificationApi, refreshToken, isElectron])

  useEffect(() => {
    const authCode =
      new URLSearchParams(location.search).get('authCode') || null
    if (
      authCode != null &&
      processedAuthCodeRef.current !== authCode
    ) {
      processedAuthCodeRef.current = authCode
      getLoginToken(authCode)
    } else if (
      token == null &&
      retreivedTokenFromCookies == true &&
      initialized == false &&
      authCode == null
    ) {
      setInitialized(true)
      setShowUnauthorizedModal(true)
      setAuthenticated(false)
    }
  }, [
    checkAuthStatus,
    location.search,
    getLoginToken,
    initialized,
    location.pathname,
    navigate,
    token,
    retreivedTokenFromCookies
  ])

  return (
    <>
      {contextHolder}
      {notificationContextHolder}
      <AuthContext.Provider
        value={{
          authenticated,
          authInitialized: retreivedTokenFromCookies,
          setUnauthenticated,
          loginWithSSO,
          getLoginToken,
          token,
          loading,
          userProfile,
          setUserProfile,
          profileImageUrl,
          logout
        }}
      >
        {children}
      </AuthContext.Provider>
      <Modal
        title={
          <Space size={'middle'}>
            <InfoCircleIcon />
            Session Expired
          </Space>
        }
        open={showSessionExpiredModal && !loading && !showAuthErrorModal}
        onOk={handleSessionExpiredModalOk}
        okText='Log In'
        style={{ maxWidth: 430 }}
        closable={false}
        centered
        maskClosable={false}
        footer={[
          <Button
            key='submit'
            type='primary'
            onClick={handleSessionExpiredModalOk}
          >
            Log In
          </Button>
        ]}
      >
        <Text>Your session has expired. Please log in again to continue.</Text>
      </Modal>
      <Modal
        title={
          <Space size={'middle'}>
            <ExclamationOctogonIcon />
            Please log in to continue
          </Space>
        }
        open={
          showUnauthorizedModal &&
          !authenticated &&
          !loading &&
          !showAuthErrorModal
        }
        onOk={() => {
          setShowUnauthorizedModal(false)
          loginWithSSO()
        }}
        okText='Log In'
        style={{ maxWidth: 430, top: '50%', transform: 'translateY(-50%)' }}
        closable={false}
        maskClosable={false}
        footer={[
          <Button
            key='submit'
            type='primary'
            onClick={() => {
              setShowUnauthorizedModal(false)
              loginWithSSO()
            }}
          >
            Log In
          </Button>
        ]}
      >
        <Text>
          You need to be logged in to access FarmControl. Please log in with
          tombutcher.work to continue.
        </Text>
      </Modal>
      <Modal
        open={loading}
        className={'loading-modal'}
        title={false}
        height={20}
        style={{ maxWidth: 220, top: '50%', transform: 'translateY(-50%)' }}
        closable={false}
        maskClosable={false}
        footer={false}
      >
        <Space size={'middle'}>
          <LoadingOutlined />
          <Text style={{ margin: 0 }}>Loading, please wait...</Text>
        </Space>
      </Modal>
      <Modal
        title={
          <Space size={'middle'}>
            <ExclamationOctogonIcon />
            Authentication Error
          </Space>
        }
        open={showAuthErrorModal && !loading}
        style={{ maxWidth: 430 }}
        closable={false}
        centered
        maskClosable={false}
        footer={[
          <Button
            key='submit'
            onClick={() => {
              setShowAuthErrorModal(false)
              loginWithSSO()
            }}
          >
            Retry Login
          </Button>
        ]}
      >
        <Text>{authError}</Text>
      </Modal>
    </>
  )
}

AuthProvider.propTypes = {
  children: PropTypes.node.isRequired
}

export { AuthContext, AuthProvider }
