package com.ruoyi.common.utils;

import cn.hutool.core.bean.BeanDesc;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.PropDesc;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import com.mongodb.client.result.DeleteResult;
import com.ruoyi.common.core.domain.Page;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

@Slf4j
public class MongoUtil {

  @Getter
  @Setter
  private static MongoTemplate template;

  static {
    try {
      template = SpringUtils.getBean(MongoTemplate.class);
    } catch (Exception e) {
      log.error("MongoUtil init error:{}", e.getMessage());
    }
  }

  private MongoUtil() {
  }

  /**
   * 多个条件组合
   *
   * @param criterias
   * @return
   */
  public static Criteria and(Criteria... criterias) {
    Criteria c = new Criteria();
    if (ArrayUtil.isNotEmpty(criterias)) {
      c = c.andOperator(criterias);
    }
    return c;
  }

  public static Criteria or(Criteria... criterias) {
    Criteria c = new Criteria();
    if (ArrayUtil.isNotEmpty(criterias)) {
      c = c.orOperator(criterias);
    }
    return c;
  }


  /**
   * 非空字段完全匹配(is，eq)<br/>
   * 字段名匹配字段值
   *
   * @param obj
   * @return
   */
  public static Criteria eq(Object obj) {
    Criteria c = new Criteria();
    if (ObjUtil.isNull(obj)) {
      return c;
    }
    for (PropDesc pd : BeanUtil.getBeanDesc(obj.getClass()).getProps()) {
      if (pd.getGetter() == null) {
        continue;
      }
      Object val = pd.getValue(obj);
      if (ObjUtil.isNull(val)) {
        continue;
      }
      if (pd.getFieldClass() == String.class) {
        String str = (String) val;
        if (StrUtil.isBlank(str)) {
          continue;
        }
      }
      c = c.and(pd.getFieldName()).is(val);
    }
    return c;
  }


  /**
   * age$gt
   * 含操作符的字符名
   * 字段命名：字段$操作符
   *
   * @param obj
   * @return
   */
  public static Criteria criteria(Object obj) {
    Map<String, Object> map = MapUtil.newHashMap();
    for (PropDesc pd : BeanUtil.getBeanDesc(obj.getClass()).getProps()) {
      if (pd.getGetter() == null) {
        continue;
      }
      Object val = pd.getValue(obj);
      if (ObjUtil.isNull(val)) {
        continue;
      }
      if (pd.getFieldClass() == String.class) {
        String str = (String) val;
        if (StrUtil.isBlank(str)) {
          continue;
        }

      }
      map.put(pd.getFieldName(), val);
    }
    return criteria(map);
  }

  public static Criteria criteria() {
    return new Criteria();
  }

  public static Criteria criteria(String key) {
    return Criteria.where(key);
  }

  /**
   * 根据map的key（rule）生成条件,要求value非空
   * rule：字段$(操作符)
   * "" : is
   * $eq : is
   * $is : is
   * $like : regex('.*value.*')
   * $leftLike : regex('value.*')
   * $RightLike : regex('.*value')
   *
   * @param map
   * @return
   */
  public static Criteria criteria(Map<String, Object> map) {
    Criteria c = new Criteria();
    if (ObjUtil.isNull(map)) {
      return c;
    }
    for (String key : map.keySet()) {
      Object value = map.get(key);
      if (ObjUtil.isNull(key)) {
        continue;
      }
      int index = key.lastIndexOf("$");
      String col = key;
      String op = "is";
      if (index > -1) {
        col = key.substring(0, index);
        op = key.substring(index + 1);
      }
      if (op.equals("isNull")) {
        c = c.and(col).isNull();
        continue;
      } else if (op.equals("isNullValue")) {
        c = c.and(col).isNullValue();
        continue;
      }
      if (ObjUtil.isNotNull(value)) {
        c = c.and(col);
        if (op.equals("eq")) {
          op = "is";
        }
        if (op.equals("regex")) {
          c = c.regex(value.toString());
          continue;
        } else if (op.equals("like") || op.equals("leftLike") || op.equals("rightLike")) {
          String v = value.toString();
          if (StrUtil.isBlank(v)) {
            continue;
          }
          switch (op) {
            case "like":
              c = c.regex(Pattern.compile("^.*" + v + ".*$", Pattern.CASE_INSENSITIVE));
              break;
            case "leftLike":
              c = c.regex(Pattern.compile("^" + v + ".*$", Pattern.CASE_INSENSITIVE));
              break;
            case "rightLike":
              c = c.regex(Pattern.compile("^.*" + v + "$", Pattern.CASE_INSENSITIVE));
              break;
          }
          continue;
        }

        try {
          c = (Criteria) ReflectUtil.getMethod(Criteria.class, op, Object.class).invoke(c, value);
        } catch (Exception e1) {
          String finalOp = op;
          Method[] ms = ReflectUtil.getMethods(Criteria.class, m1 -> m1.getName().equals(finalOp) && m1.getParameterCount() == 1);
          for (Method m : ms) {
            try {
              c = (Criteria) m.invoke(c, value);
              break;
            } catch (Exception e2) {

            }
          }
        }
      }
    }
    return c;
  }


