import { difference } from 'lodash';
import moment from 'moment';
import { Lexer } from 'lib/LogicEvaluation/Lexer';
import Parser from 'lib/LogicEvaluation/Parser';
import QueryParser from 'lib/LogicEvaluation/QueryParser';
import { IFilterRole, IModel } from 'interfaces';

const numberRoleQueryBuilder = (
  role: IFilterRole,
  comparatorColumn: string
) => {
  switch (role.comparator) {
    case 'equals':
    case 'equal':
    default:
      return (builder) => {
        builder.where(comparatorColumn, '=', role.value);
      };
    case 'not_equals':
    case 'not_equal':
      return (builder) => {
        builder.whereNot(comparatorColumn, role.value);
      };
    case 'bigger_than':
    case 'bigger':
      return (builder) => {
        builder.where(comparatorColumn, '>', role.value);
      };
    case 'bigger_or_equals':
      return (builder) => {
        builder.where(comparatorColumn, '>=', role.value);
      };
    case 'smaller_than':
    case 'smaller':
      return (builder) => {
        builder.where(comparatorColumn, '<', role.value);
      };
    case 'smaller_or_equals':
      return (builder) => {
        builder.where(comparatorColumn, '<=', role.value);
      };
  }
};

const textRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
  switch (role.comparator) {
    case 'equals':
    case 'is':
    default:
      return (builder) => {
        builder.where(comparatorColumn, role.value);
      };
    case 'not_equal':
    case 'not_equals':
    case 'is_not':
      return (builder) => {
        builder.whereNot(comparatorColumn, role.value);
      };
    case 'contain':
    case 'contains':
      return (builder) => {
        builder.where(comparatorColumn, 'LIKE', `%${role.value}%`);
      };
    case 'not_contain':
    case 'not_contains':
      return (builder) => {
        builder.whereNot(comparatorColumn, 'LIKE', `%${role.value}%`);
      };
  }
};

const dateQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
  switch (role.comparator) {
    case 'after':
    case 'before':
      return (builder) => {
        const comparator = role.comparator === 'before' ? '<' : '>';
        const hasTimeFormat = moment(
          role.value,
          'YYYY-MM-DD HH:MM',
          true
        ).isValid();
        const targetDate = moment(role.value);
        const dateFormat = 'YYYY-MM-DD HH:MM:SS';

        if (!hasTimeFormat) {
          if (role.comparator === 'before') {
            targetDate.startOf('day');
          } else {
            targetDate.endOf('day');
          }
        }
        const comparatorValue = targetDate.format(dateFormat);
        builder.where(comparatorColumn, comparator, comparatorValue);
      };
    case 'in':
      return (builder) => {
        const hasTimeFormat = moment(
          role.value,
          'YYYY-MM-DD HH:MM',
          true
        ).isValid();
        const dateFormat = 'YYYY-MM-DD HH:MM:SS';

        if (hasTimeFormat) {
          const targetDateTime = moment(role.value).format(dateFormat);
          builder.where(comparatorColumn, '=', targetDateTime);
        } else {
          const startDate = moment(role.value).startOf('day');
          const endDate = moment(role.value).endOf('day');

          builder.where(comparatorColumn, '>=', startDate.format(dateFormat));
          builder.where(comparatorColumn, '<=', endDate.format(dateFormat));
        }
      };
  }
};

/**
 * Get field column metadata and its relation with other tables.
 * @param {String} tableName - Table name of target column.
 * @param {String} fieldKey - Target column key that stored in resource field.
 */
export function getRoleFieldColumn(model: IModel, fieldKey: string) {
  const tableFields = model.fields;
  return tableFields[fieldKey] ? tableFields[fieldKey] : null;
}

/**
 * Builds roles queries.
 * @param {IModel} model -
 * @param {Object} role -
 */
export function buildRoleQuery(model: IModel, role: IFilterRole) {
  const fieldRelation = getRoleFieldColumn(model, role.fieldKey);
  const comparatorColumn =
    fieldRelation.relationColumn ||
    `${model.tableName}.${fieldRelation.column}`;

  if (typeof fieldRelation.query !== 'undefined') {
    return (builder) => {
      fieldRelation.query(builder, role);
    };
  }
  switch (fieldRelation.columnType) {
    case 'number':
      return numberRoleQueryBuilder(role, comparatorColumn);
    case 'date':
      return dateQueryBuilder(role, comparatorColumn);
    case 'text':
    case 'varchar':
    default:
      return textRoleQueryBuilder(role, comparatorColumn);
  }
}

/**
 * Extract relation table name from relation.
 * @param {String} column -
 * @return {String} - join relation table.
 */
export const getTableFromRelationColumn = (column: string) => {
  const splitedColumn = column.split('.');
  return splitedColumn.length > 0 ? splitedColumn[0] : '';
};

