import '@fastify/cookie'

import { Controller, Get, HttpException, HttpStatus, Logger, Req, Res } from '@nestjs/common'

import type { FastifyReply, FastifyRequest } from 'fastify'

import { AuthenticationService } from './authentication.service'
import { authenticationCookieName } from './authentication.types'
import { unknownToErrorMessage } from './utils'

@Controller('auth')
export class AuthenticationController {
  private readonly logger = new Logger(AuthenticationController.name)
  private readonly cookieOptions: {
    domain: string
    path: string
  }

  constructor(private readonly authService: AuthenticationService) {
    this.cookieOptions = {
      domain: this.authService.cookieDomain,
      path: '/',
    }
  }

  /**
   * Authentication route that logs a user in using an access token and sets the identity cookie.
   */
  @Get('callback')
  async loginWithToken(
    @Req() request: FastifyRequest,
    @Res({ passthrough: true }) response: FastifyReply,
  ): Promise<HttpStatus> {
    try {
      const res = await this.authService.getAccessTokenFromURL(request.url)

      if (!res.ok) {
        this.logger.error(unknownToErrorMessage(res.error, 'Login failed'))
        return HttpStatus.BAD_REQUEST
      }

      if (res.state?.redirectTo) {
        response = response.setCookie(authenticationCookieName, res.accessToken, this.cookieOptions)
        if (res.refreshToken) {
          response = response.setCookie(`${authenticationCookieName}-refresh`, res.refreshToken, this.cookieOptions)
        }
        return response.redirect(res.state.redirectTo, 302)
      }

      return response.send({ ok: true, accessToken: res.accessToken })
    } catch (e) {
      this.logger.warn(unknownToErrorMessage(e, 'Error during exchange of token'))
      throw new HttpException('User could not be authenticated', HttpStatus.UNAUTHORIZED)
    }
  }

  /**
   *  Logout route that removes subscriptions and cookies.
   */
  @Get('logout')
  async logout(
    @Req() request: FastifyRequest,
    @Res({ passthrough: true }) response: FastifyReply,
  ): Promise<HttpStatus> {
    try {
      // clear cookies
      response = response.clearCookie(authenticationCookieName, this.cookieOptions)
      response = response.clearCookie(`${authenticationCookieName}-refresh`, this.cookieOptions)

      const res = await this.authService.getLogoutUrl(request)
      // redirect to Keycloak logout page
      return response.send(res)
    } catch (e) {
      this.logger.warn(unknownToErrorMessage(e, 'Error during retrieving logout url'))
      throw new HttpException('Logout url could not be retrieved', HttpStatus.BAD_REQUEST)
    }
  }

  /**
   * Refresh route that refreshes the access token and sets the identity cookie.
   */
  @Get('refresh')
  async refreshAccessToken(
    @Req() request: FastifyRequest,
    @Res({ passthrough: true }) response: FastifyReply,
  ): Promise<HttpStatus> {
    try {
      const res = await this.authService.refreshAccessToken(request)

      if (!res.ok) {
        this.logger.error(unknownToErrorMessage(res.error, 'Refreshing token failed'))
        return HttpStatus.BAD_REQUEST
      }

      response = response.setCookie(authenticationCookieName, res.accessToken, this.cookieOptions)
      if (res.refreshToken) {
        response = response.setCookie(`${authenticationCookieName}-refresh`, res.refreshToken, this.cookieOptions)
      }
      return response.send({ ok: true, accessToken: res.accessToken })
    } catch (e) {
      this.logger.warn(unknownToErrorMessage(e, 'Error during refreshing token'))
      throw new HttpException('Token could not be refreshed', HttpStatus.UNAUTHORIZED)
    }
  }
}
