const config = require("config");
const { pki, random } = require("node-forge");
const crypto = require("crypto");

class Crypto {
  #dayjs;
  #dayjs_format;
  #redis;

  constructor() {
    this.#dayjs = require("dayjs");
    this.#dayjs_format = config.get("crypto.private_key.date_format");
    let Redis = require("ioredis");
    this.#redis = new Redis({
      host: config.get("redis.host"),
    });
  }

  private_key_get_current() {
    let seed_date = this.#dayjs().format(this.#dayjs_format);
    return this.#private_key_get(seed_date);
  }

  private_key_get_last() {
    let seed_date = this.#dayjs()
      .subtract(1, "month")
      .format(this.#dayjs_format);
    return this.#private_key_get(seed_date);
  }

  async #private_key_get(seed_date) {
    let private_key = {};

    let cache_key = "private_keys" + seed_date;

    // Check if the private key already exists in cache
    let cached_key = await this.#redis.get(cache_key);
    if (cached_key === null) {
      let seed = config.get("crypto.private_key.seed") + seed_date;

      // Feed the seed into a pseudorandom number generator
      let prng_instance = random.createInstance();
      prng_instance.seedFileSync = () => seed;

      let keypair = pki.rsa.generateKeyPair({
        bits: 4096,
        prng: prng_instance,
        workers: -1,
      });

      private_key = {
        n: keypair.privateKey.n.toString(),
        e: keypair.privateKey.e.toString(),
        d: keypair.privateKey.d.toString(),
        p: keypair.privateKey.p.toString(),
        q: keypair.privateKey.q.toString(),
        dmp1: keypair.privateKey.dP.toString(),
        dmq1: keypair.privateKey.dQ.toString(),
        coeff: keypair.privateKey.qInv.toString(),
      };

      // Store the key in cache
      await this.#redis.set(cache_key, JSON.stringify(private_key));
    } else {
      private_key = JSON.parse(await this.#redis.get(cache_key));
    }

    return private_key;
  }

  /**
   * Creates an hmac hash for purchase data so we can check it later
   */
  createIntegrityHash(
    order_id,
    amount,
    unit_size,
    price_per_unit,
    public_key_n,
    public_key_e
  ) {
    let data_to_hash = JSON.stringify({
      order_id: parseInt(order_id),
      amount: parseInt(amount),
      unit_size: parseInt(unit_size),
      price_per_unit: parseFloat(price_per_unit),
      public_key_n: public_key_n,
      public_key_e: public_key_e,
    });
    let forge = require("node-forge");
    let hmac = forge.hmac.create();
    hmac.start("sha256", config.get("crypto.hmac_integrity_seed"));
    hmac.update(data_to_hash);
    return hmac.digest().toHex();
  }

  /**
   * Validates an hmac hash for purchase data so we can check it later
   */
  validateIntegrityHash(
    user_hash,
    order_id,
    amount,
    unit_size,
    price_per_unit,
    public_key_n,
    public_key_e
  ) {
    let data_to_hash = JSON.stringify({
      order_id: parseInt(order_id),
      amount: parseInt(amount),
      unit_size: parseInt(unit_size),
      price_per_unit: parseFloat(price_per_unit),
      public_key_n: public_key_n,
      public_key_e: public_key_e,
    });
    let forge = require("node-forge");
    let hmac = forge.hmac.create();
    hmac.start("sha256", config.get("crypto.hmac_integrity_seed"));
    hmac.update(data_to_hash);
    let server_hash = hmac.digest().toHex();

    return crypto.timingSafeEqual(
      Buffer.from(server_hash, "utf8"),
      Buffer.from(user_hash, "utf8")
    );
  }
}

module.exports = Crypto;