package gc.mda.kcg.audit.annotation;

import gc.mda.kcg.audit.AuditLog;
import gc.mda.kcg.audit.AuditLogRepository;
import gc.mda.kcg.auth.AuthPrincipal;
import jakarta.servlet.http.HttpServletRequest;
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.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.HashMap;
import java.util.Map;

/**
 * @Auditable 어노테이션 → AOP가 메서드 실행 전후 auth_audit_log 기록.
 * 성공/실패 모두 기록.
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class AuditAspect {

    private final AuditLogRepository auditLogRepository;

    @Around("@annotation(auditable)")
    public Object audit(ProceedingJoinPoint pjp, Auditable auditable) throws Throwable {
        AuthPrincipal principal = currentPrincipal();
        String ipAddress = currentIp();

        Map<String, Object> detail = new HashMap<>();
        detail.put("method", ((MethodSignature) pjp.getSignature()).getMethod().getName());
        // 파라미터 이름은 컴파일 옵션 -parameters 필요 - 여기서는 단순 인덱스로 기록
        Object[] args = pjp.getArgs();
        if (args != null) {
            Map<String, Object> argMap = new HashMap<>();
            for (int i = 0; i < args.length; i++) {
                Object a = args[i];
                if (a == null) continue;
                if (a instanceof CharSequence || a instanceof Number || a instanceof Boolean) {
                    argMap.put("arg" + i, a.toString());
                }
            }
            if (!argMap.isEmpty()) detail.put("args", argMap);
        }

        try {
            Object result = pjp.proceed();
            saveLog(principal, auditable, detail, ipAddress, "SUCCESS", null);
            return result;
        } catch (Throwable e) {
            detail.put("exception", e.getClass().getSimpleName());
            saveLog(principal, auditable, detail, ipAddress, "FAILED", e.getMessage());
            throw e;
        }
    }

    private void saveLog(AuthPrincipal principal, Auditable ann, Map<String, Object> detail,
                         String ipAddress, String result, String failReason) {
        try {
            AuditLog log = AuditLog.builder()
                    .userId(principal != null ? principal.getUserId() : null)
                    .userAcnt(principal != null ? principal.getUserAcnt() : null)
                    .actionCd(ann.action())
                    .resourceType(ann.resourceType())
                    .ipAddress(ipAddress)
                    .detail(detail)
                    .result(result)
                    .failReason(failReason)
                    .build();
            auditLogRepository.save(log);
        } catch (Exception ex) {
            // 감사 기록 실패가 비즈니스를 막지 않도록
            AuditAspect.log.error("감사로그 기록 실패", ex);
        }
    }

    private AuthPrincipal currentPrincipal() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null && auth.getPrincipal() instanceof AuthPrincipal p) return p;
        return null;
    }

    private String currentIp() {
        try {
            ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attrs == null) return null;
            HttpServletRequest req = attrs.getRequest();
            String fwd = req.getHeader("X-Forwarded-For");
            if (fwd != null && !fwd.isBlank()) return fwd.split(",")[0].trim();
            return req.getRemoteAddr();
        } catch (Exception e) {
            return null;
        }
    }
}