/**
 * Builds view roles join queries.
 * @param {String} tableName - Table name.
 * @param {Array} roles - Roles.
 */
export function buildFilterRolesJoins(model: IModel, roles: IFilterRole[]) {
  return (builder) => {
    roles.forEach((role) => {
      const fieldColumn = getRoleFieldColumn(model, role.fieldKey);

      if (fieldColumn.relation) {
        const joinTable = getTableFromRelationColumn(fieldColumn.relation);

        builder.join(
          joinTable,
          `${model.tableName}.${fieldColumn.column}`,
          '=',
          fieldColumn.relation
        );
      }
    });
  };
}

export function buildSortColumnJoin(model: IModel, sortColumnKey: string) {
  return (builder) => {
    const fieldColumn = getRoleFieldColumn(model, sortColumnKey);

    if (fieldColumn.relation) {
      const joinTable = getTableFromRelationColumn(fieldColumn.relation);
      builder.join(
        joinTable,
        `${model.tableName}.${fieldColumn.column}`,
        '=',
        fieldColumn.relation
      );
    }
  };
}

/**
 * Builds database query from stored view roles.
 *
 * @param {Array} roles -
 * @return {Function}
 */
export function buildFilterRolesQuery(
  model: IModel,
  roles: IFilterRole[],
  logicExpression: string = ''
) {
  const rolesIndexSet = {};

  roles.forEach((role) => {
    rolesIndexSet[role.index] = buildRoleQuery(model, role);
  });
  // Lexer for logic expression.
  const lexer = new Lexer(logicExpression);
  const tokens = lexer.getTokens();

  // Parse the logic expression.
  const parser = new Parser(tokens);
  const parsedTree = parser.parse();

  const queryParser = new QueryParser(parsedTree, rolesIndexSet);
  return queryParser.parse();
}

/**
 * Builds filter query for query builder.
 * @param {String} tableName -
 * @param {Array} roles -
 * @param {String} logicExpression -
 */
export const buildFilterQuery = (
  model: IModel,
  roles: IFilterRole[],
  logicExpression: string
) => {
  return (builder) => {
    buildFilterRolesQuery(model, roles, logicExpression)(builder);
  };
};

/**
 * Mapes the view roles to view conditionals.
 * @param {Array} viewRoles -
 * @return {Array}
 */
export function mapViewRolesToConditionals(viewRoles) {
  return viewRoles.map((viewRole) => ({
    comparator: viewRole.comparator,
    value: viewRole.value,
    index: viewRole.index,

    columnKey: viewRole.field.key,
    slug: viewRole.field.slug,
  }));
}

export function mapFilterRolesToDynamicFilter(roles) {
  return roles.map((role) => ({
    ...role,
    columnKey: role.fieldKey,
  }));
}

/**
 * Builds sort column query.
 * @param {String} tableName -
 * @param {String} columnKey -
 * @param {String} sortDirection -
 */
export function buildSortColumnQuery(
  model: IModel,
  columnKey: string,
  sortDirection: string
) {
  const fieldRelation = getRoleFieldColumn(model, columnKey);
  const sortColumn =
    fieldRelation.relation || `${model.tableName}.${fieldRelation.column}`;

  return (builder) => {
    builder.orderBy(sortColumn, sortDirection);
    buildSortColumnJoin(model, columnKey)(builder);
  };
}

export function validateFilterLogicExpression(
  logicExpression: string,
  indexes
) {
  const logicExpIndexes = logicExpression.match(/\d+/g) || [];
  const diff = difference(logicExpIndexes.map(Number), indexes);

  return diff.length > 0 ? false : true;
}

export function validateRolesLogicExpression(
  logicExpression: string,
  roles: IFilterRole[]
) {
  return validateFilterLogicExpression(
    logicExpression,
    roles.map((r) => r.index)
  );
}

export function validateFieldKeyExistance(model: any, fieldKey: string) {
  return model?.fields?.[fieldKey] || false;
}

export function validateFilterRolesFieldsExistance(
  model,
  filterRoles: IFilterRole[]
) {
  return filterRoles.filter((filterRole: IFilterRole) => {
    return !validateFieldKeyExistance(model, filterRole.fieldKey);
  });
}

/**
 * Retrieve model fields keys.
 * @param {IModel} Model
 * @return {string[]}
 */
export function getModelFieldsKeys(Model: IModel) {
  const fields = Object.keys(Model.fields);

  return fields.sort((a, b) => {
    if (a < b) {
      return -1;
    }
    if (a > b) {
      return 1;
    }
    return 0;
  });
}

export function getModelFields(Model: IModel) {
  const fieldsKey = this.getModelFieldsKeys(Model);

  return fieldsKey.map((fieldKey) => {
    const field = Model.fields[fieldKey];
    return {
      ...field,
      key: fieldKey,
    };
  });
}
