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 isBoolean from 'lodash/isBoolean'

import { PAGES } from 'config'

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

import { useLocalStore, useToggle } from 'hooks'

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

import { getUserInfo } from 'requests/getUserInfo'
import { checkDevice, FailedResponse } from 'requests/checkDevice'

import { getClientSettings, needCheckNewDeviсe } from '../helpers'

export const useAuth = () => {
  const { changeLang, lang } = useLexicsStore()
  const history = useHistory()
  const {
    close: markUserLoaded,
    isOpen: loadingUser,
  } = useToggle(true)
  const [user, setUser] = useState<User>()
  const [isNewDeviceLogin, setIsNewDeviceLogin] = useState(false)
  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 [page, setPage] = useLocalStore({
    clearOnUnmount: true,
    defaultValue: '',
    key: 'matchBackLocation',
    validator: isString,
  })

  const [search, setSearch] = useLocalStore({
    clearOnUnmount: true,
    defaultValue: '',
    key: 'searchBack',
    validator: isString,
  })

  const signinRedirectCallback = useCallback(() => {
    userManager.signinRedirectCallback()
      .then((loadedUser) => {
        storeUser(loadedUser)
        queryParamStorage.clear()
        history.replace(PAGES.home)
        markUserLoaded()
        if (page) {
          const route = `${page}${page === '/' ? search : ''}`
          history.push(route)
          setPage('')
          setSearch('')
        }
      }).catch(login)
  }, [
    userManager,
    login,
    storeUser,
    history,
    markUserLoaded,
    page,
    search,
    setPage,
    setSearch,
  ])

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

        if (history.location.pathname === '/') {
          setSearch(history.location.search)
        }
        setPage(history.location.pathname)
      })
    }
  }, [
    checkUser,
    signinRedirectCallback,
    login,
    setPage,
    setSearch,
    history,
  ])

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

    checkDevice(loadedUser.access_token).catch((er:FailedResponse) => {
      if (er.error) return
      if (isBoolean(er.ok) && !er.ok) {
        setIsNewDeviceLogin(true)
        setTimeout(logout, 10000)
      }
    })
  }, [logout, userManager])

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

    checkDevice(loadedUser.access_token).catch(() => {
      setTimeout(reChekNewDevice, 2000)
    })
  }, [reChekNewDevice, userManager])

  useEffect(() => {
    if (!needCheckNewDeviсe) return undefined
    const startCheckDevice = setInterval(checkNewDevice, 20000)
    isNewDeviceLogin && clearInterval(startCheckDevice)
    return () => clearInterval(startCheckDevice)
  }, [checkNewDevice,
    isNewDeviceLogin,
    setIsNewDeviceLogin,
  ])

  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 fetchUserInfo = useCallback(async () => {
    const userInfoFetched = await getUserInfo()
    userInfoFetched.language.iso && changeLang(userInfoFetched.language.iso)
  }, [changeLang])

  useEffect(() => {
    if (user) {
      fetchUserInfo()
    }
  }, [fetchUserInfo, user])

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

  return auth
}
