/**
 * @mergeTarget
 * @module Src/Controllers/Storages
 */

import { LisioUserParsed, StorageInterface } from "@lisio/lisio-profils";
import { ErrorCodes } from "../../../misc/error-codes";

const prefix = location.protocol === "http:" ? "" : "__Host-";

/**
 * Class representing a cookie controller.\
 * This class implements the StorageInterface of private library lisio-profiles.\
 * It aims to centralize the processus of storing cookies.\
 * If something need to store datas in cookies it has to use this class.\
 * To execute his responsability this class does :
 *  * Store pre-formated cookie
 *  * Retrieve pre-formated cookie
 */
class CookieController implements StorageInterface {
  /**
   * Private method to store cookie with a pre-formatting
   * @param {string} name - Name of the cookie
   * @param {string} value - Value of the cookie
   * @param {Date} date - Expiration date of the cookie, by default equals to 1970 to delete cookie.
   * @returns Returns nothing
   * @source
   */
  private setCookie(
    name: string,
    value: string,
    date: Date = new Date(1970),
  ): void {
    const now: Date = new Date();
    const numberOfDaysToAdd = 90;
    const dateEnd =
      value === ""
        ? date
        : new Date(now.setDate(now.getDate() + numberOfDaysToAdd));
    document.cookie = `${prefix}${name}=${value}; expires=${dateEnd.toString()}; samesite=None; secure; path=/`;
  }

  /**
   * Private method to retrieve pre-formatted cookie
   * @param {string} name - Name of the cookie
   * @returns Returns desired cookie values
   * @throws If cookie name not exsits. See {@link Src/Utils.ErrorCodes.COOKIE_NOT_EXISTS | ErrorCodes.COOKIE_NOT_EXISTS}
   * @source
   */
  private getCookie(name: string): string {
    const cookies: string[] = document.cookie.split("; ");
    const cookie: string | undefined = cookies.find((cookie) =>
      new RegExp(`${prefix}${name}=`).test(cookie),
    );
    if (cookie == undefined) {
      throw new Error(
        `Code ${ErrorCodes.COOKIE_NOT_EXISTS}. Cookie : ${name} not found`,
      );
    } else {
      return cookie.split("=")[1];
    }
  }

  /**
   * @async
   * Public method to retrieve all stored users
   * @returns Returns a promise containing all users
   * @source
   */
  public async getAllUsers(): Promise<LisioUserParsed[]> {
    const usernames: string[] = await this.getUsernames();
    const users: LisioUserParsed[] = [];
    for (const username of usernames) {
      const user: LisioUserParsed | undefined = await this.getUser(username);
      if (user != undefined) {
        users.push(user);
      }
    }
    return users;
  }
  /**
   * @async
   * Public method to retrieve desired stored user
   * @param {string} username - Username of desired user
   * @returns Returns a promise containing desired user or undefined if no user exists
   * @source
   */
  public async getUser(username: string): Promise<LisioUserParsed | undefined> {
    try {
      return JSON.parse(this.getCookie(`user_${username}`));
    } catch (_) {
      return undefined;
    }
  }
  /**
   * @async
   * Public method to save user
   * @param {LisioUserParsed} user - User to save
   * @returns Returns a promise containing nothing
   * @source
   */
  public async setUser(user: LisioUserParsed): Promise<void> {
    this.setUsername(user.name);
    this.setCookie(
      `user_${user.name}`,
      JSON.stringify(user),
      new Date(2100, 11, 30, 0, 0, 0, 0),
    );
  }
  /**
   * @async
   * Public method to delete user
   * @param {string} username - Username of user to be deleted
   * @returns Returns a promise containing nothing
   * @source
   */
  public async deleteUser(username: string): Promise<void> {
    this.removeUsername(username);
    this.setCookie(`user_${username}`, "");
  }
  /**
   * @async
   * Public method to delete all user
   * @returns Returns a promise containing nothing
   * @source
   */
  public async deleteAllUsers(): Promise<void> {
    const usernames: string[] = await this.getUsernames();
    for (const username of usernames) {
      this.setCookie(`user_${username}`, "");
    }
    this.setCookie("usernames", "[]");
  }

  /**
   * @async
   * Public method to clear all user
   * @returns Returns a promise containing nothing
   * @source
   */
  public async clearUsers(): Promise<void> {
    const users = [...document.cookie.matchAll(/user_(.+)=/g)];
    for (const user of users) {
      this.setCookie(`user_${user[1]}`, "");
    }
    this.setCookie("usernames", "[]");
  }

