import { Button, Flex, Group, Modal, Typography } from '@tidbcloud/uikit'
import axios from 'axios'
import { useEffect, useState } from 'react'
import { useHistory } from 'react-router-dom'

import { log } from 'common/log'
import { ContactMessage, ErrorPanel, ErrorSupportLink } from 'dbaas/components/Error'
import PageLoader from 'dbaas/components/Placeholder/index'
import { ACCEPT_INVITATION_ACTION, SSO_SIGNUP_PATTERN } from 'dbaas/screens/User/InviteeAccepting/constants'
import { acceptInvitation, CentralAcceptInvitationReq } from 'dbaas/services'
import { getUserLastVisitHistory } from 'dbaas/services/account'
import auth from 'dbaas/services/auth'
import { ErrorMessage, getErrorMessage } from 'dbaas/services/errorCodes'
import { setLastCachedOrg } from 'dbaas/stores/localStorage'
import { getLakeConnectIntentFromSearch, getLakeConnectTrackingProperties } from 'dbaas/utils/lake-connect'
import { checkPasswordExpiration } from 'dbaas/utils/passwordPolicy'
import { eventTracking, MixpanelEvents } from 'dbaas/utils/tracking'
import { isUnionLoginRequest, validateUnionLogin } from 'dbaas/utils/union-login'
import { forwardSearch } from 'dbaas/utils/useQuery'
import useSessionStorage from 'dbaas/utils/useSessionStorage'

import config from '../../../config'
import { getEnterpriseSwitchURL } from '../../features/Organization'

export enum ErrorType {
  Server = 'server_error',
  Denied = 'access_denied', // like using github oauth but cancelled
  InvalidToken = 'invalid_token',
  Unauthorized = 'unauthorized'
}

const parseError = (msg: string) => {
  try {
    return JSON.parse(msg) as { message: string; companyName: string }
  } catch {
    return {
      message: msg,
      companyName: ''
    }
  }
}

