package com.yimeng.okr.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.yimeng.okr.annotation.OperationLogAnnotation;
import com.yimeng.okr.entity.OperationLog;
import com.yimeng.okr.mapper.OperationLogMapper;
import com.yimeng.okr.security.SecurityUtils;
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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Parameter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class OperationLogAspect {

    private static final String MASKED = "<masked>";
    private static final String SENSITIVE_RESULT = "<sensitive>";
    private static final Set<String> SENSITIVE_KEYS = Set.of(
            "password", "oldpassword", "newpassword", "passwordhash",
            "token", "accesstoken", "refreshtoken", "authorization",
            "commentsjson", "itemsjson", "interviewnotes", "employeefeedback",
            "reason", "reply"
    );
    private static final Set<String> SENSITIVE_ENTITY_TYPES = Set.of(
            "performance_score", "performance_feedback", "score_appeal", "database"
    );

    private final OperationLogMapper logMapper;
    private final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();

    @Around("@annotation(com.yimeng.okr.annotation.OperationLogAnnotation)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        OperationLogAnnotation annotation = signature.getMethod().getAnnotation(OperationLogAnnotation.class);

        Object result = null;
        Throwable thrown = null;
        try {
            result = joinPoint.proceed();
            return result;
        } catch (Throwable e) {
            thrown = e;
            throw e;
        } finally {
            writeOperationLog(joinPoint, annotation, result, thrown);
        }
    }

    private String toJson(Object value) throws Exception {
        return objectMapper.writeValueAsString(value);
    }

    private void writeOperationLog(ProceedingJoinPoint joinPoint,
                                   OperationLogAnnotation annotation,
                                   Object result,
                                   Throwable thrown) {
        try {
            boolean sensitive = annotation.sensitive() || SENSITIVE_ENTITY_TYPES.contains(annotation.entityType());
            OperationLog logRecord = new OperationLog();
            logRecord.setOperatorId(SecurityUtils.getCurrentUserId());
            logRecord.setAction(annotation.action());
            logRecord.setEntityType(annotation.entityType());
            logRecord.setOldDataJson(toJson(sanitize(extractParams(joinPoint))));
            logRecord.setNewDataJson(toJson(buildResultPayload(result, thrown, sensitive)));
            logRecord.setIp(getClientIp());
            logMapper.insert(logRecord);
        } catch (Exception e) {
            log.warn("Failed to write operation log for action {}", annotation.action(), e);
        }
    }

    private Object buildResultPayload(Object result, Throwable thrown, boolean sensitive) {
        if (thrown != null) {
            return Map.of(
                    "success", false,
                    "errorType", thrown.getClass().getSimpleName(),
                    "message", thrown.getMessage() != null ? thrown.getMessage() : ""
            );
        }
        if (sensitive) {
            return SENSITIVE_RESULT;
        }
        return sanitize(result);
    }

    private Map<String, Object> extractParams(ProceedingJoinPoint joinPoint) {
        Map<String, Object> params = new LinkedHashMap<>();
        Parameter[] parameters = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < parameters.length; i++) {
            String name = parameters[i].getName();
            if (!"request".equals(name) && !"response".equals(name)) {
                params.put(name, args[i]);
            }
        }
        return params;
    }

    @SuppressWarnings("unchecked")
    private Object sanitize(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Map<?, ?> map) {
            Map<String, Object> sanitized = new LinkedHashMap<>();
            for (Map.Entry<?, ?> entry : map.entrySet()) {
                String key = String.valueOf(entry.getKey());
                sanitized.put(key, isSensitiveKey(key) ? MASKED : sanitize(entry.getValue()));
            }
            return sanitized;
        }
        if (value instanceof List<?> list) {
            return list.stream().map(this::sanitize).toList();
        }
        if (value instanceof Object[] array) {
            return List.of(array).stream().map(this::sanitize).toList();
        }
        return value;
    }

    private boolean isSensitiveKey(String key) {
        String normalized = key == null ? "" : key.replace("_", "").replace("-", "").toLowerCase();
        return SENSITIVE_KEYS.stream().anyMatch(normalized::contains);
    }

    private String getClientIp() {
        try {
            ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attrs != null) {
                HttpServletRequest request = attrs.getRequest();
                String xff = request.getHeader("X-Forwarded-For");
                if (xff != null) return xff.split(",")[0].trim();
                return request.getRemoteAddr();
            }
        } catch (Exception ignored) {}
        return "unknown";
    }
}
