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'

const AccountContext = createContext()

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

const maybeSecureCookie = cookie => {
  return window.location.protocol === 'https:' ? cookie + '; Secure' : cookie
}

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

  const updateAccountsFromCookie = useCallback(() => {
    try {
      const { multi_auth: multiAuthCookie } = cookie.parse(document.cookie)
      const accounts = multiAuthCookie
        ? JSON.parse(b64Decode(multiAuthCookie))
        : me ? [{ id: Number(me.id), name: me.name, photoId: me.photoId }] : []
      setAccounts(accounts)
      // required for backwards compatibility: sync cookie with accounts if no multi auth cookie exists
      // this is the case for sessions that existed before we deployed account switching
      if (!multiAuthCookie && !!me) {
        document.cookie = maybeSecureCookie(`multi_auth=${b64Encode(accounts)}; Path=/`)
      }
    } catch (err) {
      console.error('error parsing cookies:', err)
    }
  }, [])

  useEffect(updateAccountsFromCookie, [])

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

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

  const multiAuthSignout = useCallback(async () => {
    const { status } = await fetch('/api/signout', { 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])

  useEffect(() => {
    if (SSR) return
    const { 'multi_auth.user-id': multiAuthUserIdCookie } = cookie.parse(document.cookie)
    setMeAnon(multiAuthUserIdCookie === 'anonymous')
  }, [])

  const value = useMemo(
    () => ({ accounts, addAccount, removeAccount, meAnon, setMeAnon, multiAuthSignout }),
    [accounts, addAccount, removeAccount, meAnon, setMeAnon, multiAuthSignout])
  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 [name, setName] = useState(account.name)
  const [photoId, setPhotoId] = useState(account.photoId)
  useQuery(USER,
    {
      variables: { id: account.id },
      onCompleted ({ user: { name, photoId } }) {
        if (photoId) setPhotoId(photoId)
        if (name) setName(name)
      }
    }
  )

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

    // update pointer cookie
    document.cookie = maybeSecureCookie(`multi_auth.user-id=${anonRow ? 'anonymous' : account.id}; Path=/`)

    // 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 } = useAccounts()
  const router = useRouter()

  // 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>
    </>
  )
}