package com.malk.base;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 基础对象
 *
 * @JsonInclude(JsonInclude.Include.NON_NULL)：类注解过滤null字段，包含入参和返回值【参数类搭配 @Data，才可以实例化返回值以及ToString输出】
 * -
 * lombok
 * @Data ： 注在类上，提供类的get、set、equals、hashCode、canEqual、toString方法
 * @AllArgsConstructor ： 注在类上，提供类的全参构造
 * @NoArgsConstructor ： 注在类上，提供类的无参构造
 * @Setter ： 注在属性上，提供 set 方法
 * @Getter ： 注在属性上，提供 getDefault 方法
 * @EqualsAndHashCode ： 注在类上，提供对应的 equals 和 hashCode 方法
 * @Log4j/@Slf4j ： 注在类上，提供对应的 Logger 对象，变量名为 log
 * @Builder：为类生成相对略微复杂的构建器API。来初始化实例对象：：类名.属性(值).属性(值).build()
 * @Singular：在使用@Singular注释注释一个集合字段（使用@Builder注释类），lombok会将该构建器节点视为一个集合，并生成两个adder方法而不是setter方法：：点一次集合增加一个元素
 * @Builder.Default：在类中id和insertTime上都添加注解@Builder.Default，当在使用这个实体对象时，就不需要在为这两个字段进行初始化值
 */
@Data
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class BaseDto {

    /**
     * 对象拷贝: 若是复制一个对象, 建议使用 cloneParam 避免性能问题
     */
    @Deprecated
    @SneakyThrows
    public BaseDto copyParam() {
        BaseDto dto = this.getClass().newInstance();
        BeanUtils.copyProperties(this, dto);
        return dto;
    }

    /**
     * * todo: 4.11 继承Serializable后执行深拷贝, 不能类型转换, 不继承Serializable则返回
     * 对象拷贝: 复制一个新的对象, 避免条件被修改, 尤其并发下分页混乱情况
     */
    public BaseDto cloneParam() {
        return ObjectUtil.clone(this);
    }

    /**
     * 对象属性合并: jda之save接口会以传入数据为准，若传入为空或不传入，更新会置空。目前解决办法两种，通过注解实现JPQL/SQL，或者查询出数据，将未传入字段属性拷贝后再更新【性能消耗】
     */
    public void mergeParam(BaseDto modifyDo) {
        BeanUtils.copyProperties(this, modifyDo, getNotNullPropertyNames(modifyDo));
    }

    // 忽略有值的字段
    private static String[] getNotNullPropertyNames(Object target) {
        final BeanWrapper src = new BeanWrapperImpl(target);
        PropertyDescriptor[] pds = src.getPropertyDescriptors();
        Set<String> emptyNames = new HashSet();
        for (PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue != null) {
                // 此处判断可根据需求修改, 目前过滤不为null
                emptyNames.add(pd.getName());
            }
        }
        String[] result = new String[emptyNames.size()];
        return emptyNames.toArray(result);
    }

    /**
     * 传入映射的Map, 将实体属性和Map的key转换, 返回Map
     */
    public Map convertEntity(Map<String, String> reflect) {
        Map map = JSON.parseObject(JSON.toJSONString(this, SerializerFeature.WriteNullStringAsEmpty), Map.class);
        Map<String, String> formData = new HashMap();
        for (String key : reflect.keySet()) {
            String content = String.valueOf(map.get(key));
            // json序列化已经将空字符串过滤, 若转换还有null字符串, 可能是key为null或SerializerFeature未指定到类型, 如Date
            if (StringUtils.isNotBlank(content) && !content.equals("null")) formData.put(reflect.get(key), content);
        }
        return formData;
    }


    /**
     * Map时间格式化, 直接从数据库取值后Map会有市区差, 方法1见BasePo, @Temporal & @JsonFormat 注解
     * -
     * [单独时间格式化 [废弃]]
     * * JSON.parseArray(JSON.toJSONString(data), Map.class).stream().map(item -> {
     * *     item.put("tStoreInTime", UtilDateTime.formatDateTime(new Date(UtilMap.getLong(item, "tStoreInTime"))));
     * *     return item;
     * * });
     */
    public static final Object jsonFormatDateTime(Object data) {
        return JSON.parse(JSON.toJSONString(data, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteDateUseDateFormat));
    }
}
