/* eslint-disable unicorn/no-null */
import Joi from "joi";
import * as Hapi from "@hapi/hapi";
import { ResponseToolkit, ResponseObject } from "@hapi/hapi";

export interface LinkAccountPayload {
  userId: string;
  providerType: string;
  providerId: string;
  providerAccountId: string;
  refreshToken: string;
  accessToken: string;
  accessTokenExpires?: null;
}

export const register = async <TUser, TProfile>(
  server: Hapi.Server,
  opts: any,
  auth?: string
): Promise<void> => {
  const { tags, basePath, validators } = opts;
  const { session, user, userId, profile } = validators;
  server.route([
    {
      method: "POST",
      path: `${basePath}/createUser`,
      options: {
        auth,
        tags,
        validate: {
          payload: profile,
        },
        async handler(
          request: Hapi.Request,
          h: ResponseToolkit
        ): Promise<Hapi.ResponseObject> {
          const payload: TProfile = request.payload as TProfile;
          const r = await opts
            .nextAuthAdapterFactory(request)
            .createUser(payload);
          return h.response(r as object);
        },
        description: "Create a user from a profile",
      },
    },
    {
      method: "GET",
      path: `${basePath}/getUser/{userId}`,
      options: {
        auth,
        tags,
        validate: {
          params: {
            userId,
          },
        },
        async handler(
          request: Hapi.Request,
          h: ResponseToolkit
        ): Promise<ResponseObject> {
          const id = request.params.userId;
          const r = await opts.nextAuthAdapterFactory(request).getUser(id);
          if (!r) return h.response().code(404);
          return h.response(r as object);
        },
        description: "Get a user by id",
      },
    },
    {
      method: "GET",
      path: `${basePath}/getUserByEmail/{userEmail}`,
      options: {
        auth,
        tags,
        validate: {
          params: {
            userEmail: Joi.string().email(),
          },
        },
        async handler(
          request: Hapi.Request,
          h: ResponseToolkit
        ): Promise<ResponseObject> {
          const email = request.params.userEmail;
          const r = await opts
            .nextAuthAdapterFactory(request)
            .getUserByEmail(email);
          if (!r) return h.response().code(404);
          return h.response(r as object);
        },
        description: "Get a user by email",
      },
    },
    {
      method: "GET",
      path: `${basePath}/getUserByAccount/{provider}/{providerAccountId}`,
      options: {
        auth,
        tags,
        validate: {
          params: {
            provider: Joi.string(),
            providerAccountId: Joi.string(),
          },
        },
        async handler(
          request: Hapi.Request,
          h: ResponseToolkit
        ): Promise<ResponseObject> {
          const { provider, providerAccountId } = request.params;
          const r = await opts
            .nextAuthAdapterFactory(request)
            .getUserByAccount(provider, providerAccountId);
          if (!r) return h.response().code(404);
          return h.response(r as object);
        },
        description: "Get a user by provider id and provider account id",
      },
    },
    {
      method: "PUT",
      path: `${basePath}/updateUser`,
      options: {
        auth,
        tags,
        validate: {
          payload: user,
        },
        async handler(
          request: Hapi.Request,
          h: ResponseToolkit
        ): Promise<ResponseObject> {
          const payload: TUser = request.payload as TUser;
          const r = await opts
            .nextAuthAdapterFactory(request)
            .updateUser(payload);
          if (!r) return h.response().code(404);
          return h.response(r as object);
        },
        description: "Update a user's data",
      },
    },
    {
      method: "PUT",
      path: `${basePath}/linkAccount`,
      options: {
        auth,
        tags,
        validate: {
          payload: Joi.object({
            // https://next-auth.js.org/getting-started/upgrade-v4#schema-changes
            userId: Joi.string().required(),
            provider: Joi.string().required(),
            type: Joi.string().required(),
            providerAccountId: Joi.string().required(),
            refresh_token: Joi.string().optional().allow(null),
            access_token: Joi.string().optional().allow(null),
            expires_at: Joi.number().optional().allow(null),
          }).unknown(true),
        },
        async handler(
          request: Hapi.Request,
          h: ResponseToolkit
        ): Promise<ResponseObject> {
          const {
            userId,
            providerId,
            providerType,
            providerAccountId,
            refreshToken,
            accessToken,
            accessTokenExpires,
          } = request.payload as LinkAccountPayload;
          await opts
            .nextAuthAdapterFactory(request)
            .linkAccount(
              userId,
              providerId,
              providerType,
              providerAccountId,
              refreshToken,
              accessToken,
              accessTokenExpires
            );
          return h.response().code(204);
        },
        description: "Link a provider account with a user",
      },
    },
    {
      method: "POST",
      path: `${basePath}/createSession`,
      options: {
        auth,
        tags,
        validate: {
          payload: Joi.object({
            userId: Joi.string().required(),
            sessionToken: Joi.string().required(),
            expires: Joi.string().isoDate().required(),
          }),
        },
        async handler(
          request: Hapi.Request,
          h: ResponseToolkit
        ): Promise<ResponseObject> {
          const payload: TUser = request.payload as TUser;
          const r = await opts
            .nextAuthAdapterFactory(request)
            .createSession(payload);
          return h.response(r as object);
        },
        description: "Create a new session for a user",
      },
    },
    {
      method: "GET",
      path: `${basePath}/getSessionAndUser/{sessionToken}`,
      options: {
        auth,
        tags,
        validate: {
          params: {
            sessionToken: Joi.string(),
          },
        },
        async handler(
          request: Hapi.Request,
          h: ResponseToolkit
        ): Promise<ResponseObject> {
          const token = request.params.sessionToken;
          const r = await opts
            .nextAuthAdapterFactory(request)
            .getSessionAndUser(token);
          if (!r) return h.response().code(404);
          return h.response(r as object);
        },
        description: "Get a session by its token",
      },
    },
    {
      method: "PUT",
      path: `${basePath}/updateSession`,
      options: {
        auth,
        tags,
        validate: {
          payload: session,
        },
        async handler(
          request: Hapi.Request,
          h: ResponseToolkit
        ): Promise<ResponseObject> {
          const inputPayload = request.payload as any;
          const { expires } = inputPayload;
          const payload = {
            ...inputPayload,
            expires: new Date(expires),
          };
          const force = Boolean(request.query.force);
          const r = await opts
            .nextAuthAdapterFactory(request)
            .updateSession(payload, force);
          if (!r) return h.response().code(204);
          return h.response(r as object);
        },
        description: "Update a session for a user",
      },
    },
    {
      method: "DELETE",
      path: `${basePath}/deleteSession/{sessionToken}`,
      options: {
        auth,
        tags,
        validate: {
          params: {
            sessionToken: Joi.string(),
          },
        },
        async handler(
          request: Hapi.Request,
          h: ResponseToolkit
        ): Promise<ResponseObject> {
          const token = request.params.sessionToken;
          await opts.nextAuthAdapterFactory(request).deleteSession(token);
          return h.response().code(204);
        },
        description: "Delete a user's session",
      },
    },
  ]);
};
