package com.ga.common.mybatis;

import com.ga.common.util.SecurityUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Map;

/**
 * INSERT / UPDATE 시 감사 필드(created_at, created_by, updated_at, updated_by)를 자동 채운다.
 *
 * VO 에 다음 필드가 있을 때만 작동:
 *   - createdAt, createdBy, updatedAt, updatedBy
 *
 * Map 또는 Collection 파라미터의 경우 내부 객체 각각에 대해 적용한다.
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update",
                args = {MappedStatement.class, Object.class})
})
public class AuditInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];

        SqlCommandType type = ms.getSqlCommandType();
        if (type == SqlCommandType.INSERT || type == SqlCommandType.UPDATE) {
            apply(parameter, type);
        }
        return invocation.proceed();
    }

    private void apply(Object parameter, SqlCommandType type) {
        if (parameter == null) return;

        if (parameter instanceof Collection<?> col) {
            col.forEach(o -> applyOne(o, type));
            return;
        }
        if (parameter instanceof Map<?, ?> map) {
            for (Object v : map.values()) {
                if (v instanceof Collection<?> col) {
                    col.forEach(o -> applyOne(o, type));
                } else {
                    applyOne(v, type);
                }
            }
            return;
        }
        applyOne(parameter, type);
    }

    private void applyOne(Object obj, SqlCommandType type) {
        if (obj == null) return;
        try {
            MetaObject meta = SystemMetaObject.forObject(obj);
            LocalDateTime now = LocalDateTime.now();
            Long userId = SecurityUtil.getCurrentUserId();

            if (type == SqlCommandType.INSERT) {
                if (meta.hasSetter("createdAt") && meta.getValue("createdAt") == null) {
                    meta.setValue("createdAt", now);
                }
                if (meta.hasSetter("createdBy") && meta.getValue("createdBy") == null && userId != null) {
                    meta.setValue("createdBy", userId);
                }
            }
            if (type == SqlCommandType.UPDATE) {
                if (meta.hasSetter("updatedAt")) {
                    meta.setValue("updatedAt", now);
                }
                if (meta.hasSetter("updatedBy") && userId != null) {
                    meta.setValue("updatedBy", userId);
                }
            }
        } catch (Exception ignore) {
            // VO 가 아닌 단순 파라미터(Long, String 등)는 무시
        }
    }
}
