import { ExpressRequest, ExpressResponse } from "./Express";
import Dictionary from "../../Types/Dictionary";
import ObjectID from "../../Types/ObjectID";
import { CookieOptions } from "express";
import JSONWebToken from "./JsonWebToken";
import User from "../../Models/DatabaseModels/User";
import StatusPagePrivateUser from "../../Models/DatabaseModels/StatusPagePrivateUser";
import OneUptimeDate from "../../Types/Date";
import PositiveNumber from "../../Types/PositiveNumber";
import CookieName from "../../Types/CookieName";
import {
  MASTER_PASSWORD_COOKIE_IDENTIFIER,
  MASTER_PASSWORD_COOKIE_MAX_AGE_IN_DAYS,
} from "../../Types/StatusPage/MasterPassword";
import {
  DASHBOARD_MASTER_PASSWORD_COOKIE_IDENTIFIER,
  DASHBOARD_MASTER_PASSWORD_COOKIE_MAX_AGE_IN_DAYS,
} from "../../Types/Dashboard/MasterPassword";
import CaptureSpan from "./Telemetry/CaptureSpan";

export default class CookieUtil {
  // set cookie with express response

  private static readonly DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS: number = 15 * 60;

  @CaptureSpan()
  public static getCookiesFromCookieString(
    cookieString: string,
  ): Dictionary<string> {
    const cookies: Dictionary<string> = {};
    cookieString.split(";").forEach((cookie: string) => {
      const parts: string[] = cookie.split("=");
      const key: string = (parts[0] as string).trim() as string;
      const value: string = parts[1] as string;
      cookies[key] = value;
    });

    return cookies;
  }

  @CaptureSpan()
  public static setSSOCookie(data: {
    user: User;
    projectId: ObjectID;
    expressResponse: ExpressResponse;
  }): void {
    const { user, projectId, expressResponse: res } = data;

    const ssoToken: string = JSONWebToken.sign({
      data: {
        userId: user.id!,
        projectId: projectId,
        name: user.name!,
        email: user.email,
        isMasterAdmin: false,
        isGeneralLogin: false,
      },
      expiresInSeconds: OneUptimeDate.getSecondsInDays(new PositiveNumber(30)),
    });

    CookieUtil.setCookie(res, CookieUtil.getUserSSOKey(projectId), ssoToken, {
      maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
      httpOnly: true,
    });
  }

  @CaptureSpan()
  public static setUserCookie(data: {
    expressResponse: ExpressResponse;
    user: User;
    isGlobalLogin: boolean;
    sessionId: ObjectID;
    refreshToken: string;
    refreshTokenExpiresAt: Date;
    accessTokenExpiresInSeconds?: number;
  }): void {
    const {
      expressResponse: res,
      user,
      isGlobalLogin,
      sessionId,
      refreshToken,
      refreshTokenExpiresAt,
    } = data;

    const accessTokenExpiresInSeconds: number =
      data.accessTokenExpiresInSeconds ||
      CookieUtil.DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS;

    const token: string = JSONWebToken.signUserLoginToken({
      tokenData: {
        userId: user.id!,
        email: user.email!,
        name: user.name!,
        timezone: user.timezone || null,
        isMasterAdmin: user.isMasterAdmin!,
        isGlobalLogin: isGlobalLogin, // This is a general login without SSO. So, we will set this to true. This will give access to all the projects that dont require SSO.
        sessionId: sessionId,
      },
      expiresInSeconds: accessTokenExpiresInSeconds,
    });

    // Set a cookie with token.
    CookieUtil.setCookie(res, CookieUtil.getUserTokenKey(), token, {
      maxAge: accessTokenExpiresInSeconds * 1000,
      httpOnly: true,
    });

    const refreshTokenTtl: number = Math.max(
      refreshTokenExpiresAt.getTime() - Date.now(),
      0,
    );

    CookieUtil.setCookie(res, CookieUtil.getRefreshTokenKey(), refreshToken, {
      maxAge: refreshTokenTtl,
      httpOnly: true,
    });

    if (user.id) {
      // set user id cookie
      CookieUtil.setCookie(res, CookieName.UserID, user.id!.toString(), {
        maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
        httpOnly: false,
      });
    }

    if (user.email) {
      // set user email cookie
      CookieUtil.setCookie(
        res,
        CookieName.Email,
        user.email?.toString() || "",
        {
          maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
          httpOnly: false,
        },
      );
    }

    if (user.name) {
      // set user name cookie
      CookieUtil.setCookie(res, CookieName.Name, user.name?.toString() || "", {
        maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
        httpOnly: false,
      });
    }

    if (user.timezone) {
      // set user timezone cookie
      CookieUtil.setCookie(
        res,
        CookieName.Timezone,
        user.timezone?.toString() || "",
        {
          maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
          httpOnly: false,
        },
      );
    }

    if (user.isMasterAdmin) {
      // set user isMasterAdmin cookie
      CookieUtil.setCookie(
        res,
        CookieName.IsMasterAdmin,
        user.isMasterAdmin?.toString() || "",
        {
          maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
          httpOnly: false,
        },
      );
    }

    if (user.profilePictureId) {
      // set user profile picture id cookie
      CookieUtil.setCookie(
        res,
        CookieName.ProfilePicID,
        user.profilePictureId?.toString() || "",
        {
          maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
          httpOnly: false,
        },
      );
    }
  }

