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

import { LisioUserParsed, StorageInterface } from "@lisio/lisio-profils";

/**
 * Class representing a local storage controller.\
 * This class implements the StorageInterface of private library lisio-profiles.\
 * It aims to centralize the processus of storing items in local storage.\
 * If something need to store datas in local storage it has to use this class.\
 * To execute his responsability this class does :
 *  * Store items in local storage
 *  * Retrieve items in local storage
 */
class LocalStorageController implements StorageInterface {
  /**
   * @async
   * Public method to retrieve all stored users
   * @returns Returns a promise containing all users
   * @source
   */
  public async getAllUsers(): Promise<LisioUserParsed[]> {
    const users: LisioUserParsed[] = [];
    const usernames: string[] = await this.getUsernames();
    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> {
    const user = this.getItem(`user_${username}`);
    if (user == undefined) {
      return undefined;
    } else {
      return JSON.parse(user);
    }
  }
  /**
   * @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.setItem(`user_${user.name}`, JSON.stringify(user));
  }
  /**
   * @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);
    localStorage.removeItem(`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) {
      localStorage.removeItem(`user_${username}`);
    }
    localStorage.removeItem("usernames");
  }

  /**
   * @async
   * Public method to clear all user
   * @returns Returns a promise containing nothing
   * @source
   */
  public async clearUsers(): Promise<void> {
    for (const entry of Object.keys(localStorage)) {
      const match = entry.match(/user_(.+)/);
      if (match != undefined) {
        localStorage.removeItem(`user_${match[1]}`);
      }
    }
    localStorage.removeItem("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.setItem("storage_version", storageVersion.toString());
  }
  /**
   * @async
   * Public method to retrieve stored storage version
   * @returns Returns a promise containing stored storage version
   * @throws {number} If no storage version stored
   * @source
   */
  public async getStorageVersion(): Promise<number> {
    const storageVersion =
      this.getItem("storage_version");
    if (storageVersion == undefined) {
      return -1;
    } else {
      return Number.parseInt(storageVersion);
    }
  }

  /**
   * @async
   * Public method to delete all cookies
   * @returns Returns a promise containing nothing
   * @source
   */
  public async clearAll(): Promise<void> {
    localStorage.clear();
  }

  /**
   * @async
   * Public method to retrieve stored popin state
   * @returns Returns a promise containing stored popin state
   * @throws {boolean} If no popin state stored
   * @source
   */
  public async getIsPopinHidden(): Promise<boolean> {
    return Boolean(this.getItem("is_popin_hidden"));
  }
  /**
   * @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.setItem("is_popin_hidden", String(value));
  }

  /**
   * @async
   * Public method to retrieve stored process state
   * @returns Returns a promise containing stored process state
   * @throws {boolean} If no process state stored
   * @source
   */
  public async getIsProcessEnd(): Promise<boolean> {
    return this.getItem("is_process_end") === "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.setItem("is_process_end", String(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);
      if(currentUsernames.length == 0){
        localStorage.removeItem("usernames");
      }else{
        this.setItem("usernames", JSON.stringify(currentUsernames));
      }
    }
  }
  /**
   * @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.setItem("usernames", JSON.stringify(savedUsernames));
    }
  }

  /**
   * @async
   * Public method to retrieve all usernames
   * @returns Returns a promise containing all usernames
   * @throws {string[]} If not usernames stored
   * @source
   */
  public async getUsernames(): Promise<string[]> {
    const usernames: string[] = [];
    try {
      const currentUsers = this.getItem("usernames");
      if (currentUsers == undefined) {
        throw new Error("no 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.setItem("usernames", JSON.stringify(savedUsernames));

      user.name = newUsername;

      localStorage.removeItem(`user_${username}`);
      this.setItem(`user_${newUsername}`, JSON.stringify(user));
    }
  }

  private setItem(name: string, value: string){
    const item = localStorage.getItem(name);
    if(item === value){
      return;
    }
    const now = new Date(Date.now());
    localStorage.setItem("lisio-expires-at", (now.getTime() + (90 * 8.64e7)).toString()) 
    localStorage.setItem(name, value);
  }

  private getItem(name: string){
    const expiresAt = localStorage.getItem("lisio-expires-at");
    if(expiresAt == undefined || Date.now() < new Date(parseInt(expiresAt)).getTime()){
      return localStorage.getItem(name);
    }else{
      localStorage.clear();
      return undefined;
    }
  }
}

export { LocalStorageController };
