import Sequelize = require("sequelize");

// https://github.com/Hodor9898/sequelize-query-builder/blob/master/index.ts

const Op = Sequelize.Op;

export enum CONNECTING_OPERATORS {
  OR = "OR",
  AND = "AND",
}

export enum OPERATORS {
  EQ = "EQ",
  NOT = "NOT",
  IN = "IN",
  NOTIN = "NOTIN",
  BETWEEN = "BETWEEN",
  NOTBETWEEN = "NOTBETWEEN",
  LT = "LT",
  LTE = "LTE",
  GT = "GT",
  GTE = "GTE",
  NULL = "NULL",
  LIKE = "LIKE",
  NOTLIKE = "NOTLIKE",
}

export enum FUNCTIONS {
  INCLUDES = "INCLUDES",
}

const SEQUELIZE_OP_MAP: {
  [key: string]: any;
} = {
  [CONNECTING_OPERATORS.OR]: Op.or,
  [CONNECTING_OPERATORS.AND]: Op.and,
  [OPERATORS.EQ]: Op.eq,
  [OPERATORS.NOT]: Op.not,
  [OPERATORS.IN]: Op.in,
  [OPERATORS.NOTIN]: Op.notIn,
  [OPERATORS.BETWEEN]: Op.between,
  [OPERATORS.NOTBETWEEN]: Op.notBetween,
  [OPERATORS.LT]: Op.lt,
  [OPERATORS.LTE]: Op.lte,
  [OPERATORS.GT]: Op.gt,
  [OPERATORS.GTE]: Op.gte,
  [OPERATORS.NULL]: Op.is,
  [OPERATORS.LIKE]: Op.like,
  [OPERATORS.NOTLIKE]: Op.notLike,
};

const SEQUELIZE_FN_MAP: {
  [key: string]: any;
} = {
  [FUNCTIONS.INCLUDES]: (field: string, val: string) =>
    Sequelize.where(
      Sequelize.fn(
        "FIND_IN_SET",
        Sequelize.literal(`'${val}'`),
        Sequelize.col(field),
      ),
      SEQUELIZE_OP_MAP[OPERATORS.GT],
      0,
    ),
};

const OPERATOR_VALUE_TRANSFORMER: {
  [key: string]: any;
} = {
  [OPERATORS.LIKE]: (val: string) => `%${val}%`,
  [OPERATORS.NOTLIKE]: (val: string) => `%${val}%`,
  [OPERATORS.IN]: (val: string) => val.split("-."),
  [OPERATORS.BETWEEN]: (val: string) => val,
};

export type FilterObject = {
  operator: string;
  field: string;
  value: any;
};

export class SequelizeParseFilter {
  static parseFilter(filterRoot: Partial<FilterObject>): any {
    if (filterRoot === null) {
      return null;
    }

    const { operator = null, field = null, value = null } = filterRoot;

    if (operator === null) {
      return null;
    }

    const _op: any =
      CONNECTING_OPERATORS[operator as keyof typeof CONNECTING_OPERATORS] ||
      OPERATORS[operator as keyof typeof OPERATORS] ||
      FUNCTIONS[operator as keyof typeof FUNCTIONS];

    if (Object.values(CONNECTING_OPERATORS).includes(_op)) {
      return {
        [SEQUELIZE_OP_MAP[operator]]: value.map((val: any) => ({
          ...SequelizeParseFilter.parseFilter(val),
        })),
      };
    }

    if (Object.values(FUNCTIONS).includes(_op) && field !== null) {
      return [SEQUELIZE_FN_MAP[_op](field, value)];
    }

    if (Object.values(OPERATORS).includes(_op) && field !== null) {
      return {
        [field]: {
          [SEQUELIZE_OP_MAP[operator]]: OPERATOR_VALUE_TRANSFORMER[operator]
            ? OPERATOR_VALUE_TRANSFORMER[operator](value)
            : value,
        },
      };
    }

    throw new Error(`Filter operator ${operator} is invalid!`);
  }
}
