const { request } = require("express");

const { Op, where } = require("sequelize");

class FilterService {
  constructor(logger, sequelize) {
    this.logger = logger;
    this.sequelize = sequelize;
    this.pipeline = [];
  }

  addFilter(...fns) {
    fns.forEach((fn) => this.pipeline.push(fn));
  }

  async filter(initial) {
    let temp = initial;
    for (let key in this.pipeline) {
      temp = await this.pipeline[key](temp, this.sequelize);
    }
    if (temp.length === 1) {
      return temp[0];
    } else if (temp.length === 0) {
      throw new Error("Not found");
    }
    return temp;
  }

  static async match(input, sequelize) {
    const { Robot, Feature } = sequelize.models;

    const { FeatureId, priceFrom, priceTo } = input;

    const AndFilters = [
      {
        price: { [Op.between]: [priceFrom || 0, priceTo || 9999999] },
      },
    ];

    FeatureId &&
      AndFilters.push({
        "$features.id$": FeatureId || 0,
      });
    // Add filter only if values are not null
    ["weight", "autonomy", "type", "BrandId"].forEach((key) => {
      input[key] && AndFilters.push({ [key]: input[key] });
    });

    const filter = {
      [Op.and]: AndFilters,
    };

    let matches = await Robot.findAll({
      attributes: ["id"],
      where: filter,
      include: [
        {
          model: Feature,
          as: "features",
        },
      ],
    });

    return matches;
  }

  static async bestKnowledge(robots, sequelize) {
    const { StaffMember, Robot, Feature } = sequelize.models;

    let whereStatement = {
      "$robots.id$": { [Op.in]: robots.map((i) => i.id) },
    }
    if (!robots.length) {
      whereStatement = {};
    }

    const members = await StaffMember.findAll({
      attributes: ["id"],
      where: whereStatement,
      include: [
        {
          model: Robot,
          as: "robots",
          include: [{ model: Feature, as: "features" }],
        },
      ],
    });

    for (let i in members) {
      members[i].dataValues.counter = members[i].dataValues.robots.length;
    }
    const staffMembers = FilterService.sortAndFilter(members, "counter");
    return staffMembers;
  }

  static async bestAvgFeatures(staffMembers, sequelize) {
    const members = staffMembers.map((member) => {
      const robots = member.dataValues.robots;
      const featureArr = robots.map((item) => item.features.length);
      const avg = featureArr.reduce((total, current) => total + current, 0) / featureArr.length;
      member.dataValues.avgFeatures = avg;
      return member;
    });

    staffMembers = FilterService.sortAndFilter(members, "avgFeatures");
    return staffMembers;
  }

  static async bestAvgPrice(staffMembers, sequelize) {
    const members = staffMembers.map((member) => {
      const robots = member.dataValues.robots;
      const featureArr = robots.map((item) => item.price);
      const avg = featureArr.reduce((total, current) => total + current, 0) / featureArr.length;
      member.dataValues.avgPrice = avg;
      return member;
    });

    staffMembers = FilterService.sortAndFilter(members, "avgPrice");
    return staffMembers;
  }

  static async random(staffMembers) {
    let rind = Math.floor(Math.random() * staffMembers.length);
    if (rind === staffMembers.length) rind--;
    return [staffMembers[rind]];
  }

  static sortAndFilter(arr, field) {
    if (!arr.length) return [];

    const staffMembersSorted = arr.sort((a, b) => b.dataValues[field] - a.dataValues[field]);
    const maxValue = staffMembersSorted[0].dataValues[field];
    // kepp members with maxValue only
    const staffMembers = staffMembersSorted.filter(
      (member) => member.dataValues[field] === maxValue
    );
    return staffMembers;
  }
}

module.exports = FilterService;
