package com.utic.center.common.aspect;

import com.utic.center.common.annotation.SqlOperation;
import com.utic.center.common.utils.LogUtils;
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;

import java.util.List;
import java.util.Objects;

@Slf4j
@Aspect
@Component
public class SqlOperationAspect extends AbstractLoggingAspect {

    protected String typeString(String type) {
        final int targetLength = 8;
        if (type.length() >= targetLength) {
            return type; // 초과하면 그대로 반환
        }
        return padEnd(type, targetLength, ' '); // 왼쪽에 '.'으로 채움
    }

    @Around("@annotation(com.utic.center.common.annotation.SqlOperation)")
    public Object aspectSqlOperation(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();                 // joinPoint 의 시그니처 가져오기
        SqlOperation sqlOperation  = signature.getMethod().getAnnotation(SqlOperation.class);   // annotation 가져오기
        String param = sqlOperation.param();                                                    // 특정 파라미터명 가져오기
        String param2 = sqlOperation.param2();                                                  // 특정 파라미터명 가져오기
        Object[] args = joinPoint.getArgs();                                                    // 실행된 메서드의 모든 파라미터 값 가져오기
        boolean isLogging = sqlOperation.logging();                          // logging 여부

//        String type = typeString(sqlOperation.type().name());
        String type = sqlOperation.type().getShortName();
        String tableName = sqlOperation.table();
        String paramValue = null;
        String paramValue2 = null;

        // 실행된 메서드의 파라미터 이름과 어노테이션의 param 값 비교
        String[] paramNames = signature.getParameterNames();
        for (int ii = 0; ii < paramNames.length; ii++) {
            if (!Objects.equals(param, "")) {
                if (paramNames[ii].equalsIgnoreCase(param)) {
                    paramValue = String.valueOf(args[ii]);  // 해당 값 추출
                }
            }
            if (!Objects.equals(param, "") && !Objects.equals(param2, "")) {
                if (paramNames[ii].equalsIgnoreCase(param2)) {
                    paramValue2 = String.valueOf(args[ii]);  // 해당 값 추출
                }
            }
        }

        final long start = System.currentTimeMillis();
        long executionTime;
        Object result;

        if (!Objects.equals(param, "") && paramValue == null) {
            paramValue = param;
        }
        if (!Objects.equals(param2, "") && paramValue2 == null) {
            paramValue2 = param2;
        }

        try {
            result = joinPoint.proceed();  // SQL 실행
        } catch (Exception e) {
            executionTime = System.currentTimeMillis() - start;
            exceptionLog(type, tableName, executionTime, e.getMessage(), paramValue, paramValue2);
            if (signature.getReturnType().equals(int.class)) {
                return -1;  // int 반환 타입 예외 발생 시 -1 반환
            }

            if (Objects.requireNonNull(sqlOperation.type()) == SqlOperation.SqlType.SELECT) {
                return null;  // SELECT 예외 발생 시 null 반환
            }
            return -1;  // 예외 발생 시 -1 반환
        }
        executionTime = System.currentTimeMillis() - start;

        int count = 1;
        if (signature.getReturnType().equals(int.class)) {
            count = (int)result;  // int 반환 타입 처리
        } else if (signature.getReturnType().equals(List.class)) {
            List<?> list = (List<?>)result;  // List 반환 타입 처리
            count = list.size();  // List 의 크기 계산
        }

        if (isLogging) {
            String info;
            if (Objects.requireNonNull(sqlOperation.type()) == SqlOperation.SqlType.TRUNCATE) {
                info = LogUtils.elapsedLog(tableName, executionTime);
            } else {
                info = LogUtils.elapsedLog(tableName, count, executionTime);
            }
            logging(type, info, paramValue, paramValue2);
        }
        return result;
    }

    private void exceptionLog(String type, String tableName, long executionTime, String errMessage, String paramValue, String paramValue2) {
        String info = LogUtils.elapsedLog(tableName, executionTime);
        if (paramValue != null) {
            if (paramValue2 != null) {
                log.error("({}) {}: {}, {}, Exception: {}", type, info, paramValue, paramValue2, errMessage);
            }
            else {
                log.error("({}) {}: {}, Exception: {}", type, info, paramValue, errMessage);
            }
        }
        else {
            log.error("({}) {}: Exception: {}", type, info, errMessage);
        }
    }

    private void logging(String type, String info, String paramValue, String paramValue2) {
        if (paramValue != null) {
            if (paramValue2 != null) {
                log.info("({}) {}, {}, {}", type, info, paramValue, paramValue2);
            }
            else {
                log.info("({}) {}, {}", type, info, paramValue);
            }
        }
        else {
            if (paramValue2 != null) {
                log.info("({}) {}: {}", type, info, paramValue2);
            }
            else {
                log.info("({}) {}", type, info);
            }
        }
    }

}
