package com.ga.common.aop;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ga.common.annotation.DataChangeLog;
import com.ga.common.system.DataChangeLogService;
import com.ga.common.util.SecurityUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

/**
 * @DataChangeLog 처리.
 *
 * 정책:
 *  - 메서드 호출 전 첫 인자(또는 ID로 추정 가능한 인자)를 직렬화 → before
 *  - 정상 종료 후 동일 인자를 직렬화 → after
 *  - DataChangeLogService 가 before/after 비교 후 changed_keys 산출하여 INSERT
 *
 * 단순화를 위해 본 구현은 메서드 인자 전체를 JSON 직렬화 후 저장한다.
 * 실제 환경에서는 menu/table 별 조회 콜백을 등록하여 정확한 before-state 를 가져오는 것을 권장.
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class DataChangeLogAspect {

    private final DataChangeLogService dataChangeLogService;
    private final ObjectMapper objectMapper;

    @Around("@annotation(com.ga.common.annotation.DataChangeLog)")
    public Object log(ProceedingJoinPoint jp) throws Throwable {
        MethodSignature sig = (MethodSignature) jp.getSignature();
        DataChangeLog ann = sig.getMethod().getAnnotation(DataChangeLog.class);

        String beforeJson = "{}";
        String afterJson;
        Object[] args = jp.getArgs();
        try {
            beforeJson = args.length > 0 ? toJson(args[0]) : "{}";
        } catch (Exception ignore) { }

        Object result = jp.proceed();

        try {
            afterJson = args.length > 0 ? toJson(args[0]) : toJson(result);
            dataChangeLogService.save(
                    SecurityUtil.getCurrentUserId(),
                    ann.menu(),
                    ann.table(),
                    extractRecordId(args),
                    inferAction(ann, sig.getMethod().getName()),
                    beforeJson,
                    afterJson
            );
        } catch (Exception e) {
            log.warn("DataChangeLog save failed: {}", e.getMessage());
        }

        return result;
    }

    private String toJson(Object o) {
        if (o == null) return "{}";
        try {
            return objectMapper.writeValueAsString(o);
        } catch (Exception e) {
            return "{}";
        }
    }

    private String extractRecordId(Object[] args) {
        for (Object arg : args) {
            if (arg instanceof Long l)    return String.valueOf(l);
            if (arg instanceof Integer i) return String.valueOf(i);
            if (arg instanceof String s)  return s;
        }
        return null;
    }

    private String inferAction(DataChangeLog ann, String methodName) {
        if (!ann.action().isBlank()) return ann.action();
        String n = methodName.toLowerCase();
        if (n.startsWith("create") || n.startsWith("insert") || n.startsWith("add")) return "INSERT";
        if (n.startsWith("delete") || n.startsWith("remove")) return "DELETE";
        return "UPDATE";
    }
}