  @CaptureSpan()
  public static setStatusPagePrivateUserCookie(data: {
    expressResponse: ExpressResponse;
    user: StatusPagePrivateUser;
    statusPageId: ObjectID;
    sessionId: ObjectID;
    refreshToken: string;
    refreshTokenExpiresAt: Date;
    accessTokenExpiresInSeconds?: number;
  }): string {
    const {
      expressResponse: res,
      user,
      statusPageId,
      sessionId,
      refreshToken,
      refreshTokenExpiresAt,
    } = data;

    const accessTokenExpiresInSeconds: number =
      data.accessTokenExpiresInSeconds ||
      CookieUtil.DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS;

    const token: string = JSONWebToken.sign({
      data: {
        userId: user.id!,
        email: user.email!,
        statusPageId: statusPageId,
        sessionId: sessionId,
      },
      expiresInSeconds: accessTokenExpiresInSeconds,
    });

    CookieUtil.setCookie(res, CookieUtil.getUserTokenKey(statusPageId), token, {
      maxAge: accessTokenExpiresInSeconds * 1000,
      httpOnly: true,
    });

    const refreshTokenTtl: number = Math.max(
      refreshTokenExpiresAt.getTime() - Date.now(),
      0,
    );

    CookieUtil.setCookie(
      res,
      CookieUtil.getRefreshTokenKey(statusPageId),
      refreshToken,
      {
        maxAge: refreshTokenTtl,
        httpOnly: true,
      },
    );

    return token;
  }

  @CaptureSpan()
  public static setStatusPageMasterPasswordCookie(data: {
    expressResponse: ExpressResponse;
    statusPageId: ObjectID;
  }): void {
    const expiresInDays: PositiveNumber = new PositiveNumber(
      MASTER_PASSWORD_COOKIE_MAX_AGE_IN_DAYS,
    );

    const token: string = JSONWebToken.signJsonPayload(
      {
        statusPageId: data.statusPageId.toString(),
        type: MASTER_PASSWORD_COOKIE_IDENTIFIER,
      },
      OneUptimeDate.getSecondsInDays(expiresInDays),
    );

    CookieUtil.setCookie(
      data.expressResponse,
      CookieUtil.getStatusPageMasterPasswordKey(data.statusPageId),
      token,
      {
        maxAge: OneUptimeDate.getMillisecondsInDays(expiresInDays),
        httpOnly: true,
      },
    );
  }

  @CaptureSpan()
  public static setCookie(
    res: ExpressResponse,
    name: string | CookieName,
    value: string,
    options: CookieOptions,
  ): void {
    const cookieOptions: CookieOptions = {
      path: "/",
      sameSite: "lax",
      ...options,
    };

    res.cookie(name, value, cookieOptions);
  }

