package org.dromara.system.zwy.utils;


import java.lang.reflect.Field;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * 对象数据校验码生成器
 * 用于生成对象的校验码，检测数据是否被修改
 * 支持排除时间类型字段和修改人ID字段
 */
public class ObjectHashGenerator {

    // 默认排除的字段名（不区分大小写）
    private static final Set<String> DEFAULT_EXCLUDED_FIELDS = new HashSet<>(Arrays.asList(
        "id", "ids", "created_at", "createdat", "updated_at", "updatedat", "oper_id","businesstypes",
        "create_time", "createtime", "update_time", "updatetime",
        "modify_time", "modifytime", "modified_by", "modifiedby", "editor", "hmac", "costtime", "cost_time",
        // 下面改成小写
        "typenum", "borrowstatus", "archivalcode", "fondsnum", "fondsname", "state", "isdelete", "updateby", "remark",
        "aggregationlevel", "cart", "grouptime", "archiveswarehousebentboxid", "savetype", "archivetype",
        "overallstatus", "listingtime", "archivecarrier", "datadigest", "appraisaltime", "infoid", "operid", "ossid",
        "createby", "docid", "createdept"
    ));


    // 默认排除的时间类型
    private static final Set<Class<?>> DEFAULT_TIME_TYPES = new HashSet<>(Arrays.asList(
        Date.class, java.sql.Date.class, java.sql.Timestamp.class,
        Calendar.class, java.time.LocalDate.class, java.time.LocalDateTime.class,
        java.time.ZonedDateTime.class
    ));

    /**
     * 生成对象的校验码
     * @param obj 要生成校验码的对象
     * @return 校验码字符串（SHA-256）
     */
    public static String generateHash(Object obj) {

        return generateHash(obj, null, null, "SHA-256");
    }

    /**
     * 生成对象的校验码
     * @param obj 要生成校验码的对象
     * @param algorithm 哈希算法（如MD5, SHA-256）
     * @return 校验码字符串
     */
    public static String generateHash(Object obj, String algorithm) {
        return generateHash(obj, null, null, algorithm);
    }

    /**
     * 生成对象的校验码，自定义排除字段和时间类型
     * @param obj 要生成校验码的对象
     * @param excludedFields 额外需要排除的字段名列表
     * @param timeTypes 额外需要排除的时间类型列表
     * @param algorithm 哈希算法
     * @return 校验码字符串
     */
    public static String generateHash(Object obj, Set<String> excludedFields,
                                      Set<Class<?>> timeTypes, String algorithm) {
        if (obj == null) {
            return "";
        }

        // 合并默认排除字段和自定义排除字段
        Set<String> allExcludedFields = new HashSet<>(DEFAULT_EXCLUDED_FIELDS);
        if (excludedFields != null) {
            allExcludedFields.addAll(excludedFields);
        }

        // 合并默认时间类型和自定义时间类型
        Set<Class<?>> allTimeTypes = new HashSet<>(DEFAULT_TIME_TYPES);
        if (timeTypes != null) {
            allTimeTypes.addAll(timeTypes);
        }

        // 收集对象的所有属性值并排序
        List<String> fieldValues = collectFieldValues(obj, allExcludedFields, allTimeTypes);
        Collections.sort(fieldValues);

        // 拼接所有属性值并生成哈希
        String concatenatedValues = String.join("", fieldValues);
        return hashString(concatenatedValues, algorithm);
    }

    /**
     * 递归收集对象的所有属性值
     */
    private static List<String> collectFieldValues(Object obj, Set<String> excludedFields, Set<Class<?>> timeTypes) {
        List<String> values = new ArrayList<>();

        if (obj == null) {
            values.add("null");
            return values;
        }

        // 处理基本类型
        if (isPrimitiveOrWrapper(obj)) {
            values.add(obj.toString());
            return values;
        }

        // 处理字符串
        if (obj instanceof String) {
            values.add((String) obj);
            return values;
        }

        // 处理时间类型（排除）
        if (isTimeType(obj, timeTypes)) {
            return values; // 时间类型直接返回空列表
        }

        // 处理集合类型
        if (obj instanceof Collection) {
            Collection<?> collection = (Collection<?>) obj;
            for (Object item : collection) {
                values.addAll(collectFieldValues(item, excludedFields, timeTypes));
            }
            return values;
        }

        // 处理Map类型
        if (obj instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) obj;
            List<String> mapEntries = new ArrayList<>();
            for (Map.Entry<?, ?> entry : map.entrySet()) {
                String key = entry.getKey() != null ? entry.getKey().toString() : "null";
                // 检查key是否为排除字段
                if (excludedFields.contains(key)) {
                    continue;
                }
                List<String> valueList = collectFieldValues(entry.getValue(), excludedFields, timeTypes);
                mapEntries.add(key + ":" + String.join("", valueList));
            }
            Collections.sort(mapEntries);
            values.addAll(mapEntries);
            return values;
        }

