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 jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Order(Ordered.LOWEST_PRECEDENCE)
public class OperationLogWebFilter implements WebFilter {

    private static final Logger logger = LoggerFactory.getLogger(OperationLogWebFilter.class);

    private final IOperationLogService operationLogService;
    private final ObjectMapper objectMapper;

    private static final Map<String, OperationInfo> OPERATION_MAPPING = new ConcurrentHashMap<>();

    static {
        OPERATION_MAPPING.put("POST:/api/roles", new OperationInfo("角色管理", "创建角色"));
        OPERATION_MAPPING.put("PUT:/api/roles/", new OperationInfo("角色管理", "更新角色"));
        OPERATION_MAPPING.put("DELETE:/api/roles/", new OperationInfo("角色管理", "删除角色"));
        OPERATION_MAPPING.put("POST:/api/users", new OperationInfo("用户管理", "创建用户"));
        OPERATION_MAPPING.put("PUT:/api/users/", new OperationInfo("用户管理", "更新用户"));
        OPERATION_MAPPING.put("DELETE:/api/users/", new OperationInfo("用户管理", "删除用户"));
        OPERATION_MAPPING.put("POST:/api/users/", new OperationInfo("用户管理", "用户操作"));
        OPERATION_MAPPING.put("POST:/api/menus", new OperationInfo("菜单管理", "创建菜单"));
        OPERATION_MAPPING.put("PUT:/api/menus/", new OperationInfo("菜单管理", "更新菜单"));
        OPERATION_MAPPING.put("DELETE:/api/menus/", new OperationInfo("菜单管理", "删除菜单"));
    }

    public OperationLogWebFilter(IOperationLogService operationLogService, ObjectMapper objectMapper) {
        logger.info("=== OperationLogWebFilter 构造函数被调用 ===");
        this.operationLogService = operationLogService;
        this.objectMapper = objectMapper;
    }

    @PostConstruct
    public void init() {
        logger.info("=== OperationLogWebFilter 初始化 ===");
        logger.info("操作日志映射配置数量: {}", OPERATION_MAPPING.size());
        OPERATION_MAPPING.forEach((key, value) -> {
            logger.info("  {} -> {}:{}", key, value.module, value.operation);
        });
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String method = request.getMethod().name();
        String path = request.getPath().value();

        logger.info("WebFilter 拦截请求: {} {}", method, path);

        OperationInfo operationInfo = findOperationInfo(method, path);

        if (operationInfo == null) {
            logger.info("未匹配到操作日志配置，跳过: {} {}", method, path);
            return chain.filter(exchange);
        }

        logger.info("匹配到操作日志配置: {} {} -> {}:{}", method, path, operationInfo.module, operationInfo.operation);

        long startTime = System.currentTimeMillis();
        String ip = IpUtils.getClientIp(request);

        return Mono.deferContextual(contextView -> {
            return chain.filter(exchange)
                    .then(Mono.defer(() -> {
                        long duration = System.currentTimeMillis() - startTime;
                        logger.info("请求处理完成，准备保存操作日志: {} {}, 耗时: {}ms", method, path, duration);

                        return ReactiveSecurityContextHolder.getContext()
                                .flatMap(securityContext -> {
                                    Object principal = securityContext.getAuthentication().getPrincipal();
                                    String username = principal instanceof String ? (String) principal : "system";
                                    logger.info("获取到用户名: {}", username);
                                    return Mono.just(username);
                                })
                                .defaultIfEmpty("system")
                                .flatMap(username -> {
                                    logger.info("开始保存操作日志: 用户={}, 操作={}", username,
                                            operationInfo.module + " - " + operationInfo.operation);

                                    OperationLog log = new OperationLog();
                                    log.setUsername(username);
                                    log.setOperation(operationInfo.module + " - " + operationInfo.operation);
                                    log.setMethod(method + " " + path);
                                    log.setParams(null);
                                    log.setIp(ip);
                                    log.setDuration(duration);
                                    log.setStatus("0");

                                    return operationLogService.save(log)
                                            .doOnSuccess(saved -> logger.info("操作日志保存成功: {} - {}",
                                                    operationInfo.module, operationInfo.operation))
                                            .doOnError(e -> logger.error("操作日志保存失败: {}", e.getMessage(), e))
                                            .onErrorResume(e -> Mono.empty());
                                })
                                .then();
                    }))
                    .onErrorResume(error -> {
                        long duration = System.currentTimeMillis() - startTime;
                        logger.error("请求处理失败: {} {}, 错误: {}", method, path, error.getMessage());

                        return ReactiveSecurityContextHolder.getContext()
                                .flatMap(securityContext -> {
                                    Object principal = securityContext.getAuthentication().getPrincipal();
                                    String username = principal instanceof String ? (String) principal : "system";
                                    return Mono.just(username);
                                })
                                .defaultIfEmpty("system")
                                .flatMap(username -> {
                                    OperationLog log = new OperationLog();
                                    log.setUsername(username);
                                    log.setOperation(operationInfo.module + " - " + operationInfo.operation);
                                    log.setMethod(method + " " + path);
                                    log.setParams(null);
                                    log.setIp(ip);
                                    log.setDuration(duration);
                                    log.setStatus("1");
                                    log.setErrorMsg(error.getMessage());

                                    return operationLogService.save(log)
                                            .doOnError(e -> logger.error("错误日志保存失败: {}", e.getMessage()))
                                            .onErrorResume(e -> Mono.empty());
                                })
                                .then(Mono.error(error));
                    });
        });
    }

    private OperationInfo findOperationInfo(String method, String path) {
        String key = method + ":" + path;
        if (OPERATION_MAPPING.containsKey(key)) {
            return OPERATION_MAPPING.get(key);
        }

        for (Map.Entry<String, OperationInfo> entry : OPERATION_MAPPING.entrySet()) {
            String mappingKey = entry.getKey();
            if (key.startsWith(mappingKey)) {
                return entry.getValue();
            }
        }

        return null;
    }

    private static class OperationInfo {
        final String module;
        final String operation;

        OperationInfo(String module, String operation) {
            this.module = module;
            this.operation = operation;
        }
    }
}
