package cn.novalon.gym.manage.sys.audit;

import cn.novalon.gym.manage.sys.core.domain.OperationLog;
import cn.novalon.gym.manage.sys.core.service.IOperationLogService;
import cn.novalon.gym.manage.sys.util.IpUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Aspect
@Component
public class OperationLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(OperationLogAspect.class);
    private static final int MAX_PARAM_LENGTH = 2000;
    private static final int MAX_RESULT_LENGTH = 5000;
    
    private final IOperationLogService logService;
    private final ObjectMapper objectMapper;
    
    public OperationLogAspect(IOperationLogService logService, ObjectMapper objectMapper) {
        this.logService = logService;
        this.objectMapper = objectMapper;
    }
    
    @Around("@annotation(operationLogAnnotation)")
    public Object around(ProceedingJoinPoint point, cn.novalon.gym.manage.sys.audit.OperationLog operationLogAnnotation) throws Throwable {
        long startTime = System.currentTimeMillis();
        ServerRequest serverRequest = extractServerRequest(point.getArgs());
        String ip = IpUtils.getClientIp(serverRequest);
        String method = point.getSignature().toShortString();
        String params = serializeParams(point.getArgs());
        
        try {
            Object result = point.proceed();
            
            if (result instanceof Mono) {
                return getCurrentUsername()
                        .flatMap(username -> ((Mono<?>) result)
                                .flatMap(res -> {
                                    long duration = System.currentTimeMillis() - startTime;
                                    return saveLogAsync(operationLogAnnotation, username, ip, method, params, res, duration, "0", null)
                                            .onErrorResume(e -> Mono.empty())
                                            .thenReturn(res);
                                })
                                .onErrorResume(error -> {
                                    long duration = System.currentTimeMillis() - startTime;
                                    return saveLogAsync(operationLogAnnotation, username, ip, method, params, null, duration, "1", error.getMessage())
                                            .onErrorResume(e -> Mono.empty())
                                            .then(Mono.error(error));
                                })
                        );
            } else if (result instanceof Flux) {
                return getCurrentUsername()
                        .flatMapMany(username -> ((Flux<?>) result)
                                .collectList()
                                .flatMapMany(res -> {
                                    long duration = System.currentTimeMillis() - startTime;
                                    return saveLogAsync(operationLogAnnotation, username, ip, method, params, res, duration, "0", null)
                                            .onErrorResume(e -> Mono.empty())
                                            .thenMany(Flux.fromIterable(res));
                                })
                                .onErrorResume(error -> {
                                    long duration = System.currentTimeMillis() - startTime;
                                    return saveLogAsync(operationLogAnnotation, username, ip, method, params, null, duration, "1", error.getMessage())
                                            .onErrorResume(e -> Mono.empty())
                                            .thenMany(Flux.error(error));
                                })
                        );
            }
            
            return result;
        } catch (Throwable error) {
            long duration = System.currentTimeMillis() - startTime;
            getCurrentUsername()
                    .flatMap(username -> saveLogAsync(operationLogAnnotation, username, ip, method, params, null, duration, "1", error.getMessage()))
                    .subscribe();
            throw error;
        }
    }
    
    private ServerRequest extractServerRequest(Object[] args) {
        if (args == null || args.length == 0) return null;
        for (Object arg : args) {
            if (arg instanceof ServerRequest) return (ServerRequest) arg;
        }
        return null;
    }
    
    private Mono<String> getCurrentUsername() {
        return ReactiveSecurityContextHolder.getContext()
                .map(ctx -> ctx.getAuthentication().getPrincipal())
                .map(principal -> principal instanceof String ? (String) principal : "system")
                .defaultIfEmpty("system")
                .onErrorReturn("system");
    }
    
    private String serializeParams(Object[] args) {
        try {
            if (args == null || args.length == 0) return null;
            String json = objectMapper.writeValueAsString(args);
            if (json.length() > MAX_PARAM_LENGTH) {
                return json.substring(0, MAX_PARAM_LENGTH) + "...(truncated)";
            }
            return json;
        } catch (Exception e) {
            logger.warn("序列化参数失败: {}", e.getMessage());
            return null;
        }
    }
    
    private String serializeResult(Object result) {
        try {
            if (result == null) return null;
            String json = objectMapper.writeValueAsString(result);
            if (json.length() > MAX_RESULT_LENGTH) {
                return json.substring(0, MAX_RESULT_LENGTH) + "...(truncated)";
            }
            return json;
        } catch (Exception e) {
            logger.warn("序列化结果失败: {}", e.getMessage());
            return null;
        }
    }
    
    private Mono<Void> saveLogAsync(cn.novalon.gym.manage.sys.audit.OperationLog annotation, 
                                     String username, String ip, String method, 
                                     String params, Object result, long duration, 
                                     String status, String errorMsg) {
        OperationLog log = new OperationLog();
        log.setUsername(username);
        log.setOperation(annotation.module() + " - " + annotation.operation());
        log.setMethod(method);
        log.setParams(params);
        log.setResult(serializeResult(result));
        log.setIp(ip);
        log.setDuration(duration);
        log.setStatus(status);
        log.setErrorMsg(errorMsg);
        
        return logService.save(log)
                .doOnSuccess(saved -> logger.debug("操作日志保存成功: {} - {}", 
                        annotation.module(), annotation.operation()))
                .doOnError(error -> logger.error("操作日志保存失败: {}", error.getMessage()))
                .then();
    }
}
