import * as admin from 'firebase-admin'
import fetch from 'node-fetch'
import { IncomingMessage, ServerResponse } from 'http'
import { FIREBASE_CONFIG, PROJECT_ID } from 'common/envs/constants'
import { getAuthCookies, setAuthCookies } from './auth'
import { GetServerSideProps, GetServerSidePropsContext } from 'next'

const ensureApp = async () => {
  // Note: firebase-admin can only be imported from a server context,
  // because it relies on Node standard library dependencies.
  if (admin.apps.length === 0) {
    // never initialize twice
    return admin.initializeApp({ projectId: PROJECT_ID })
  }
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  return admin.apps[0]!
}

const requestFirebaseIdToken = async (refreshToken: string) => {
  // See https://firebase.google.com/docs/reference/rest/auth/#section-refresh-token
  const refreshUrl = new URL('https://securetoken.googleapis.com/v1/token')
  refreshUrl.searchParams.append('key', FIREBASE_CONFIG.apiKey)
  const result = await fetch(refreshUrl.toString(), {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
    }),
  })
  if (!result.ok) {
    throw new Error(`Could not refresh ID token: ${await result.text()}`)
  }
  return (await result.json()) as any
}

type RequestContext = {
  req: IncomingMessage
  res: ServerResponse
}

export const getServerAuthenticatedUid = async (ctx: RequestContext) => {
  const app = await ensureApp()
  const auth = app.auth()
  const { idToken, refreshToken } = getAuthCookies(ctx.req)

  // If we have a valid ID token, verify the user immediately with no network trips.
  // If the ID token doesn't verify, we'll have to refresh it to see who they are.
  // If they don't have any tokens, then we have no idea who they are.
  if (idToken != null) {
    try {
      return (await auth.verifyIdToken(idToken))?.uid
    } catch {
      // plausibly expired; try the refresh token, if it's present
    }
  }
  if (refreshToken != null) {
    try {
      const resp = await requestFirebaseIdToken(refreshToken)
      setAuthCookies(resp.id_token, resp.refresh_token, ctx.res)
      return (await auth.verifyIdToken(resp.id_token))?.uid
    } catch (e) {
      // this is a big unexpected problem -- either their cookies are corrupt
      // or the refresh token API is down. functionally, they are not logged in
      console.error(e)
    }
  }
  return undefined
}

export const redirectIfLoggedIn = (dest: string, fn?: GetServerSideProps) => {
  return async (ctx: GetServerSidePropsContext) => {
    const uid = await getServerAuthenticatedUid(ctx)
    if (uid == null) {
      return fn != null ? await fn(ctx) : { props: {} }
    } else {
      return { redirect: { destination: dest, permanent: false } }
    }
  }
}

export const redirectIfLoggedOut = (dest: string, fn?: GetServerSideProps) => {
  return async (ctx: GetServerSidePropsContext) => {
    const uid = await getServerAuthenticatedUid(ctx)
    if (uid == null) {
      return { redirect: { destination: dest, permanent: false } }
    } else {
      return fn != null ? await fn(ctx) : { props: {} }
    }
  }
}