import { get, head, last, isArray, isFunction } from 'lodash';
import { Opera } from './dbOpera';
/**
 *
 * @param builder model的createQueryBuilder,只有到最后要查数据的时候才是异步的
 * @param {object} query 查询条件
 * @param {object} operas 指定查询方式
 * @param {any} model 存在的情况,default会调用columnIsString 判断是否是字符串字段的函数或其他任何形式的参数
 */
export const completeBuilderCondition = (builder, query = {}, operas = {}, model?) => {
  // 组织查询条件
  if (!query) return;
  const searchColumns = Object.keys(query);
  if (searchColumns.length <= 0) return;
  for (let i = 0; i < searchColumns.length; i++) {
    const key = searchColumns[i];
    const value = query[key];
    if (!value) continue;
    /**该字段的查询方式 */
    const opera = get(operas, key);
    /**builder的使用函数名 */
    let method = 'where';
    if (i === 0) method = 'where';
    else method = 'andWhere';
    let str;
    let params;
    // 需要给变量位置重命名,否则多个条件叠加后,都会使用第一个参数
    const valueStr = `value${i}`;
    let valueArr = [];
    const strArr = [];
    switch (opera) {
      case Opera.Between:
        str = `"${key}" Between :${valueStr}_1 AND :${valueStr}_2`;
        params = { [`${valueStr}_1`]: head(value), [`${valueStr}_2`]: last(value) };
        break;
      case Opera.Not:
        str = `"${key}" != :${valueStr}`;
        params = { [`${valueStr}`]: value };
        break;
      case Opera.Like:
        str = `"${key}" Like :${valueStr}`;
        params = { [`${valueStr}`]: `%${value}%` };
        break;
      case Opera.LikeLeft:
        str = `"${key}" Like :${valueStr}`;
        params = { [`${valueStr}`]: `%${value}` };
        break;
      case Opera.LikeRight:
        str = `"${key}" Like :${valueStr}`;
        params = { [`${valueStr}`]: `${value}%` };
        break;
      case Opera.ILike:
        str = `"${key}" Not Like :${valueStr}`;
        params = { [`${valueStr}`]: `%${value}%` };
        break;
      case Opera.ILikeLeft:
        str = `"${key}" Not Like :${valueStr}`;
        params = { [`${valueStr}`]: `%${value}` };
        break;
      case Opera.ILikeRight:
        str = `"${key}" Not Like :${valueStr}`;
        params = { [`${valueStr}`]: `${value}%` };
        break;
      case Opera.LessThan:
        str = `"${key}" < :${valueStr}`;
        params = { [`${valueStr}`]: value };
        break;
      case Opera.LessThanOrEqual:
        str = `"${key}" <= :${valueStr}`;
        params = { [`${valueStr}`]: value };
        break;
      case Opera.MoreThan:
        str = `"${key}" > :${valueStr}`;
        params = { [`${valueStr}`]: value };
        break;
      case Opera.MoreThanOrEqual:
        str = `"${key}" >= :${valueStr}`;
        params = { [`${valueStr}`]: value };
        break;
      case Opera.In:
        if (!isArray(value)) str = `"${key}" IN (:${valueStr})`;
        else str = `"${key}" IN (:...${valueStr})`;
        params = { [`${valueStr}`]: value };
        break;
      case Opera.IsNull:
        str = `"${key}" IS NULL`;
        break;
      case Opera.IsNotNull:
        str = `"${key}" IS NOT NULL`;
        params = { [`${valueStr}`]: value };
        break;
      case Opera.Json:
        params = {};
        if (isArray(value)) valueArr = value;
        else valueArr = [value];
        for (let vi = 0; vi < valueArr.length; vi++) {
          const v = valueArr[vi];
          const mvalKey = `${valueStr}${vi}`;
          const mstr = `JSONB_EXISTS("${key}", :${mvalKey})`;
          strArr.push(mstr);
          params[mvalKey] = v;
        }
        str = `(${strArr.join(' OR ')})`;
        break;
      case Opera.JsonObject:
        const jokeys = key.split('.');
        const jorootCol = head(jokeys);
        const jolastKey = last(jokeys);
        const jopath = jokeys.filter(f => f !== jorootCol && f !== jolastKey);
        str = `"${jorootCol}" `;
        for (const jok of jopath) {
          str = `${str} -> ${jok}`;
        }
        str = `${str} ->> '${jolastKey}' = :${valueStr}`;
        params = { [`${valueStr}`]: value };
        break;
      case Opera.JsonArrayObject:
        /**
         * 1.分割key,过来的属性默认以 x.y.z... 形式
         * x:根子段;后面,数组中依次往下的属性名
         */
        const keys = key.split('.');
        let numberValue;
        if (isFinite(parseInt(value))) numberValue = parseInt(value);
        let rootCol = head(keys);
        let lastKey = last(keys);
        let path = keys.filter(f => f !== rootCol && f !== lastKey);
        const getObject = (path, lastKey, value) => {
          let obj = {};
          let mid = obj;
          for (const k of path) {
            mid[k] = {};
            mid = mid[k];
          }
          mid[lastKey] = value;
          return obj;
        };
        const obj = getObject(path, lastKey, value);
        let newVal = JSON.stringify([obj]);
        str = `"${rootCol}" @> :${valueStr}`;
        params = { [`${valueStr}`]: newVal };
        if (numberValue) {
          const numObj = getObject(path, lastKey, numberValue);
          let numVal = JSON.stringify([numObj]);
          const valueStrNum = `${valueStr}Num`;
          str = `(${str} OR "${rootCol}" @> :${valueStrNum})`;
          params[valueStrNum] = numVal;
        }
        break;
      case Opera.Equal:
        str = `"${key}" = :${valueStr}`;
        params = { [`${valueStr}`]: value };
        break;
      default:
        let isString = false;
        if (model) isString = columnIsString(key, model);
        if (isString) {
          // 字符串默认使用模糊查询
          str = `"${key}" Like :${valueStr}`;
          params = { [`${valueStr}`]: `%${value}%` };
        } else {
          str = `"${key}" = :${valueStr}`;
          params = { [`${valueStr}`]: value };
        }
        break;
    }
    if (!str) continue;
    builder[method](str, params);
  }
};

const columnIsString = (columnName: string, model?) => {
  if (model) return false;
  const columns = model.metadata.columns;
  const colSetting = columns.find(f => get(f, 'propertyName') === columnName);
  if (!colSetting) return false;
  const type = get(colSetting, 'type');
  if (!type) return false;
  return type === 'character varying' || type === 'text';
};
