/**
 * Het 'use server' directive wordt gebruikt om aan te geven dat de code in dit bestand enkel server actions bevat.
 * Server actions zijn asynchrone functies die enkel op de server uitgevoerd kunnen worden.
 * Een server action wordt automatisch geconverteerd naar HTTP endpoints door Next.js en kunnen dus aangeroepen worden
 * van op de client.
 *
 * Zodra het 'use server' directive toegevoegd wordt, mag het bestand enkel asynchrone functies exporteren.
 */
'use server'

import {redirect} from 'next/navigation'
import {revalidatePath} from 'next/cache'
import {convertFormData} from '@/lib/convertFormData'
import type {CreateUserParams, UpdateUserParams} from '@/dal/users'
import {createUser, getUserByEmail, startSession, stopSession, updateUser} from '@/dal/users'
import {getSalt, hashOptions, verifyPassword} from '@/lib/passwordUtils'
import {clearSessionCookie, getSessionId, getSessionProfileFromCookie, setSessionCookie} from '@/lib/sessionUtils'
import {getLogger} from '@/lib/logger'

export async function registerAction(_prevData: unknown, formData: FormData): Promise<void> {
  const logger = await getLogger()
  const newUserInfo = convertFormData<CreateUserParams>(formData)
  const user = await createUser(newUserInfo)
  logger.info(`New user created: ${newUserInfo.id}`)

  const session = await startSession(user.id, user.role)
  logger.info(`New session started: ${session.id}, ends at ${session.activeUntil.toISOString()}`)

  await setSessionCookie(session)

  // De gebruiker is ingelogd, dus redirecten we naar de contactenpagina.
  redirect('/contacts')
}

export async function signInAction(_prevData: unknown, formData: FormData): Promise<void> {
  const params = convertFormData<{email: string; password: string}>(formData)
  const user = await getUserByEmail(params.email)
  const logger = await getLogger()

  // Als we meteen een unauthorized terug geven nadat een gebruiker niet gevonden is in de database, kan een aanvaller
  // hieruit afleiden dat het e-mailadres niet bestaat.
  // Vervolgens kan de aanvaller overgaan naar andere email adressen, en moet deze geen tijd meer spenderen aan het adres
  // dat niet bestaat.
  // Als oplossing voegen we een alternatief wachtwoord toe dat gebruikt wordt als de gebruiker niet gevonden is in de
  // database.
  // Omdat we nu in alle gevallen een wachtwoord hashen, is het moeilijker om te bepalen of een e-mailadres bestaat of
  // niet op basis van de response tijd.
  const timingSafePassword = `${hashOptions.iterations}$${hashOptions.keyLength}$preventTimingBasedAttacks123$${getSalt()}`
  const isValidPassword = verifyPassword(user?.password ?? timingSafePassword, params.password)

  if (!isValidPassword) {
    logger.warn(`Failed sign in attempt for ${params.email}.`)
    return
  }

  logger.info(`Successful authentication request for ${user!.id}`)
  const session = await startSession(user!.id, user!.role)
  logger.info(`New session started: ${session.id}, ends at ${session.activeUntil.toISOString()}`)

  await setSessionCookie(session)

  // De gebruiker is ingelogd, dus redirecten we naar de contactenpagina.
  redirect('/contacts')
}

/**
 * Pas de profielgegevens van de ingelogde gebruiker aan.
 *
 * @param _prevData De vorige data die ingestuurd werd naar de actie. We maken hier geen gebruik van, de parameter is
 * enkel aanwezig om de signatuur van de functie gelijkt te stellen met wat Next.js/React verwacht.
 * @param formData De data die ingestuurd werd naar de actie.
 */
export async function updateProfileAction(_prevData: unknown, formData: FormData): Promise<void> {
  const logger = await getLogger()
  const updatedData = convertFormData<UpdateUserParams>(formData)
  const user = await getSessionProfileFromCookie()

  if (!user) {
    logger.warn(`Unauthenticated attempt to update a user's profile.`)
    return
  }

  // Het is belangrijk dat het id van de gebruiker opgehaald wordt op basis van de sessie (via de backend) en niet
  // zomaar door de client ingevuld kan worden.
  // Als je de formuliergegevens die de client doorstuurt blindelings vertrouwd, kan een kwaadwillige gebruiker data van
  // andere gebruikers aanpassen.
  await updateUser({...updatedData, id: user.id})
  logger.info(`Successfully updated the profile for ${user.id}.`)

  // Het profiel wordt gebruikt in de Navbar component, aangezien deze component op de homepagina staat moeten we
  // het root path van de applicatie revalideren.
  revalidatePath('/', 'layout')
}

/**
 * Log uit en redirect naar de homepagina.
 */
export async function signOutServerFunction(): Promise<void> {
  const logger = await getLogger()

  // Deze server action heeft geen parameters, dit betekent dat we deze actie niet kunnen aanroepen via een formulier.
  // De action kan echter wel gewoon opgeroepen worden als klik handler op een knop.
  const sessionId = await getSessionId()

  if (sessionId) {
    await stopSession(sessionId)
    logger.info(`Session stopped: ${sessionId}.`)

    await clearSessionCookie()
  }

  redirect('/login')
}
