package com.yd.csf.service.audit;

import cn.hutool.core.collection.CollUtil;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.yd.csf.service.annotation.Auditable;
import com.yd.csf.service.model.SysOperDataChange;
import com.yd.csf.service.model.SysOperDataSnapshot;
import com.yd.csf.service.service.AuditLogDataChangeService;
import com.yd.csf.service.service.AuditLogDataSnapshotService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.*;

@Slf4j
@Component
@RequiredArgsConstructor
public class AuditHelper {

    private final AuditLogDataSnapshotService snapshotService;
    private final AuditLogDataChangeService changeService;
    private final FieldLabelResolver fieldLabelResolver;

    private static final Set<String> EXCLUDED_FIELDS = new HashSet<>(Arrays.asList(
            "id", "createTime", "updateTime", "creatorId", "creatorName",
            "updaterId", "updaterName", "isDeleted", "serialVersionUID"
    ));

    private static final Gson GSON = new GsonBuilder()
            .addSerializationExclusionStrategy(new ExclusionStrategy() {
                @Override
                public boolean shouldSkipField(FieldAttributes f) {
                    return EXCLUDED_FIELDS.contains(f.getName());
                }

                @Override
                public boolean shouldSkipClass(Class<?> clazz) {
                    return false;
                }
            })
            .create();

    /**
     * 记录数据变更。
     *
     * @param tableName 表名
     * @param beforeMap 变更前的数据，key 为主键值，value 为实体对象（INSERT 时为 null）
     * @param afterMap  变更后的数据，key 为主键值，value 为实体对象（DELETE 时为 null）
     * @param ctx       审计上下文
     */
    public void record(String tableName, Map<String, ?> beforeMap, Map<String, ?> afterMap, AuditContext ctx) {
        if (beforeMap == null && afterMap == null) {
            return;
        }

        Class<?> entityClass = getEntityClass(beforeMap, afterMap);
        if (entityClass == null) {
            log.warn("无法确定实体类型，跳过审计记录: table={}", tableName);
            return;
        }

        Auditable auditable = entityClass.getAnnotation(Auditable.class);
        if (auditable == null) {
            throw new IllegalArgumentException("实体类 " + entityClass.getName() + " 缺少 @Auditable 注解");
        }

        List<Field> diffFields = getDiffFields(entityClass);

        Set<String> allIds = new LinkedHashSet<>();
        if (beforeMap != null) allIds.addAll(beforeMap.keySet());
        if (afterMap != null) allIds.addAll(afterMap.keySet());

        List<SysOperDataSnapshot> snapshots = new ArrayList<>();
        List<SysOperDataChange> changes = new ArrayList<>();
        Date now = new Date();

        for (String recordId : allIds) {
            Object before = beforeMap != null ? beforeMap.get(recordId) : null;
            Object after = afterMap != null ? afterMap.get(recordId) : null;

            String beforeJson = before != null ? GSON.toJson(before) : null;
            String afterJson = after != null ? GSON.toJson(after) : null;

            boolean isInsert = before == null && after != null;
            boolean isDelete = before != null && after == null;
            String changeType = isInsert ? "ADD" : (isDelete ? "DELETE" : "MODIFY");

            SysOperDataSnapshot snapshot = new SysOperDataSnapshot();
            snapshot.setRequestId(ctx.getRequestId());
            snapshot.setTableName(tableName);
            snapshot.setRecordId(recordId);
            snapshot.setBeforeJson(beforeJson);
            snapshot.setAfterJson(afterJson);
            snapshot.setCreateTime(now);
            snapshots.add(snapshot);

            if (!isInsert && !isDelete && before != null && after != null) {
                for (Field field : diffFields) {
                    try {
                        field.setAccessible(true);
                        Object oldVal = field.get(before);
                        Object newVal = field.get(after);
                        if (!Objects.equals(oldVal, newVal)) {
                            SysOperDataChange change = new SysOperDataChange();
                            change.setRequestId(ctx.getRequestId());
                            change.setTableName(tableName);
                            change.setRecordId(recordId);
                            change.setFieldName(field.getName());
                            change.setFieldComment(fieldLabelResolver.resolve(field, tableName));
                            change.setOldValue(oldVal != null ? oldVal.toString() : null);
                            change.setNewValue(newVal != null ? newVal.toString() : null);
                            change.setChangeType("MODIFY");
                            change.setCreateTime(now);
                            changes.add(change);
                        }
                    } catch (IllegalAccessException e) {
                        log.debug("跳过无法访问的字段: {}", field.getName());
                    }
                }
            }
        }

        if (CollUtil.isNotEmpty(snapshots)) {
            snapshotService.saveBatch(snapshots);
        }
        if (CollUtil.isNotEmpty(changes)) {
            changeService.saveBatch(changes);
        }
    }

    private Class<?> getEntityClass(Map<String, ?> beforeMap, Map<String, ?> afterMap) {
        if (beforeMap != null) {
            for (Object v : beforeMap.values()) {
                if (v != null) return v.getClass();
            }
        }
        if (afterMap != null) {
            for (Object v : afterMap.values()) {
                if (v != null) return v.getClass();
            }
        }
        return null;
    }

    private List<Field> getDiffFields(Class<?> entityClass) {
        List<Field> fields = new ArrayList<>();
        Class<?> current = entityClass;
        while (current != null && current != Object.class) {
            for (Field field : current.getDeclaredFields()) {
                if (!EXCLUDED_FIELDS.contains(field.getName())
                        && !java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
                    fields.add(field);
                }
            }
            current = current.getSuperclass();
        }
        return fields;
    }

    /**
     * 对实体列表做深拷贝快照。
     * 使用 Gson 序列化→反序列化做深拷贝，自动从 @Auditable.idField 读取 Map key。
     *
     * @param entities 实体列表，null 或空列表返回空 Map
     * @param <T>      实体类型
     * @return Map&lt;业务ID, 深拷贝后的实体&gt;
     * @throws IllegalArgumentException 如果实体类缺少 @Auditable 注解
     */
    @SuppressWarnings("unchecked")
    public <T> Map<String, T> snapshot(List<T> entities) {
        if (CollUtil.isEmpty(entities)) {
            return Collections.emptyMap();
        }

        T first = entities.get(0);
        if (first == null) {
            return Collections.emptyMap();
        }

        Class<?> entityClass = first.getClass();
        Auditable auditable = entityClass.getAnnotation(Auditable.class);
        if (auditable == null) {
            throw new IllegalArgumentException("实体类 " + entityClass.getName() + " 缺少 @Auditable 注解");
        }

        String idFieldName = auditable.idField();
        Map<String, T> result = new LinkedHashMap<>();
        for (T entity : entities) {
            try {
                Field idField = findField(entityClass, idFieldName);
                if (idField == null) {
                    throw new RuntimeException("实体类 " + entityClass.getName() + " 中没有字段: " + idFieldName);
                }
                idField.setAccessible(true);
                Object idValue = idField.get(entity);
                if (idValue == null) {
                    log.warn("实体 idField={} 值为 null，跳过: {}", idFieldName, entity);
                    continue;
                }
                T copy = (T) GSON.fromJson(GSON.toJson(entity), entityClass);
                result.put(idValue.toString(), copy);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("无法读取字段 " + idFieldName, e);
            }
        }
        return result;
    }

    private Field findField(Class<?> clazz, String fieldName) {
        Class<?> current = clazz;
        while (current != null && current != Object.class) {
            try {
                return current.getDeclaredField(fieldName);
            } catch (NoSuchFieldException e) {
                current = current.getSuperclass();
            }
        }
        return null;
    }
}
