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 { isMatchPage } from 'helpers/isMatchPage'

import {
  useLocalStore,
  useSessionStore,
  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 { getTokenVirtualUser } from 'requests/getTokenVirtualUser'

// eslint-disable-next-line
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 [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 [isFromLanding, setIsFromLanding] = useSessionStore({
    clearOnUnmount: true,
    defaultValue: false,
    key: 'isFromLanding',
    validator: isBoolean,
  })

  useEffect(() => {
    if (isMatchPage()) setPage(history.location.pathname)
    if (history.location.pathname !== page) setIsFromLanding(false)
  }, [
    history.location.pathname,
    page,
    setIsFromLanding,
    setPage,
  ])

  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}${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])

  // eslint-disable-next-line
  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(() => {
    // попытаемся обновить токен используя refresh_token
    const tryRenewToken = () => {
      userManager.signinSilent()
        .catch(() => user && logout())
    }
    // если запросы вернули 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,
    isFromLanding,
    isNewDeviceLogin,
    loadingUser,
    login,
    logout,
    page,
    setIsFromLanding,
    setPage,
    setSearch,
    setUserInfo,
    user,
    userInfo,
  }), [
    fetchUserInfo,
    isNewDeviceLogin,
    logout,
    user,
    userInfo,
    login,
    loadingUser,
    setSearch,
    setPage,
    setUserInfo,
    page,
    setIsFromLanding,
    isFromLanding,
  ])

  return auth
}