  // get cookie with express request

  @CaptureSpan()
  public static getCookieFromExpressRequest(
    req: ExpressRequest,
    name: string,
  ): string | undefined {
    return req.cookies[name];
  }

  @CaptureSpan()
  public static getRefreshTokenFromExpressRequest(
    req: ExpressRequest,
    id?: ObjectID,
  ): string | undefined {
    return CookieUtil.getCookieFromExpressRequest(
      req,
      CookieUtil.getRefreshTokenKey(id),
    );
  }

  // delete cookie with express response

  @CaptureSpan()
  public static removeCookie(res: ExpressResponse, name: string): void {
    res.clearCookie(name, {
      path: "/",
      sameSite: "lax",
    });
  }

  @CaptureSpan()
  public static removeStatusPageMasterPasswordCookie(
    res: ExpressResponse,
    statusPageId: ObjectID,
  ): void {
    CookieUtil.removeCookie(
      res,
      CookieUtil.getStatusPageMasterPasswordKey(statusPageId),
    );
  }

  @CaptureSpan()
  public static setDashboardMasterPasswordCookie(data: {
    expressResponse: ExpressResponse;
    dashboardId: ObjectID;
  }): void {
    const expiresInDays: PositiveNumber = new PositiveNumber(
      DASHBOARD_MASTER_PASSWORD_COOKIE_MAX_AGE_IN_DAYS,
    );

    const token: string = JSONWebToken.signJsonPayload(
      {
        dashboardId: data.dashboardId.toString(),
        type: DASHBOARD_MASTER_PASSWORD_COOKIE_IDENTIFIER,
      },
      OneUptimeDate.getSecondsInDays(expiresInDays),
    );

    CookieUtil.setCookie(
      data.expressResponse,
      CookieUtil.getDashboardMasterPasswordKey(data.dashboardId),
      token,
      {
        maxAge: OneUptimeDate.getMillisecondsInDays(expiresInDays),
        httpOnly: true,
      },
    );
  }

  @CaptureSpan()
  public static removeDashboardMasterPasswordCookie(
    res: ExpressResponse,
    dashboardId: ObjectID,
  ): void {
    CookieUtil.removeCookie(
      res,
      CookieUtil.getDashboardMasterPasswordKey(dashboardId),
    );
  }

  @CaptureSpan()
  public static getDashboardMasterPasswordKey(id: ObjectID): string {
    return `${CookieName.DashboardMasterPassword}-${id.toString()}`;
  }

  // get all cookies with express request
  @CaptureSpan()
  public static getAllCookies(req: ExpressRequest): Dictionary<string> {
    return req.cookies || {};
  }

  @CaptureSpan()
  public static getUserTokenKey(id?: ObjectID): string {
    if (!id) {
      return CookieName.Token;
    }

    return `${CookieName.Token}-${id.toString()}`;
  }

  @CaptureSpan()
  public static getRefreshTokenKey(id?: ObjectID): string {
    if (!id) {
      return CookieName.RefreshToken;
    }

    return `${CookieName.RefreshToken}-${id.toString()}`;
  }

  @CaptureSpan()
  public static getStatusPageMasterPasswordKey(id: ObjectID): string {
    return `${CookieName.StatusPageMasterPassword}-${id.toString()}`;
  }

  @CaptureSpan()
  public static getUserSSOKey(id: ObjectID): string {
    return `${this.getSSOKey()}${id.toString()}`;
  }

  @CaptureSpan()
  public static getSSOKey(): string {
    return `sso-`;
  }

  // delete all cookies.
  @CaptureSpan()
  public static removeAllCookies(
    req: ExpressRequest,
    res: ExpressResponse,
  ): void {
    const cookies: Dictionary<string> = this.getAllCookies(req);
    for (const key in cookies) {
      this.removeCookie(res, key);
    }

    // Always attempt to remove refresh token cookie even if not parsed.
    this.removeCookie(res, this.getRefreshTokenKey());
  }
}
