import type { APIGatewayEvent, Context } from 'aws-lambda'
import CryptoJS from 'crypto-js'

import { db } from 'src/lib/db'
import { logger } from 'src/lib/logger'

/**
 * The handler function is your code that processes http request events.
 * You can use return and throw to send a response or error, respectively.
 *
 * Important: When deployed, a custom serverless function is an open API endpoint and
 * is your responsibility to secure appropriately.
 *
 * @see {@link https://redwoodjs.com/docs/serverless-functions#security-considerations|Serverless Function Considerations}
 * in the RedwoodJS documentation for more information.
 *
 * @typedef { import('aws-lambda').APIGatewayEvent } APIGatewayEvent
 * @typedef { import('aws-lambda').Context } Context
 * @param { APIGatewayEvent } event - an object which contains information from the invoker.
 * @param { Context } _context - contains information about the invocation,
 * function, and execution environment.
 */
export const handler = async (event: APIGatewayEvent, _context: Context) => {
  logger.info(`${event.httpMethod} ${event.path}: oauth function`)

  switch (event.path) {
    case '/oauth/microsoft/callback':
      return await callback(event)
    default:
      return {
        statusCode: 404,
      }
  }
}

const callback = async (event: APIGatewayEvent) => {
  const { code } = event.queryStringParameters

  console.log('Before fetch')

  const res = await fetch(
    `https://login.microsoftonline.com/common/oauth2/v2.0/token`,
    {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded              ',
      },
      body: new URLSearchParams({
        client_id: process.env.MICROSOFT_OAUTH_CLIENT_ID,
        code,
        redirect_uri: process.env.MICROSOFT_OAUTH_REDIRECT_URI,
        grant_type: 'authorization_code',
        client_secret: process.env.MICROSOFT_OAUTH_CLIENT_SECRET,
      }),
    }
  )

  const body = await res.text()
  const { access_token: accessToken, scope, error } = JSON.parse(body)

  if (error) {
    logger.warn(error)
    return {
      statuscode: 400,
      body: JSON.stringify({ body, error, code, scope }),
    }
  }

  try {
    const providerUser = await getProviderUser(accessToken)
    console.log(providerUser)
    const user = await getUser({ providerUser, accessToken, scope })
    const cookie = secureCookie(user)

    return {
      statusCode: 302,
      headers: {
        'Set-Cookie': cookie,
        Location: '/',
      },
    }
  } catch (e) {
    return { statuscode: 500, body: e.message }
  }

  // return {
  //   body: JSON.stringify({ accessToken, scope }),
  // }
}

const secureCookie = (user) => {
  const expires = new Date()
  expires.setFullYear(expires.getFullYear() + 1)

  const cookieAttrs = [
    `Expires=${expires.toUTCString()}`,
    'HttpOnly=true',
    'Path=/',
    'SameSite=Strict',
    `Secure=${process.env.NODE_ENV !== 'development'}`,
  ]
  const data = JSON.stringify({ id: user.id })

  const encrypted = CryptoJS.AES.encrypt(
    data,
    process.env.SESSION_SECRET
  ).toString()

  return [`session_8911=${encrypted}`, ...cookieAttrs].join('; ')
}

const getUser = async ({ providerUser, accessToken, scope }) => {
  const { user, identity } = await findOrCreateUser(providerUser)

  await db.identity.update({
    where: { id: identity.id },
    data: { accessToken, scope, lastLoginAt: new Date() },
  })

  return user
}

const findOrCreateUser = async (providerUser) => {
  const identity = await db.identity.findFirst({
    where: {
      provider: 'microsoft',
      uid: providerUser.id.toString(),
    },
  })

  if (identity) {
    const user = await db.user.findUnique({ where: { id: identity.userId } })
    return { user, identity }
  }

  return await db.$transaction(async (tx) => {
    const user = await tx.user.create({
      data: {
        email: providerUser.mail,
        firstName: providerUser.givenName,
        lastName: providerUser.surname,
      },
    })

    const identity = await tx.identity.create({
      data: {
        userId: user.id,
        provider: 'microsoft',
        uid: providerUser.id.toString(),
      },
    })

    return { user, identity }
  })
}

const getProviderUser = async (token) => {
  const response = await fetch('https://graph.microsoft.com/v1.0/me', {
    headers: { Authorization: `Bearer ${token}` },
  })
  const body = JSON.parse(await response.text())

  return body
}
