import { IPageContent } from './Page/Content';
import * as forge from 'node-forge';

export interface IEncryptionKey {
  name: string;
  passphrase: string;
}

export interface ICryptoService {
  decryptContent: (key: any, data: string) => IPageContent | null;
  encryptContent: (key: IEncryptionKey, data: IPageContent) => string | null;
}

export class Crypto implements ICryptoService {
  public static _instance(): Crypto {
    return new Crypto();
  }

  /**
   * Decrypt page content using encryption key and encrypted string
   *
   * @param key
   * @param data
   * @returns {any}
   */
  public decryptContent(key: any, data: string): IPageContent | null {
    if (!this.libCheck()) {
      return null;
    }

    if (!data) return null;

    let rawData: string = forge.util.decode64(data);
    let iv: string = rawData.substring(0, 16);
    let cleanData: string = rawData.substring(16);

    // cleanData = forge.util.createBuffer(cleanData, 'utf8');
    // iv = forge.util.createBuffer(iv, 'utf8');

    let decipher: any = forge.cipher.createDecipher('AES-CBC', this.hashPassphrase(key.passphrase));
    decipher.start({ iv: forge.util.createBuffer(iv) });
    decipher.update(forge.util.createBuffer(cleanData));
    decipher.finish();

    let decrypted: IPageContent | null;

    try {
      decrypted = JSON.parse(decipher.output.toString());
    } catch (e) {
      decrypted = null;
    }

    return decrypted;
  }

  /**
   * Encrypt raw page content with supplied encryption key
   *
   * @param key
   * @param data
   * @returns {any}
   */
  public encryptContent(key: IEncryptionKey, data: IPageContent): string | null {
    if (!this.libCheck()) {
      return null;
    }

    let readyData: string = JSON.stringify(data); // Stringify JS object data

    let hash: string = this.hashPassphrase(key.passphrase);
    let iv: string = forge.random.getBytesSync(16);

    let cipher: any = forge.cipher.createCipher('AES-CBC', hash);
    cipher.start({ iv: iv });
    cipher.update(forge.util.createBuffer(readyData));
    cipher.finish();

    let encrypted: any = cipher.output;

    let buffer: any = forge.util.createBuffer();

    buffer.putBytes(iv);
    buffer.putBytes(encrypted.bytes());

    let output: any = buffer.getBytes();

    return forge.util.encode64(output);
  }

  /**
   * Use forge library"s util to hash passphrase
   *
   * @param passphrase
   * @returns {any}
   */
  private hashPassphrase(passphrase: string): string {
    let md: any = forge.md.sha256.create();
    md.update(passphrase);
    return md.digest().bytes();
  }

  private libCheck(): boolean {
    // Check if forge library is loaded. We do this because the library is huge and we dont want to package it together
    if (typeof forge === 'undefined') {
      console.error(
        '[iPushPull]',
        'If you want to use encryption make sure you include forge library in your header or use ng-ipushpull-standalone.min.js',
      );
    }

    return typeof forge !== 'undefined';
  }
}
