package incheon.com.config;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

/**
 * 로깅 AOP
 * 성능 모니터링과 디버깅을 위한 표준화된 로깅 체계를 제공
 *
 * @author 이재룡
 * @since 2025.09.02
 * @version 2.0.0
 */
@Slf4j
@Aspect
@Component
public class LoggingAspect {

    // ===== 포인트컷 정의 =====

    /**
     * 컨트롤러 레이어 포인트컷
     * 모든 컨트롤러 클래스의 public 메서드
     */
    @Pointcut("execution(public * incheon.uis..web.*Controller.*(..))")
    public void controllerMethods() {}

    /**
     * 서비스 레이어 포인트컷
     * 모든 서비스 클래스의 public 메서드
     */
    @Pointcut("execution(public * incheon.uis..service.*Service.*(..))")
    public void serviceMethods() {}

    /**
     * 데이터 액세스 레이어 포인트컷
     * 모든 매퍼 인터페이스의 메서드
     */
    @Pointcut("execution(public * incheon.uis..mapper.*Mapper.*(..))")
    public void mapperMethods() {}

    /**
     * 퍼블릭 메서드 포인트컷
     * uis 패키지 내 모든 퍼블릭 메서드
     */
    @Pointcut("execution(public * incheon.uis..*.*(..))")
    public void uisPublicMethods() {}

    // ===== 어드바이스 정의 =====

    /**
     * 컨트롤러 메서드 실행 전 로깅
     */
    @Before("controllerMethods()")
    public void logBeforeController(JoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        log.info("=== 컨트롤러 시작: {}.{} ===", className, methodName);
        log.debug("파라미터: {}", joinPoint.getArgs());
    }

    /**
     * 컨트롤러 메서드 실행 후 로깅
     */
    @After("controllerMethods()")
    public void logAfterController(JoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        log.info("=== 컨트롤러 완료: {}.{} ===", className, methodName);
    }

    /**
     * 서비스 메서드 실행 전 로깅
     */
    @Before("serviceMethods()")
    public void logBeforeService(JoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        log.debug("서비스 시작: {}.{}", className, methodName);
        log.trace("서비스 파라미터: {}", joinPoint.getArgs());
    }

    /**
     * 서비스 메서드 실행 후 로깅
     */
    @After("serviceMethods()")
    public void logAfterService(JoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        log.debug("서비스 완료: {}.{}", className, methodName);
    }

    /**
     * 매퍼 메서드 실행 전 로깅
     */
    @Before("mapperMethods()")
    public void logBeforeMapper(JoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        log.trace("매퍼 호출: {}.{}", className, methodName);
        log.trace("매퍼 파라미터: {}", joinPoint.getArgs());
    }

    /**
     * 성능 모니터링 어드바이스
     * 지정된 메서드들의 실행 시간을 측정
     */
    @Around("uisPublicMethods() && !mapperMethods()")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        try {
            Object result = joinPoint.proceed();
            stopWatch.stop();

            long executionTime = stopWatch.getTotalTimeMillis();

            // 실행 시간이 1초 이상인 경우 경고 로그
            if (executionTime >= 1000) {
                log.warn("성능 경고: {}.{} 실행 시간 = {}ms", className, methodName, executionTime);
            } else if (log.isDebugEnabled()) {
                log.debug("실행 시간: {}.{} = {}ms", className, methodName, executionTime);
            }

            return result;

        } catch (Exception ex) {
            stopWatch.stop();
            log.error("예외 발생: {}.{} (실행 시간: {}ms) - {}",
                    className, methodName, stopWatch.getTotalTimeMillis(), ex.getMessage());
            throw ex;
        }
    }

    /**
     * 예외 로깅 어드바이스
     * uis 패키지에서 발생하는 예외를 로깅
     */
    @AfterThrowing(pointcut = "uisPublicMethods()", throwing = "ex")
    public void logException(JoinPoint joinPoint, Exception ex) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        log.error("예외 발생: {}.{} - {}", className, methodName, ex.getMessage());
        log.error("예외 상세: ", ex);
        log.error("메서드 파라미터: {}", joinPoint.getArgs());
    }

    /**
     * 특정 비즈니스 메서드 모니터링
     * 중요 비즈니스 로직의 실행을 별도로 로깅
     */
    @Pointcut("execution(public * incheon.uis..service.*Service.save*(..)) || " +
              "execution(public * incheon.uis..service.*Service.update*(..)) || " +
              "execution(public * incheon.uis..service.*Service.delete*(..))")
    public void businessCriticalMethods() {}

    /**
     * 비즈니스 크리티컬 메서드 로깅
     */
    @Around("businessCriticalMethods()")
    public Object logBusinessCritical(ProceedingJoinPoint joinPoint) throws Throwable {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        log.info("비즈니스 크리티컬 작업 시작: {}.{}", className, methodName);

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        try {
            Object result = joinPoint.proceed();
            stopWatch.stop();

            log.info("비즈니스 크리티컬 작업 완료: {}.{} ({}ms)",
                    className, methodName, stopWatch.getTotalTimeMillis());

            return result;

        } catch (Exception ex) {
            stopWatch.stop();
            log.error("비즈니스 크리티컬 작업 실패: {}.{} ({}ms) - {}",
                    className, methodName, stopWatch.getTotalTimeMillis(), ex.getMessage());
            throw ex;
        }
    }

    /**
     * 데이터 검증 로깅
     * 입력 데이터의 유효성을 검증하는 메서드들을 로깅
     */
    @Pointcut("execution(public * incheon.uis..*.*validate*(..)) || " +
              "execution(public * incheon.uis..*.*check*(..))")
    public void validationMethods() {}

    /**
     * 검증 메서드 로깅
     */
    @AfterReturning(pointcut = "validationMethods()", returning = "result")
    public void logValidationResult(JoinPoint joinPoint, Object result) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();

        if (result instanceof Boolean) {
            boolean isValid = (Boolean) result;
            if (!isValid) {
                log.warn("검증 실패: {}.{} - 입력 데이터가 유효하지 않습니다.", className, methodName);
            } else {
                log.debug("검증 성공: {}.{}", className, methodName);
            }
        }
    }
}
