import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { useRouter } from 'next/router'
import * as cookie from 'cookie'
import { useMe } from '@/components/me'
import { USER_ID, SSR } from '@/lib/constants'
import { USER } from '@/fragments/users'
import { useQuery } from '@apollo/client'
import { UserListRow } from '@/components/user-list'
import Link from 'next/link'
import AddIcon from '@/svgs/add-fill.svg'
import { MultiAuthErrorBanner } from '@/components/banners'
import { cookieOptions } from '@/lib/auth'

const AccountContext = createContext()

const CHECK_ERRORS_INTERVAL_MS = 5_000

const b64Decode = str => Buffer.from(str, 'base64').toString('utf-8')

export const AccountProvider = ({ children }) => {
  const [accounts, setAccounts] = useState([])
  const [meAnon, setMeAnon] = useState(true)
  const [errors, setErrors] = useState([])

  const updateAccountsFromCookie = useCallback(() => {
    const { multi_auth: multiAuthCookie } = cookie.parse(document.cookie)
    const accounts = multiAuthCookie
      ? JSON.parse(b64Decode(multiAuthCookie))
      : []
    setAccounts(accounts)
  }, [])

  const addAccount = useCallback(user => {
    setAccounts(accounts => [...accounts, user])
  }, [])

  const removeAccount = useCallback(userId => {
    setAccounts(accounts => accounts.filter(({ id }) => id !== userId))
  }, [])

  const nextAccount = useCallback(async () => {
    const { status } = await fetch('/api/next-account', { credentials: 'include' })
    // if status is 302, this means the server was able to switch us to the next available account
    // and the current account was simply removed from the list of available accounts including the corresponding JWT.
    const switchSuccess = status === 302
    if (switchSuccess) updateAccountsFromCookie()
    return switchSuccess
  }, [updateAccountsFromCookie])

  const checkErrors = useCallback(() => {
    const {
      multi_auth: multiAuthCookie,
      'multi_auth.user-id': multiAuthUserIdCookie
    } = cookie.parse(document.cookie)

    const errors = []

    if (!multiAuthCookie) errors.push('multi_auth cookie not found')
    if (!multiAuthUserIdCookie) errors.push('multi_auth.user-id cookie not found')

    setErrors(errors)
  }, [])

  useEffect(() => {
    if (SSR) return

    updateAccountsFromCookie()

    const { 'multi_auth.user-id': multiAuthUserIdCookie } = cookie.parse(document.cookie)
    setMeAnon(multiAuthUserIdCookie === 'anonymous')

    const interval = setInterval(checkErrors, CHECK_ERRORS_INTERVAL_MS)
    return () => clearInterval(interval)
  }, [updateAccountsFromCookie, checkErrors])

  const value = useMemo(
    () => ({
      accounts,
      addAccount,
      removeAccount,
      meAnon,
      setMeAnon,
      nextAccount,
      multiAuthErrors: errors
    }),
    [accounts, addAccount, removeAccount, meAnon, setMeAnon, nextAccount, errors])
  return <AccountContext.Provider value={value}>{children}</AccountContext.Provider>
}

export const useAccounts = () => useContext(AccountContext)

const AccountListRow = ({ account, ...props }) => {
  const { meAnon, setMeAnon } = useAccounts()
  const { me, refreshMe } = useMe()
  const anonRow = account.id === USER_ID.anon
  const selected = (meAnon && anonRow) || Number(me?.id) === Number(account.id)
  const router = useRouter()

  // fetch updated names and photo ids since they might have changed since we were issued the JWTs
  const { data, error } = useQuery(USER,
    {
      variables: { id: account.id }
    }
  )
  if (error) console.error(`query for user ${account.id} failed:`, error)

  const name = data?.user?.name || account.name
  const photoId = data?.user?.photoId || account.photoId

  const onClick = async (e) => {
    // prevent navigation
    e.preventDefault()

    // update pointer cookie
    const options = cookieOptions({ httpOnly: false })
    document.cookie = cookie.serialize('multi_auth.user-id', anonRow ? 'anonymous' : account.id, options)

    // update state
    if (anonRow) {
      // order is important to prevent flashes of no session
      setMeAnon(true)
      await refreshMe()
    } else {
      await refreshMe()
      // order is important to prevent flashes of inconsistent data in switch account dialog
      setMeAnon(account.id === USER_ID.anon)
    }

    // reload whatever page we're on to avoid any bugs due to missing authorization etc.
    router.reload()
  }

  return (
    <div className='d-flex flex-row'>
      <UserListRow
        user={{ ...account, photoId, name }}
        className='d-flex align-items-center me-2'
        {...props}
        onNymClick={onClick}
        selected={selected}
      />
    </div>
  )
}

export default function SwitchAccountList () {
  const { accounts, multiAuthErrors } = useAccounts()
  const router = useRouter()

  const hasError = multiAuthErrors.length > 0

  if (hasError) {
    return (
      <>
        <div className='my-2'>
          <div className='d-flex flex-column flex-wrap mt-2 mb-3'>
            <MultiAuthErrorBanner errors={multiAuthErrors} />
          </div>
        </div>
      </>
    )
  }

  // can't show hat since the streak is not included in the JWT payload
  return (
    <>
      <div className='my-2'>
        <div className='d-flex flex-column flex-wrap mt-2 mb-3'>
          <h4 className='text-muted'>Accounts</h4>
          <AccountListRow account={{ id: USER_ID.anon, name: 'anon' }} showHat={false} />
          {
            accounts.map((account) => <AccountListRow key={account.id} account={account} showHat={false} />)
          }
        </div>
        <Link
          href={{
            pathname: '/login',
            query: { callbackUrl: window.location.origin + router.asPath, multiAuth: true }
          }}
          className='text-reset fw-bold'
        >
          <AddIcon height={20} width={20} /> another account
        </Link>
      </div>
    </>
  )
}