export default () => {
  const history = useHistory()

  const [error, setError] = useState<{
    type: 'signup' | 'invite' | 'auth'
    message: string
    companyName?: string
  } | null>(null)

  const urlParams = new URLSearchParams(window.location.search)

  const [socialSignUp, , removeSocialSignUp] = useSessionStorage('social_signup')
  const [inviteUser, , removeInviteUser] = useSessionStorage<
    CentralAcceptInvitationReq & { invite_link: string; connection: string; search: string }
  >('invite_user')

  const [passwordModal, setPasswordModal] = useState({
    open: false,
    content: '',
    resolver: (() => {}) as (val: boolean) => void
  })

  const handleConfirm = () => {
    history.push('/account/password')
    passwordModal.resolver(false)
  }

  const handleCancel = () => {
    passwordModal.resolver(true)
  }

  const handlePasswordPolicyCheck = async () => {
    const time = await auth.getLastPasswordResetTime()
    if (time) {
      const result = checkPasswordExpiration(time)
      if (result.shouldWarn) {
        return await new Promise<boolean>((resolve) => {
          setPasswordModal({
            open: true,
            content: result.content,
            resolver: resolve
          })
        })
      }
    }
    return true
  }

  const handleRegularLoginRedirect = () => {
    if (urlParams.get('prev') === '/') {
      urlParams.delete('prev')
    }

    const search = window.location.search?.replace('?prev=/?', '?')

    history.push(`/${search ? forwardSearch(search) : ''}`)
  }

  const handleUnionLoginRedirect = () => {
    if (!isUnionLoginRequest(urlParams)) {
      return false
    }

    const validation = validateUnionLogin(urlParams, config.UNION_LAKE_REDIRECT_ORIGINS)

    if (!validation.ok) {
      log.auth.warning('Union Login Redirect Error', validation.message)
      setError({
        type: 'auth',
        message: validation.message
      })
      return true
    }

    const redirectUrl = new URL(validation.value.redirectUri)
    const intent = getLakeConnectIntentFromSearch(redirectUrl.searchParams)

    if (intent) {
      eventTracking(MixpanelEvents.TidbCloudLake.View.EnterClicked, {
        entry: 'union_login',
        ...getLakeConnectTrackingProperties(intent)
      })
    }

    window.location.replace(validation.value.redirectUri)
    return true
  }

  const handleInvitedUserLoginRedirect = async () => {
    // Check if the user is an unregistered SSO user,
    // if so, redirect to /signup-sso
    try {
      await getUserLastVisitHistory({ isHandleErr: true, _warningStatus: [401] })
    } catch (err) {
      const is401Err = axios.isAxiosError(err) && err?.response?.status === 401
      const errMsg: string = getErrorMessage(err)
      const isUnregisteredSSOUser = errMsg?.toLowerCase() === ErrorMessage.UNREGISTERED_SSO_USER
      const isPendingSSOUser = errMsg?.toLocaleLowerCase() === ErrorMessage.PENDING_SIGNUP_SSO_USER
      const msp = new URLSearchParams(inviteUser.search).get('msp') || ''

      if (is401Err && isUnregisteredSSOUser) {
        return history.push({
          pathname: '/signup-sso',
          search: `?${SSO_SIGNUP_PATTERN}=${ACCEPT_INVITATION_ACTION}${!!msp ? '&msp=' + msp : ''}`
        })
      }
      if (is401Err && isPendingSSOUser) {
        return history.push({ pathname: '/forgot_password', search: '?type=pending' })
      }
    }

    try {
      const { invite_link, connection, ...userInfo } = inviteUser

      await acceptInvitation(inviteUser.org_id, userInfo)
      // remove invite user session storage
      removeInviteUser()
      // Cache org id as the first choice for invitee
      setLastCachedOrg(inviteUser.org_id)
      // Redirect to index page
      // Maybe replace `history` provided by `react-router`
      // with window.location.href to force refresh
      history.replace('/')
    } catch (e) {
      log.error('Invite error', e as Error)
      setError({
        type: 'invite',
        message: getErrorMessage(e)
      })
    }
  }

  /**
   * Check if the invitee's email is
   * the same as the logged-in user's email.
   * If it is, call `handleInvitedUserLoginRedirect`
   * to continue accepting the invitation.
   * Otherwise, call `removeInviteUser` to
   * clear the invitation info.
   */
  const handleInviteeAuth = async () => {
    const { invite_link, ...inviteeInfo } = inviteUser
    const loginEmail = localStorage.getItem('sign_in_email')

    // using lowercase beacause email is case-insensitive
    if (!!inviteeInfo.email && inviteeInfo.email.toLowerCase() === loginEmail?.toLowerCase()) {
      await handleInvitedUserLoginRedirect()

      return {
        isInvitation: true
      }
    } else {
      removeInviteUser()

      log.warning(`Invitee inconsistent, before: ${inviteeInfo.email}, after: ${loginEmail}`)

      return {
        isInvitation: false
      }
    }
  }

  const handleSocialLoginRedirect = () => {
    try {
      removeSocialSignUp()

      // For new sign up user, redirect to home page
      history.replace('/')
    } catch (e) {
      log.auth.error('Signup error', e as Error)
      setError({
        type: 'signup',
        message: getErrorMessage(e)
      })
    }
  }

  useEffect(() => {
    async function handleAuthSuccess() {
      if (inviteUser) {
        const { isInvitation } = await handleInviteeAuth()

        if (isInvitation) {
          return
        }
      }

      if (socialSignUp) {
        return handleSocialLoginRedirect()
      }

      if (await handlePasswordPolicyCheck()) {
        if (handleUnionLoginRedirect()) {
          return
        }

        handleRegularLoginRedirect()
      }
    }

    function handleAuthError(err: auth0.Auth0ParseHashError | null) {
      if (!err) {
        if (!window.location.hash) {
          log.auth.exception(new Error('Auth Error: Missing hash'))
        } else {
          log.auth.exception(new Error('Auth Error'))
        }
        setError({
          type: 'auth',
          message: 'Authentication failure.'
        })
        return
      }

      if (err.error === ErrorType.Server) {
        // redirect to universal login page when auth0 embedded login failed
        log.auth.warning('Auth Error Redirect', JSON.stringify(err))
        auth.login()
      } else if (err.error === ErrorType.InvalidToken && err.errorDescription === '`state` does not match.') {
        log.auth.warning('Auth Invalid Token', JSON.stringify(err))
        auth.logout('/')
      } else if (err.error === ErrorType.Denied) {
        setError({
          type: 'auth',
          ...parseError(err.errorDescription || 'Access Denied')
        })
      } else if (err.error === ErrorType.Unauthorized && err.errorDescription === 'password_expiration') {
        history.replace(`/reset_password`)
      } else {
        log.auth.warning('Auth Error', JSON.stringify(err))
        setError({
          type: 'auth',
          message: err.errorDescription || 'Authentication failure.'
        })
      }
    }

    auth.handleAuthentication(handleAuthSuccess, handleAuthError)

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

  const handleRetry = () => {
    // get invite link pathname
    const link = inviteUser?.invite_link
    // remove sessionStorage
    removeInviteUser()

    history.replace(link)
  }

  // accept invite error case
  if (error?.type === 'invite') {
    return (
      <ErrorPanel withSupport={false}>
        <Typography variant="title-lg" ta="center">
          Please sign up with your trial email: {inviteUser?.email}
        </Typography>
        <Typography ta="center">{error?.message}</Typography>
        <Flex justify="center">
          <Button
            mt={24}
            variant="default"
            onClick={handleRetry}
            data-mp-event="Retry Sign Up Button Clicked"
            data-mp-reason="Invite"
          >
            Retry Sign Up
          </Button>
        </Flex>
      </ErrorPanel>
    )
  }

  // other error cases
  if (error)
    return (
      <ErrorPanel message={error?.message} withSupport={false}>
        <ContactMessage mt={8} />
        <Flex justify="center" mt={24}>
          <Button
            variant="default"
            onClick={() => auth.logout(error.companyName ? getEnterpriseSwitchURL(error.companyName) : undefined)}
            data-mp-event="mp-retrylogin-1691461013055"
            mr={32}
          >
            Retry Login
          </Button>
          <ErrorSupportLink />
        </Flex>
      </ErrorPanel>
    )

  return (
    <>
      <PageLoader />
      <Modal
        size="small"
        opened={passwordModal.open}
        onClose={handleCancel}
        title="Do you want to change your password now?"
        trapFocus={false}
        centered={true}
      >
        <p>{passwordModal.content}</p>
        <Group justify="flex-end">
          <Button variant="default" c="carbon.8" onClick={handleCancel} data-mp-event="Auth Redirect No Button Clicked">
            No
          </Button>
          <Button onClick={handleConfirm} data-mp-event="Auth Redirect Yes Button Clicked">
            Yes
          </Button>
        </Group>
      </Modal>
    </>
  )
}