  public static Query query(Map<String, Object> map) {
    Query q = new Query(criteria(map));
    q.allowDiskUse(true);
    return q;
  }

  public static Query query() {
    Query q = new Query();
    q.allowDiskUse(true);
    return q;
  }

  public static Query query(Criteria criteria) {
    Query q = new Query(criteria);
    q.allowDiskUse(true);
    return q;
  }

  public static Query query(Criteria... criterias) {
    return query(and(criterias));
  }

  public static Query query(Object obj, Criteria... criterias) {
    Criteria c = eq(obj);
    c.andOperator(criterias);
    return query(c);
  }

  public static <T> Page<T> findPage(Class<T> cls, Page<T> page, Map<String, Object> map) {
    return findPage(cls, page, new Query(criteria(map)));
  }

  public static <T> Page<T> findPage(Class<T> cls, Page<T> page, Criteria... criterias) {
    return findPage(cls, page, new Query(and(criterias)));
  }


  /**
   * 获取（Long,Integer）类型主键的集合的最大主键
   *
   * @param cls
   * @return
   */
  public static Long findMaxId(Class<?> cls) {
    Query query = new Query(Criteria.where("_id").exists(true));
    query.fields().include("_id");
    query.with(Sort.by(Sort.Direction.DESC, "_id"));
    query.allowDiskUse(true);
    query.limit(1);
    Map<String, Object> map = template.findOne(query, Map.class, template.getCollectionName(cls));
    if (MapUtil.isEmpty(map)) {
      return 0L;
    } else {
      Object object = map.get("_id");
      log.debug("maxId: {} - {} - {}", cls.getSimpleName(), object.getClass(), object);
      return Long.valueOf(object.toString());
    }
  }

  public static <T> Page<T> findPage(Class<T> cls, Page<T> page, Query query) {

    query.allowDiskUse(true);
    if (page.searchCount()) {
      page.setTotal(template.count(query, cls));
    }
    if (page != null) {
      query.with(page.pageable());
    }
    page.setRecords(template.find(query, cls));
    return page;
  }

  public static <T> T findOne(Class<T> cls, Map<String, Object> map) {
    return findOne(cls, criteria(map));
  }

  public static <T> T findOne(Class<T> cls, Sort sort, Map<String, Object> map) {
    return findOne(cls, sort, criteria(map));
  }

  public static <T> T findOne(Class<T> cls, Criteria... criterias) {
    return findOne(cls, new Query(and(criterias)));
  }

  public static <T> T findOne(Class<T> cls, Sort sort, Criteria... criterias) {
    return findOne(cls, sort, new Query(and(criterias)));
  }

  public static <T> T findOne(Class<T> cls, Query query) {
    query.limit(1);
    return template.findOne(query, cls);
  }

  public static <T> T findOne(Class<T> cls, Sort sort, Query query) {
    query.with(sort);
    return findOne(cls, query);
  }


  public static <T> List<T> find(Class<T> cls, Map<String, Object> map) {
    return find(cls, criteria(map));
  }

  public static <T> List<T> find(Class<T> cls, Map<String, Object> map, Sort sort) {
    Query query = new Query(criteria(map));
    query.with(sort);
    query.allowDiskUse(true);
    return find(cls, query);
  }

  public static <T> List<T> find(Class<T> cls, Criteria... criterias) {
    return find(cls, query(criterias));
  }

  public static <T> List<T> find(Class<T> cls, Sort sort, Criteria... criterias) {
    Query query = new Query(criteria(and(criterias)));
    query.with(sort);
    query.allowDiskUse(true);
    return find(cls, new Query());
  }

  public static boolean findExists(Query query, Class<?> entityClass) {
    return template.exists(query, entityClass);
  }


  public static boolean findExists(Query query, String collectionName) {
    return template.exists(query, collectionName);
  }

  public static <T> List<T> find(Class<T> cls, Query query) {
    return template.find(query, cls);
  }

  public static <T> T findById(Class<T> cls, Object id) {
    return template.findById(id, cls);
  }


  /**
   * 插入
   *
   * @param entity
   * @param <T>
   * @return
   */
  public static <T> T doInsert(T entity) {
    return template.insert(entity);
  }