        // 处理数组类型
        if (obj.getClass().isArray()) {
            int length = java.lang.reflect.Array.getLength(obj);
            for (int i = 0; i < length; i++) {
                Object item = java.lang.reflect.Array.get(obj, i);
                values.addAll(collectFieldValues(item, excludedFields, timeTypes));
            }
            return values;
        }

        // 处理自定义对象
        Class<?> clazz = obj.getClass();
        while (clazz != null && clazz != Object.class) {
            for (Field field : clazz.getDeclaredFields()) {
                field.setAccessible(true);
                String fieldName = field.getName();

                // 检查是否为排除字段
                if (excludedFields.contains(fieldName.toLowerCase())) {
                    continue;
                }

                try {
                    Object fieldValue = field.get(obj);
                    values.addAll(collectFieldValues(fieldValue, excludedFields, timeTypes));
                } catch (IllegalAccessException e) {
                    // 忽略无法访问的字段
                }
            }
            clazz = clazz.getSuperclass();
        }

        return values;
    }

    /**
     * 检查对象是否为基本类型或包装类
     */
    private static boolean isPrimitiveOrWrapper(Object obj) {
        return obj instanceof Boolean || obj instanceof Character ||
            obj instanceof Number;
    }

    /**
     * 检查对象是否为时间类型
     */
    private static boolean isTimeType(Object obj, Set<Class<?>> timeTypes) {
        for (Class<?> timeType : timeTypes) {
            if (timeType.isInstance(obj)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 对字符串进行哈希计算
     */
    private static String hashString(String input, String algorithm) {
        try {
            MessageDigest digest = MessageDigest.getInstance(algorithm);
            byte[] hashBytes = digest.digest(input.getBytes());

            StringBuilder hexString = new StringBuilder();
            for (byte hashByte : hashBytes) {
                String hex = Integer.toHexString(0xff & hashByte);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }

            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("不支持的哈希算法: " + algorithm, e);
        }
    }

    // 示例使用
    public static void main(String[] args) {
        // 示例对象
        ExampleObject obj1 = new ExampleObject();
        obj1.setId(123);
        obj1.setName("测试对象");
        obj1.setCreateTime(new Date());
        obj1.setModifiedBy(456);
        obj1.setDescription("这是一个示例对象");

        // 生成校验码
        String hash1 = generateHash(obj1);
        System.out.println("原始对象校验码: " + hash1);

        // 修改对象
        ExampleObject obj2 = new ExampleObject();
        obj2.setId(123);
        obj2.setName("测试对象");
        obj2.setCreateTime(new Date()); // 时间会变化
        obj2.setModifiedBy(789); // 修改人ID变化
        obj2.setDescription("这是一个示例对象"); // 内容未变

        // 生成修改后的校验码
        String hash2 = generateHash(obj2);
        System.out.println("修改后对象校验码: " + hash2);

        // 比较校验码
        boolean isSame = hash1.equals(hash2);
        System.out.println("数据是否未修改: " + isSame);
    }

    // 示例类
    static class ExampleObject {
        private int id;
        private String name;
        private Date createTime;
        private int modifiedBy;
        private String description;

        // Getters and Setters
        public int getId() { return id; }
        public void setId(int id) { this.id = id; }

        public String getName() { return name; }
        public void setName(String name) { this.name = name; }

        public Date getCreateTime() { return createTime; }
        public void setCreateTime(Date createTime) { this.createTime = createTime; }

        public int getModifiedBy() { return modifiedBy; }
        public void setModifiedBy(int modifiedBy) { this.modifiedBy = modifiedBy; }

        public String getDescription() { return description; }
        public void setDescription(String description) { this.description = description; }
    }
}