  /**
   * @async
   * Public method to save current storage version
   * @param {number} storageVersion - Current storage version
   * @returns Returns a promise containing nothing
   * @source
   */
  public async setStorageVersion(storageVersion: number): Promise<void> {
    this.setCookie("storage_version", storageVersion.toString());
  }
  /**
   * @async
   * Public method to retrieve stored storage version
   * @returns Returns a promise containing stored storage version
   * @source
   */
  public async getStorageVersion(): Promise<number> {
    try {
      return Number.parseInt(this.getCookie("storage_version"));
    } catch (err) {
      return -1;
    }
  }

  /**
   * @async
   * Public method to delete all cookies
   * @returns Returns a promise containing nothing
   * @source
   */
  public async clearAll(): Promise<void> {
    const cookies: string[] = document.cookie.split("; ");
    for (const cookie of cookies) {
      const cookieName: string = cookie.split("=")[0];
      this.setCookie(cookieName, "");
    }
  }

  /**
   * @async
   * Public method to retrieve stored popin state
   * @returns Returns a promise containing stored popin state
   * @source
   */
  public async getIsPopinHidden(): Promise<boolean> {
    try {
      return this.getCookie("is_popin_hidden") == "true";
    } catch (err) {
      return true;
    }
  }
  /**
   * @async
   * Public method to save popin state (hidden or not)
   * @param {boolean} value - If popin is hidden or not
   * @returns Returns a promise containing nothing
   * @source
   */
  public async setIsPopinHidden(value: boolean): Promise<void> {
    this.setCookie("is_popin_hidden", JSON.stringify(value));
  }

  /**
   * @async
   * Public method to retrieve stored process state
   * @returns Returns a promise containing stored process state
   * @source
   */
  public async getIsProcessEnd(): Promise<boolean> {
    try {
      return this.getCookie("is_proccess_end") === "true";
    } catch (err) {
      return true;
    }
  }
  /**
   * @async
   * Public method to save process state (ended or not)
   * @param {boolean} value - If process is ended or not
   * @returns Returns a promise containing nothing
   * @source
   */
  public async setIsProcessEnd(value: boolean): Promise<void> {
    this.setCookie("is_proccess_end", JSON.stringify(value));
  }

  /**
   * @async
   * Public method to remove a desired username
   * @param {string} username - Desired username to be removed
   * @returns Returns a promise containing nothing
   * @source
   */
  public async removeUsername(username: string): Promise<void> {
    const currentUsernames: string[] = await this.getUsernames();
    const indexOfUsernameToRemove: number = currentUsernames.indexOf(username);
    if(indexOfUsernameToRemove>=0){
      currentUsernames.splice(indexOfUsernameToRemove, 1);
      this.setCookie(
        "usernames",
        JSON.stringify(currentUsernames),
        new Date(2100, 11, 30, 0, 0, 0, 0),
      );
    }
  }
  /**
   * @async
   * Public method to save a new username
   * @param {string} username - Username to save
   * @returns Returns a promise containing nothing
   * @source
   */
  public async setUsername(username: string): Promise<void> {
    const savedUsernames: string[] = await this.getUsernames();
    if (!savedUsernames.some((savedUsername) => username === savedUsername)) {
      savedUsernames.push(username);
      this.setCookie(
        "usernames",
        JSON.stringify(savedUsernames),
        new Date(2100, 11, 30, 0, 0, 0, 0),
      );
    }
  }

  /**
   * @async
   * Public method to retrieve all usernames
   * @returns Returns a promise containing all usernames
   * @source
   */
  public async getUsernames(): Promise<string[]> {
    const usernames: string[] = [];
    try {
      const currentUsers: string = this.getCookie("usernames");
      return usernames.concat(JSON.parse(currentUsers));
    } catch (err) {
      return usernames;
    }
  }

  /**
   * @async
   * Public method to rename a saved user.\
   * Thsi method will change the username in cookie name of current user and replace old username by new username in usernames array
   * @param {string} username - Current username to rename
   * @param {string} newUsername - New username
   * @returns Returns a promise containing nothing
   * @source
   */
  public async renameUser(
    username: string,
    newUsername: string,
  ): Promise<void> {
    const user: LisioUserParsed | undefined = await this.getUser(username);
    if (user != undefined) {
      const savedUsernames: string[] = await this.getUsernames();
      savedUsernames[savedUsernames.indexOf(username)] = newUsername;
      this.setCookie(
        "usernames",
        JSON.stringify(savedUsernames),
        new Date(2100, 11, 30, 0, 0, 0, 0),
      );
      user.name = newUsername;
      this.setCookie(`user_${username}`, "");
      this.setCookie(`user_${newUsername}`, JSON.stringify(user));
    }
  }
}

export { CookieController };
