import {
  useCallback,
  useState,
  useMemo,
  useEffect,
} from 'react'
import { useHistory } from 'react-router'

import type { User } from 'oidc-client'
import { UserManager } from 'oidc-client'

import isString from 'lodash/isString'

import { PAGES } from 'config'

import { addLanguageUrlParam } from 'helpers/languageUrlParam'
import { writeToken, removeToken } from 'helpers/token'
import { setCookie, removeCookie } from 'helpers/cookie'
import { isMatchPage } from 'helpers/isMatchPage'

import { useLocalStore, useToggle } from 'hooks'

import { useLexicsStore } from 'features/LexicsStore'
import { queryParamStorage } from 'features/QueryParamsStorage'

import { getClientSettings } from '../helpers'

export const useAuth = () => {
  const { lang } = useLexicsStore()
  const history = useHistory()
  const {
    close: markUserLoaded,
    isOpen: loadingUser,
  } = useToggle(true)
  const [user, setUser] = useState<User>()
  const userManager = useMemo(() => new UserManager(getClientSettings()), [])

  const login = useCallback(async () => (
    userManager.signinRedirect({ extraQueryParams: { lang } })
  ), [userManager, lang])

  const logout = useCallback(() => {
    userManager.clearStaleState()
    userManager.createSigninRequest().then(({ url }) => {
      const urlWithLang = addLanguageUrlParam(lang, url)
      userManager.signoutRedirect({ post_logout_redirect_uri: urlWithLang })
    })
    removeToken()
    removeCookie('access_token')
  }, [userManager, lang])

  const storeUser = useCallback((loadedUser: User) => {
    setUser(loadedUser)
    writeToken(loadedUser.access_token)
    setCookie({
      exdays: 1,
      name: 'access_token',
      value: loadedUser.access_token,
    })
  }, [])

  const checkUser = useCallback(async () => {
    const loadedUser = await userManager.getUser()
    if (!loadedUser) return Promise.reject()

    storeUser(loadedUser)
    markUserLoaded()
    return loadedUser
  }, [
    userManager,
    storeUser,
    markUserLoaded,
  ])

  const [matchPage, setMatchPage] = useLocalStore({
    clearOnUnmount: true,
    defaultValue: '',
    key: 'matchBackLocation',
    validator: isString,
  })

  const signinRedirectCallback = useCallback(() => {
    userManager.signinRedirectCallback()
      .then((loadedUser) => {
        storeUser(loadedUser)
        queryParamStorage.clear()
        history.replace(PAGES.home)
        markUserLoaded()
        if (matchPage) {
          history.push(matchPage)
          setMatchPage('')
        }
      }).catch(login)
  }, [
    userManager,
    history,
    setMatchPage,
    storeUser,
    markUserLoaded,
    matchPage,
    login,
  ])

  useEffect(() => {
    const isRedirectedBackFromAuthProvider = history.location.pathname === '/redirect'
    if (isRedirectedBackFromAuthProvider) {
      signinRedirectCallback()
    } else {
      checkUser().catch(() => {
        if (!isMatchPage()) {
          login()
          setMatchPage('')
        } else {
          setMatchPage(history.location.pathname)
        }
      })
    }
  }, [
    checkUser,
    signinRedirectCallback,
    login,
    setMatchPage,
    history,
  ])

  useEffect(() => {
    // попытаемся обновить токен используя refresh_token
    const tryRenewToken = () => {
      userManager.signinSilent().catch(logout)
    }
    // если запросы вернули 401 | 403
    window.addEventListener('FORBIDDEN_REQUEST', tryRenewToken)

    // и если токен истек
    userManager.events.addAccessTokenExpired(tryRenewToken)
    return () => {
      window.removeEventListener('FORBIDDEN_REQUEST', tryRenewToken)
      userManager.events.removeAccessTokenExpired(tryRenewToken)
    }
  }, [userManager, logout])

  useEffect(() => {
    // событие срабатывает после получения токена(первый логин и обновление токена)
    userManager.events.addUserLoaded(storeUser)
    return () => userManager.events.removeUserLoaded(storeUser)
  }, [userManager, storeUser])

  const auth = useMemo(() => ({
    loadingUser,
    login,
    logout,
    user,
  }), [
    logout,
    user,
    login,
    loadingUser,
  ])

  return auth
}
