/* eslint-disable  unicorn/no-null */
import type {
  Adapter,
  AdapterAccount,
  AdapterSession,
  AdapterUser,
} from "next-auth/adapters";
import * as Wreck from "@hapi/wreck";
import * as Boom from "@hapi/boom";

import type { IAppConfig } from "@digiresilience/metamigo-config";

export interface Profile {
  name: string;
  email: string;
  emailVerified: string;
  userRole: string;
  avatar?: string;
  image?: string;
  createdBy: string;
}

export type User = Profile & { id: string; createdAt: Date; updatedAt: Date };

export interface Session {
  userId: string;
  expires: Date;
  sessionToken: string;
  accessToken: string;
  createdAt: Date;
  updatedAt: Date;
}

// from https://github.com/nextauthjs/next-auth/blob/main/src/lib/errors.js
class UnknownError extends Error {
  constructor(message: any) {
    super(message);
    this.name = "UnknownError";
    this.message = message;
  }

  toJSON() {
    return {
      error: {
        name: this.name,
        message: this.message,
        // stack: this.stack
      },
    };
  }
}

class CreateUserError extends UnknownError {
  constructor(message: any) {
    super(message);
    this.name = "CreateUserError";
    this.message = message;
  }
}

const basicHeader = (secret: any) =>
  "Basic " + Buffer.from(secret + ":", "utf8").toString("base64");

export const MetamigoAdapter = (config: IAppConfig): Adapter => {
  if (!config) throw new Error("MetamigoAdapter: config is not defined.");
  const wreck = Wreck.defaults({
    headers: {
      authorization: basicHeader(config.nextAuth.secret),
    },
    baseUrl: `${config.frontend.apiUrl}/api/nextauth/`,
    maxBytes: 1024 * 1024,
    json: "force",
  });

  function getAdapter(): Adapter {
    async function createUser(profile: Profile) {
      try {
        if (!profile.createdBy) profile = { ...profile, createdBy: "nextauth" };
        profile.avatar = profile.image;
        delete profile.image;
        const { payload } = await wreck.post("createUser", {
          payload: profile,
        });
        return payload;
      } catch {
        throw new CreateUserError("CREATE_USER_ERROR");
      }
    }

    async function getUser(id: string) {
      try {
        const { payload } = await wreck.get(`getUser/${id}`);

        return payload;
      } catch (error) {
        if (Boom.isBoom(error, 404)) return null;
        throw new Error("GET_USER_BY_ID_ERROR");
      }
    }

    async function getUserByEmail(email: string) {
      try {
        const { payload } = await wreck.get(`getUserByEmail/${email}`);
        return payload;
      } catch (error) {
        if (Boom.isBoom(error, 404)) return null;
        throw new Error("GET_USER_BY_EMAIL_ERROR");
      }
    }

    async function getUserByAccount({
      providerAccountId,
      provider,
    }: {
      providerAccountId: string;
      provider: string;
    }) {
      try {
        const { payload } = await wreck.get(
          `getUserByAccount/${provider}/${providerAccountId}`
        );

        return payload;
      } catch (error) {
        if (Boom.isBoom(error, 404)) return null;
        console.log(error);
        throw new Error("GET_USER_BY_ACCOUNT");
      }
    }

    async function updateUser(user: User) {
      try {
        const { payload } = await wreck.put("updateUser", {
          payload: user,
        });

        return payload;
      } catch {
        throw new Error("UPDATE_USER");
      }
    }

    async function linkAccount(account: AdapterAccount) {
      try {
        await wreck.put("linkAccount", { payload: account } as any);
      } catch (error) {
        console.log(error);
        throw new Error("LINK_ACCOUNT_ERROR");
      }
    }

    async function createSession(user: User) {
      try {
        const { payload }: { payload: AdapterSession } = await wreck.post(
          "createSession",
          {
            payload: user,
          }
        );
        payload.expires = new Date(payload.expires);
        return payload;
      } catch (error) {
        console.log(error);
        throw new Error("CREATE_SESSION_ERROR");
      }
    }

    async function getSessionAndUser(sessionToken: string) {
      try {
        const { payload }: { payload: any } = await wreck.get(
          `getSessionAndUser/${sessionToken}`
        );
        const {
          session,
          user,
        }: { session: AdapterSession; user: AdapterUser } = payload;
        session.expires = new Date(session.expires);
        return { session, user };
      } catch (error) {
        console.log(error);
        if (Boom.isBoom(error, 404)) return null;
        throw new Error("GET_SESSION_AND_USER_ERROR");
      }
    }

    async function updateSession(session: Session, force: boolean) {
      try {
        const payload = {
          ...session,
          expires: new Date(session.expires).getTime(),
        };
        const { payload: result } = await wreck.put(
          `updateSession?force=${Boolean(force)}`,
          {
            payload,
          }
        );
        return result;
      } catch {
        throw new Error("UPDATE_SESSION_ERROR");
      }
    }

    async function deleteSession(sessionToken: string) {
      try {
        await wreck.delete(`deleteSession/${sessionToken}`);
      } catch {
        throw new Error("DELETE_SESSION_ERROR");
      }
    }

    return {
      createUser,
      getUser,
      getUserByEmail,
      getUserByAccount,
      updateUser,
      // deleteUser,
      linkAccount,
      // unlinkAccount,
      createSession,
      getSessionAndUser,
      updateSession,
      deleteSession,
      // @ts-expect-error: Type error
    } as AdapterInstance<Profile, User, Session, unknown>;
  }

  return getAdapter();
};
