package com.bonus.canteen.core.common.utils;

import cn.hutool.core.bean.BeanDesc;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.annotations.ApiModelProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class LogUtil {
   private static final Logger log = LoggerFactory.getLogger(LogUtil.class);
   private static final String TRACE_KEY = "X-Trace-Id";
   private static final String ARG_SEPERATOR = " --> ";
   private static final ThreadLocal<Boolean> THREAD_LOCAL_FLAG = new InheritableThreadLocal();
   @Lazy
   private static final Environment environment = (Environment)SpringContextHolder.getBean(Environment.class);

   public static void generateNewLogTraceId() {
      MDC.put("X-Trace-Id", UUID.fastUUID().toString(true));
   }

   public static void putLogTraceId(String traceId) {
      MDC.put("X-Trace-Id", traceId);
   }

   public static String getCurrentTraceId() {
      return MDC.get("X-Trace-Id");
   }

   public static void printArgsIn(String tag, Object... args) {
      if ((Boolean)environment.getProperty("system.args-print.enable", Boolean.class, Boolean.FALSE)) {
         info(StrUtil.concat(true, new CharSequence[]{tag, "][入参"}), args);
      }
   }

   public static void printArgsOut(String tag, Object... args) {
      if ((Boolean)environment.getProperty("system.args-print.enable", Boolean.class, Boolean.FALSE)) {
         info(StrUtil.concat(true, new CharSequence[]{tag, "][出参"}), args);
      }
   }

   public static void printArgs(String tag, Object... args) {
      if ((Boolean)environment.getProperty("system.args-print.enable", Boolean.class, Boolean.FALSE)) {
         info(tag, args);
      }
   }

   public static void printF(String tag, Object... args) {
      if (!Boolean.FALSE.equals(THREAD_LOCAL_FLAG.get())) {
         info(tag, args);
      }
   }

   public static void setF(boolean flag) {
      THREAD_LOCAL_FLAG.set(flag);
   }

   public static void removeF() {
      THREAD_LOCAL_FLAG.remove();
   }

   public static void info(String tag, Object... args) {
      StringBuilder sb = new StringBuilder();
      sb.append(tag);
      if (ArrayUtil.isNotEmpty(args)) {
         Object[] var3 = args;
         int var4 = args.length;

         for(int var5 = 0; var5 < var4; ++var5) {
            Object arg = var3[var5];
            Object argStr = isBasicType(arg) ? arg : JacksonUtil.writeValueAsStringIgnoreNull(arg);
            sb.append(" --> ").append(argStr);
         }
      }

      log.info(sb.toString());
   }

   public static void infoFields(String tag, Object arg, String... fieldNames) {
      StringBuilder sb = new StringBuilder();
      sb.append(tag);
      concatString(sb, arg, fieldNames);
      log.info(sb.toString());
   }

   public static void infoFieldsX(String tag, Object... args) {
      StringBuilder sb = new StringBuilder();
      sb.append(tag);

      for(int i = 0; i < args.length; ++i) {
         if (i % 2 == 0) {
            Object arg = args[i];
            String[] fieldNames = (String[])args[i + 1];
            concatString(sb, arg, fieldNames);
         }
      }

      log.info(sb.toString());
   }

   public static <T> void infoDesc(String tag, Object... args) {
      infoFieldsDesc(tag, args);
   }

   public static void infoFieldsDesc(String tag, Object arg, String... fieldNames) {
      StringBuilder sb = new StringBuilder();
      sb.append(tag);
      concatStringWithDesc(sb, arg, fieldNames);
      log.info(sb.toString());
   }

   public static <T> String getDescOfField(Class<T> aClass, String filedName) {
      BeanDesc beanDesc = BeanUtil.getBeanDesc(aClass);
      Field declaredField = beanDesc.getField(filedName);
      if (declaredField == null) {
         Class<? super T> superclass = aClass.getSuperclass();
         return superclass != null ? getDescOfField(superclass, filedName) : "";
      } else {
         return getDescOfField(declaredField);
      }
   }

   private static String getDescOfField(Field field) {
      ApiModelProperty annotation = (ApiModelProperty)field.getAnnotation(ApiModelProperty.class);
      if (annotation != null) {
         StringBuilder sb = new StringBuilder();
         String annotationRemark = annotation.value();
         if (StrUtil.isNotEmpty(annotationRemark)) {
            sb.append("【").append(StrUtil.isNotEmpty(annotationRemark) ? annotationRemark : "").append("】");
         }

         return sb.toString();
      } else {
         return "";
      }
   }

   private static Map<String, Object> descMapFroObj(Object arg, Class<Object> aClass, String... propertyNames) {
      Map<String, Object> descMap = MapUtil.newHashMap();
      CopyOptions copyOptions = CopyOptions.create().setIgnoreNullValue(false).setFieldNameEditor((s) -> {
         return !ArrayUtil.isEmpty(propertyNames) && !ArrayUtil.contains(propertyNames, s) ? null : s + getDescOfField(aClass, s);
      }).setFieldValueEditor((s, o) -> {
         if (isBasicType(o)) {
            return o;
         } else if (o instanceof Collection) {
            List<Map<String, Object>> listProps = CollUtil.newArrayList(new Map[0]);
            ((Collection)o).forEach((o1) -> {
               listProps.add(descMapFroObj(o1, ClassUtil.getClass(o1)));
            });
            return listProps;
         } else {
            return descMapFroObj(o, ClassUtil.getClass(o));
         }
      });
      BeanUtil.beanToMap(arg, descMap, copyOptions);
      return descMap;
   }

   private static void concatStringWithDesc(StringBuilder sb, Object arg, String... fieldNames) {
      if (arg instanceof Collection) {
         ArrayList<Object> list = CollUtil.newArrayList(new Object[0]);
         ((Collection)arg).forEach((o) -> {
            Class<Object> aClass = ClassUtil.getClass(arg);
            Map<String, Object> descMap = descMapFroObj(arg, aClass, fieldNames);
            list.add(descMap);
         });
         sb.append(" --> ").append(JacksonUtil.writeValueAsStringIgnoreNull(list));
      } else if (arg != null) {
         Class<Object> aClass = ClassUtil.getClass(arg);
         Map<String, Object> descMap = descMapFroObj(arg, aClass, fieldNames);
         sb.append(" --> ").append(JacksonUtil.writeValueAsStringIgnoreNull(descMap));
      }

   }

   private static void concatString(StringBuilder sb, Object arg, String[] fieldNames) {
      if (arg instanceof Collection) {
         ArrayList<Object> list = CollUtil.newArrayList(new Object[0]);
         ((Collection)arg).forEach((o) -> {
            Map<String, Object> descMap = MapUtil.newHashMap();
            Map<String, Object> stringObjectMap = BeanUtil.beanToMap(o, descMap, false, (s) -> {
               return !ArrayUtil.isEmpty(fieldNames) && !ArrayUtil.contains(fieldNames, s) ? null : s;
            });
            list.add(stringObjectMap);
         });
         sb.append(" --> ").append(JacksonUtil.writeValueAsStringIgnoreNull(list));
      } else if (arg != null) {
         Map<String, Object> descMap = MapUtil.newHashMap();
         BeanUtil.beanToMap(arg, descMap, false, (s) -> {
            return !ArrayUtil.isEmpty(fieldNames) && !ArrayUtil.contains(fieldNames, s) ? null : s;
         });
         sb.append(" --> ").append(JacksonUtil.writeValueAsStringIgnoreNull(descMap));
      }

   }

   private static boolean isBasicType(Object obj) {
      return obj == null || ClassUtil.isBasicType(obj.getClass()) || ClassUtil.isSimpleValueType(obj.getClass()) || obj instanceof LocalDate || obj instanceof LocalDateTime || obj instanceof Map;
   }
}
