import { IIPPConfig, IConfig } from "./Config";

export interface IStorageProvider {
  prefix: string;
  suffix: string;

  create: (
    key: string,
    value: string,
    expireDays?: number,
    ignorePrefix?: boolean
  ) => void;
  save: (
    key: string,
    value: string,
    expireDays?: number,
    ignorePrefix?: boolean
  ) => void;
  get: (key: string, defaultValue?: any, ignorePrefix?: boolean) => any;
  remove: (key: string) => void;
}

function getBrowserVersion() {
  const regex = /Chrome\/[0-9.]+/gm;
  const str = navigator ? navigator.userAgent : "";
  let m;
  let version = 0;
  if (!str) return version;

  while ((m = regex.exec(str)) !== null) {
    // This is necessary to avoid infinite loops with zero-width matches
    if (m.index === regex.lastIndex) {
      regex.lastIndex++;
    }
    let result = m[0].split("/");
    version = parseInt(result[1]);
  }
  return version;
}

class LocalStorage implements IStorageProvider {
  public prefix: string = "ipp";
  public suffix: string = "";

  constructor(public config: IConfig) {}

  public create(key: string, value: string): void {
    localStorage.setItem(this.makeKey(key), value);
  }

  // Alias
  public save(key: string, value: string): void {
    return this.create(key, value);
  }

  public get(key: string, defaultValue: any = null): string {
    let val: any = localStorage.getItem(this.makeKey(key));

    if (!val) {
      return defaultValue;
    }

    if (this.isValidJSON(val)) {
      return JSON.parse(val);
    } else {
      return val;
    }
  }

  public remove(key: string): void {
    localStorage.removeItem(this.makeKey(key));
  }

  private makeKey(key: string): string {
    if (this.config.storage_prefix) {
      this.prefix = this.config.storage_prefix;
    }
    if (this.prefix && key.indexOf(this.prefix) !== 0) {
      key = `${this.prefix}_${key}`;
    }

    if (this.suffix) {
      key = `${key}_${this.suffix}`;
    }

    return key;
  }

  private isValidJSON(val: any): boolean {
    try {
      let json: any = JSON.parse(val);
      return true;
    } catch (e) {
      return false;
    }
  }
}

class CookieStorage implements IStorageProvider {
  // public config: IConfig;
  public prefix: string = "ipp";
  public suffix: string = "";

  private _domain: string;

  constructor(public config: IConfig) {
    let domain: string = document.domain;
    if (document.domain.indexOf("ipushpull") > 0) {
      let domainParts: string[] = document.domain.split(".");
      domainParts.splice(0, 1);
      domain = domainParts.join(".");
    }
    this._domain = domain;
  }

  public create(
    key: string,
    value: string,
    expireDays?: number,
    ignorePrefix?: boolean
  ): void {
    let expires: string = "";

    if (expireDays) {
      let date: Date = new Date();
      date.setTime(date.getTime() + expireDays * 24 * 60 * 60 * 1000);
      expires = "; expires=" + date.toUTCString();
    }

    let path: string = "path=/;";
    const newKey: string = ignorePrefix ? key : this.makeKey(key);
    const version = getBrowserVersion();
    const secure = this.isSecure() ? "Secure;" : "";
    const sameSite =
      (version && version < 80) || !secure ? "" : "SameSite=None; ";

    document.cookie = `${newKey}=${value}${expires}; ${path} domain=${this._domain}; ${sameSite}${secure}`;
  }

  public save(
    key: string,
    value: string,
    expireDays?: number,
    ignorePrefix?: boolean
  ): void {
    this.create(key, value, expireDays, ignorePrefix);
  }

  public get(key: string, defaultValue?: any, ignorePrefix?: boolean): string {
    key = ignorePrefix ? key : this.makeKey(key);

    let nameEQ: string = key + "=";
    let ca: string[] = document.cookie.split(";");

    for (let i: number = 0; i < ca.length; i++) {
      let c: string = ca[i];
      while (c.charAt(0) === " ") {
        c = c.substring(1, c.length);
      }

      if (c.indexOf(nameEQ) === 0) {
        let val: string = c.substring(nameEQ.length, c.length);

        if (this.isValidJSON(val)) {
          return JSON.parse(val);
        } else {
          return val;
        }
      }
    }

    return defaultValue;
  }

  public remove(key: string): void {
    this.create(this.makeKey(key), "", -1);
  }

  private isSecure(): boolean {
    return window.location.protocol === "https:";
  }

  private makeKey(key: string): string {
    if (this.config.storage_prefix) {
      this.prefix = this.config.storage_prefix;
    }
    if (this.prefix && key.indexOf(this.prefix) !== 0) {
      key = `${this.prefix}_${key}`;
    }

    if (this.suffix) {
      key = `${key}_${this.suffix}`;
    }

    return key;
  }

  private isValidJSON(val: any): boolean {
    try {
      let json: any = JSON.parse(val);
      return true;
    } catch (e) {
      return false;
    }
  }
}

export interface IStorageService {
  user: IStorageProvider;
  global: IStorageProvider;
  persistent: IStorageProvider;
}

export class StorageService {
  public static $inject: string[] = ["ippConfig"];

  public constructor(ippConfig: IConfig) {
    // User Storage
    let userStorage: IStorageProvider = new LocalStorage(ippConfig);
    userStorage.suffix = "GUEST";

    // Global storage
    let globalStorage: IStorageProvider = new LocalStorage(ippConfig);

    // Persistent storage
    // @todo Should log some warning at least
    let persistentStorage: IStorageProvider =
      typeof navigator !== "undefined" && navigator.cookieEnabled
        ? new CookieStorage(ippConfig)
        : new LocalStorage(ippConfig);

    if (ippConfig.storage_prefix) {
      userStorage.prefix = ippConfig.storage_prefix;
      globalStorage.prefix = ippConfig.storage_prefix;
      persistentStorage.prefix = ippConfig.storage_prefix;
    }

    return {
      user: userStorage,
      global: globalStorage,
      persistent: persistentStorage
    };
  }
}
