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,
  readToken,
} from 'helpers/token'
import {
  setCookie,
  removeCookie,
} from 'helpers/cookie'

import { useLocalStore, useToggle } from 'hooks'

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

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

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

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 [userInfo, setUserInfo] = useState<UserInfo>()
  const userManager = useMemo(() => new UserManager(getClientSettings()), [])

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

  const logout = useCallback((key?: string) => {
    setPage(history.location.pathname)
    userManager.clearStaleState()
    userManager.createSigninRequest().then(({ url }) => {
      const urlWithLang = addLanguageUrlParam(lang, url)
      userManager.signoutRedirect({ post_logout_redirect_uri: urlWithLang })
    })
    removeToken()
    if (key !== 'saveToken') {
      removeCookie('access_token')
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [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) {
      if (!readToken()) {
        const token = await getTemporaryToken()
        token && await fetchUserInfo()
        return Promise.resolve()
      }
      return Promise.reject()
    }

    storeUser(loadedUser)
    markUserLoaded()
    return loadedUser
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    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 getTemporaryToken = async () => {
    try {
      const { access_token } = await getTokenVirtualUser()

      writeToken(access_token)
      setCookie({
        exdays: 1,
        name: 'access_token',
        value: access_token,
      })
      return access_token
      // eslint-disable-next-line no-empty
    } catch {
      return ''
    }
  }

  const signinRedirectCallback = useCallback(() => {
    userManager.signinRedirectCallback()
      .then((loadedUser) => {
        storeUser(loadedUser)
        queryParamStorage.clear()
        if (page.includes(PAGES.useraccount)) {
          history.push(PAGES.home)
        } else {
          const route = `${page}${page === '/' ? search : ''}`
          history.push(route)
        }
        markUserLoaded()
        setPage('')
        setSearch('')
        // }
      }).catch(login)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    userManager,
    login,
    storeUser,
    markUserLoaded,
  ])

  useEffect(() => {
    const isRedirectedBackFromAuthProvider = history.location.pathname === '/redirect'
    isRedirectedBackFromAuthProvider ? signinRedirectCallback() : checkUser()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    checkUser,
    signinRedirectCallback,
    login,
  ])

  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 && !user) return undefined
    const startCheckDevice = setInterval(checkNewDevice, 20000)
    isNewDeviceLogin && clearInterval(startCheckDevice)
    return () => clearInterval(startCheckDevice)

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    checkNewDevice,
    isNewDeviceLogin,
    setIsNewDeviceLogin,
  ])

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

    // и если токен истек
    userManager.events.addAccessTokenExpired(tryRenewToken)
    return () => {
      window.removeEventListener('FORBIDDEN_REQUEST', tryRenewToken)
      userManager.events.removeAccessTokenExpired(tryRenewToken)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [userManager, logout])

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

  const fetchUserInfo = useCallback(async () => {
    try {
      const userInfoFetched = await getUserInfo()

      setUserInfo(userInfoFetched)

      userInfoFetched.language.iso && changeLang(userInfoFetched.language.iso)

      // eslint-disable-next-line no-empty
    } catch (error) {}
  }, [changeLang])

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

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

  return auth
}
