import type { UserRoles, ViewerResult } from '@authentication/authentication.types'
import { useQuery } from '@tanstack/react-query'
import type { User } from '@types'

import Keycloak from 'keycloak-js'
import * as React from 'react'
import { createContext, useCallback, useEffect, useMemo, useState } from 'react'

import { APP_CONFIG } from '@lib/config'
import { createUseContext } from '@lib/react'
import { useTRPC } from '@lib/trpc'
import { Result } from '@postxl/utils'

type AppUser = {
  userDetails: User | null
  userRoles: UserRoles
}

export type AuthContext = {
  isAuthenticated: boolean
  isInitComplete: boolean
  login: () => Promise<void>
  logout: () => Promise<void>
  user: AppUser | null
}

const AuthContext = createContext<AuthContext | null>(null)

let _keycloakInitializationStarted = false
let _authToken: string | undefined

export function setAuthToken(token?: string) {
  _authToken = token
}

export function getAuthToken() {
  return _authToken
}

export function getAuthHeaders(options?: RequestInit) {
  const token = getAuthToken()
  const headers = new Headers(options?.headers)

  if (token) {
    headers.set('Authorization', `Bearer${token}`)
  }
  return headers
}

const keycloak = new Keycloak({
  url: APP_CONFIG.keycloakURL,
  realm: APP_CONFIG.keycloakRealm,
  clientId: APP_CONFIG.keycloakClientId,
})

keycloak.onAuthLogout = () => {
  console.log('KEYCLOAK AUTH LOGOUT')
  setAuthToken()
}

keycloak.onTokenExpired = () => {
  console.log('KEYCLOAK TOKEN EXPIRED')
}
keycloak.onAuthSuccess = () => {
  console.log('KEYCLOAK AUTH SUCCESS')
}
keycloak.onAuthRefreshSuccess = () => {
  console.log('KEYCLOAK AUTH REFRESH SUCCESS')
}
keycloak.onAuthError = () => {
  console.log('KEYCLOAK AUTH ERROR')
}

export function AuthProvider({ children }: Readonly<{ children: React.ReactNode }>) {
  const [user, setUser] = React.useState<AppUser | null>(null)
  const [isAuthenticated, setIsAuthenticated] = useState(false)
  const [isInitComplete, setIsInitComplete] = useState(false)

  const trpc = useTRPC()

  const {
    data: userData,
    isSuccess: isUserSuccessfullyLoaded,
    isError: isUserDataError,
  } = useQuery(
    trpc.viewer.viewer.queryOptions(
      { pageUrl: location.href },
      // load user data when keycloak init complete and user is authenticated
      { enabled: isInitComplete && isAuthenticated },
    ),
  )

  const logout = useCallback(async () => {
    setAuthToken()
    await keycloak.logout()
  }, [])

  const login = useCallback(async () => {
    await keycloak.login()
  }, [])

  /**
   * #1 - initialize the keycloak adapter on a page-load (only once)
   */
  useEffect(() => {
    const initKeycloak = async () => {
      try {
        if (!_keycloakInitializationStarted) {
          _keycloakInitializationStarted = true

          const authenticated = await keycloak.init({
            onLoad: 'check-sso',
            silentCheckSsoRedirectUri: `${location.origin}/silent-check-sso.html`,
            enableLogging: true,
            // SSO-check via iFrame doesn't work for Brave browser, if
            // the domain of the auth keycloak url differs from the app domain
            // see: https://www.keycloak.org/securing-apps/javascript-adapter#_session_status_iframe
            // This is no problem in the USP setup as Keycloak runs on the same domain,
            // but should be addressed in PXL
            checkLoginIframe: APP_CONFIG.checkLoginIframe,
          })

          if (authenticated) {
            setAuthToken(keycloak.token)
            setIsAuthenticated(true)
          }
          // if keycloak initialized and the user is not authenticated,
          // the user will be redirected to the keycloak login page (see "/pages/Login")
          console.log('Keycloak initialized COMPLETE, user authenticated:', authenticated)
          setIsInitComplete(true)
        }
      } catch (error) {
        console.error('Failed to initialize adapter:', error)
        void logout() // NOSONAR
      }
    }

    if (APP_CONFIG.AUTH) {
      void initKeycloak()
    } else {
      setIsAuthenticated(true)
      setIsInitComplete(true)
    }
  }, [logout])

  /**
   * #2 - user data loading
   * when user authenticated by keycloak, the user data is loaded from the backend
   */
  useEffect(() => {
    if (isInitComplete && isUserDataError) {
      void logout() // NOSONAR
      return
    }

    if (userData && isUserSuccessfullyLoaded) {
      const userDataResult: ViewerResult = Result.fromObject(userData as unknown as any)
      if (userDataResult.isOk()) {
        const value = userDataResult.unwrap()
        console.log('User data loaded successfully:', value)
        setUser({
          userRoles: value.userRoles,
          userDetails: value.user,
        })
      } else if (userDataResult.isErr()) {
        console.log('User data loaded with an error => logout()', { error: userDataResult.unwrapErr() })
        // when we get an error result on loading the user, we log out the user
        void logout() // NOSONAR
      }
    }
  }, [userData, isUserSuccessfullyLoaded, isUserDataError, isInitComplete, logout])

  /**
   * #3 - token refreshing
   * when the user is authenticated, the token is refreshed every 5 seconds
   */
  useEffect(() => {
    if (!isInitComplete || !isAuthenticated || !APP_CONFIG.AUTH) {
      return
    }

    const refreshIntervalId = setInterval(async () => {
      try {
        // refresh the token if it expires within 60 seconds
        const refreshed = await keycloak.updateToken(60)

        if (refreshed) {
          console.log('Token was successfully refreshed')
          // Update your application state if needed
          setAuthToken(keycloak.token)
        }
      } catch (error) {
        console.error('Failed to refresh token', error)
        // if the token could not be refreshed, log out the user
        void logout() // NOSONAR
      }
    }, 5000)

    return () => clearInterval(refreshIntervalId)
  }, [isAuthenticated, isInitComplete, logout])

  const memoizedValues = useMemo(
    () => ({ isAuthenticated, login, logout, user, isInitComplete }),
    [isAuthenticated, login, logout, user, isInitComplete],
  )

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

export const useAuth = createUseContext(AuthContext, 'AuthContext')