  /**
   * 整体保存，更新或插入
   *
   * @param entity
   * @param <T>
   * @return
   */
  public static <T> T doSave(T entity) {
    return template.save(entity);
  }

  /**
   * 根据主键更新非空字段
   *
   * @param entity
   */
  public static <T> void doUpdate(T entity) {
    Object id = null;
    BeanDesc beanDesc = BeanUtil.getBeanDesc(entity.getClass());
    Update update = new Update();
    for (PropDesc propDesc : beanDesc.getProps()) {
      if (propDesc.getField().isAnnotationPresent(Id.class)) {
        id = propDesc.getValue(entity);
      } else {
        Object value = propDesc.getValue(entity);
        if (ObjUtil.isNotNull(value)) {
          update.set(propDesc.getFieldName(), value);
        }
      }
    }

    Query query = new Query(Criteria.where("_id").is(id));
    template.updateFirst(query, update, entity.getClass());
  }


  /**
   * 根据条件更新非空字段
   *
   * @param query
   * @param entity
   * @param <T>
   */
  public static <T> void doUpdate(Query query, T entity) {
    BeanDesc beanDesc = BeanUtil.getBeanDesc(entity.getClass());
    Update update = new Update();
    for (PropDesc propDesc : beanDesc.getProps()) {
      if (!propDesc.getField().isAnnotationPresent(Id.class)) {
        Object value = propDesc.getValue(entity);
        if (ObjUtil.isNotNull(value)) {
          update.set(propDesc.getFieldName(), value);
        }
      }
    }
    template.updateMulti(query, update, entity.getClass());
  }

  /**
   * 删除
   *
   * @param cls
   * @param id
   * @return
   */
  public static DeleteResult doRemoveById(Class<?> cls, Object id) {
    return doRemove(cls, new Query(Criteria.where("_id").is(id)));
  }

  /**
   * 删除
   *
   * @param cls
   * @param query
   * @return
   */
  public static DeleteResult doRemove(Class<?> cls, Query query) {
    return template.remove(query, cls);
  }


  public static Conditions conditions() {
    return new Conditions();
  }

  public static class Conditions extends HashMap<String, Object> {
    @Override
    public Conditions put(String key, Object value) {
      super.put(key, value);
      return this;
    }

    public Conditions put(boolean conditions, String key, Object value) {
      if (conditions) {
        super.put(key, value);
      }
      return this;
    }

    public Criteria criteria() {
      return MongoUtil.criteria(this);
    }

    public Query query() {
      return MongoUtil.query(this);
    }

  }

  /**
   * <pre>
   * auto :       Integer (increment),Long (increment),String(date_id)
   * increment :  Integer,Long
   * date_id :    Long,String(date_id)
   * uuid :       String
   * </pre>
   */
  public static enum FillIdType {


    auto, increment, date_id, uuid;
  }

  /**
   * 填充主键
   *
   * @param entity
   * @param <T>
   */
  public static <T> T fillId(T entity) {
    fillId(entity, FillIdType.auto);
    return entity;
  }


  /**
   * 填充主键
   *
   * @param entity
   * @param type
   * @param <T>
   */
  public static <T> T fillId(T entity, FillIdType type) {
    Assert.notNull(entity, "entity不能为空");

    Field[] fs = ReflectUtil.getFields(entity.getClass(), field -> field.isAnnotationPresent(Id.class));
    if (fs.length != 1) {
      return entity;
    }
    Field f = fs[0];
    if (f.getType() == Integer.class) {
      if (!IdUtils.nextIdInit(entity.getClass())) {
        IdUtils.nextIdInit(entity.getClass(), findMaxId(entity.getClass()));
      }
      ReflectUtil.setFieldValue(entity, f, IdUtils.nextId(entity.getClass()).intValue());
    } else if (f.getType() == Long.class) {
      if (type == FillIdType.date_id) {
        ReflectUtil.setFieldValue(entity, f, IdUtils.nextDateIdLong(entity.getClass(), 18));
      } else {
        if (!IdUtils.nextIdInit(entity.getClass())) {
          IdUtils.nextIdInit(entity.getClass(), findMaxId(entity.getClass()));
        }
        ReflectUtil.setFieldValue(entity, f, IdUtils.nextId(entity.getClass()));
      }
    } else if (f.getType() == String.class) {
      if (type == FillIdType.date_id) {
        ReflectUtil.setFieldValue(entity, f, IdUtils.nextDateId(entity.getClass(), 18));
      } else {
        ReflectUtil.setFieldValue(entity, f, UUID.fastUUID().toString(true));
      }
    }
    return entity;

  }

}
