package cn.novalon.manage.sys.handler.auth;

import cn.novalon.manage.sys.dto.request.LoginRequest;
import cn.novalon.manage.sys.dto.request.UserRegisterRequest;
import cn.novalon.manage.sys.dto.response.AuthResponse;
import cn.novalon.manage.sys.security.JwtTokenProvider;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.core.domain.SysLoginLog;
import cn.novalon.manage.sys.core.service.ISysUserService;
import cn.novalon.manage.sys.core.service.ISysLoginLogService;
import cn.novalon.manage.sys.util.UserAgentParser;
import cn.novalon.manage.sys.util.IpLocationParser;
import cn.novalon.manage.common.util.StatusConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 认证处理器
 * 
 * 文件定义：处理用户登录、注册等认证相关的HTTP请求
 * 涉及业务：用户登录、用户注册、Token生成、密码验证
 * 算法：使用BCrypt验证密码，使用JWT生成Token
 * 
 * @author 张翔
 * @date 2026-03-13
 */
@Component
@Tag(name = "认证管理", description = "登录认证相关操作")
public class SysAuthHandler {

        private static final Logger logger = LoggerFactory.getLogger(SysAuthHandler.class);
        private final ISysUserService userService;
        private final PasswordEncoder passwordEncoder;
        private final JwtTokenProvider jwtTokenProvider;
        private final ISysLoginLogService loginLogService;
        private final UserAgentParser userAgentParser;
        private final IpLocationParser ipLocationParser;

        // 使用多个编码器来支持不同的 BCrypt 版本和 strength
        private static final BCryptPasswordEncoder directEncoder10 = new BCryptPasswordEncoder(10);
        private static final BCryptPasswordEncoder directEncoder12 = new BCryptPasswordEncoder(12);

        public SysAuthHandler(ISysUserService userService,
                        @Qualifier("passwordEncoder") PasswordEncoder passwordEncoder,
                        JwtTokenProvider jwtTokenProvider, ISysLoginLogService loginLogService,
                        UserAgentParser userAgentParser, IpLocationParser ipLocationParser) {
                this.userService = userService;
                this.passwordEncoder = passwordEncoder;
                this.jwtTokenProvider = jwtTokenProvider;
                this.loginLogService = loginLogService;
                this.userAgentParser = userAgentParser;
                this.ipLocationParser = ipLocationParser;

                logger.info("SysAuthHandler使用的密码编码器类型: {}", passwordEncoder.getClass().getName());

                // 测试编码器
                String testPassword = "test123";
                String testHash10 = directEncoder10.encode(testPassword);
                String testHash12 = directEncoder12.encode(testPassword);
                logger.info("DirectEncoder10测试: 密码={}, 哈希={}, 前缀={}",
                                testPassword, testHash10.substring(0, 10), testHash10.substring(0, 7));
                logger.info("DirectEncoder12测试: 密码={}, 哈希={}, 前缀={}",
                                testPassword, testHash12.substring(0, 10), testHash12.substring(0, 7));
        }

        @Operation(summary = "用户登录", description = "使用用户名和密码登录系统")
        public Mono<ServerResponse> login(ServerRequest request) {
                return request.bodyToMono(LoginRequest.class)
                                .filter(loginRequest -> loginRequest.getUsername() != null
                                                && !loginRequest.getUsername().trim().isEmpty())
                                .switchIfEmpty(Mono.error(new IllegalArgumentException("用户名不能为空")))
                                .filter(loginRequest -> loginRequest.getPassword() != null
                                                && !loginRequest.getPassword().trim().isEmpty())
                                .switchIfEmpty(Mono.error(new IllegalArgumentException("密码不能为空")))
                                .flatMap(loginRequest -> {
                                        logger.info("用户登录请求: username={}", loginRequest.getUsername());
                                        String clientIp = getClientIp(request);
                                        String userAgent = request.headers().firstHeader("User-Agent");
                                        return userService.findByUsername(loginRequest.getUsername())
                                                        .flatMap(user -> {
                                                                // 使用注入的密码编码器验证密码
                                                                boolean passwordMatches = passwordEncoder.matches(
                                                                                loginRequest.getPassword(),
                                                                                user.getPassword());

                                                                if (passwordMatches) {
                                                                        logger.info("密码验证成功: username={}",
                                                                                        loginRequest.getUsername());
                                                                }

                                                                if (!passwordMatches) {
                                                                        logger.warn("用户登录失败: username={}, reason=密码错误",
                                                                                        loginRequest.getUsername());
                                                                        recordLoginLog(loginRequest.getUsername(),
                                                                                        clientIp, "1", "密码错误",
                                                                                        userAgent);
                                                                        return Mono.error(new RuntimeException(
                                                                                        "用户名或密码错误"));
                                                                }

                                                                if (user.getStatus() != 1) {
                                                                        logger.warn("用户登录失败: username={}, reason=用户已禁用",
                                                                                        loginRequest.getUsername());
                                                                        recordLoginLog(loginRequest.getUsername(),
                                                                                        clientIp, "1", "用户已禁用",
                                                                                        userAgent);
                                                                        return Mono.error(new RuntimeException(
                                                                                        "用户名或密码错误"));
                                                                }

                                                                return userService.getUserRoles(user.getId())
                                                                                .map(role -> role.getRoleKey())
                                                                                .collectList()
                                                                                .flatMap(roleKeys -> {
                                                                                        String token = jwtTokenProvider
                                                                                                        .generateToken(
                                                                                                                        user.getUsername(),
                                                                                                                        user.getId(),
                                                                                                                        roleKeys);
                                                                                        logger.info("用户登录成功: username={}, userId={}, roles={}",
                                                                                                        user.getUsername(),
                                                                                                        user.getId(),
                                                                                                        roleKeys);
                                                                                        recordLoginLog(loginRequest
                                                                                                        .getUsername(),
                                                                                                        clientIp,
                                                                                                        "0", "登录成功",
                                                                                                        userAgent);
                                                                                        AuthResponse response = new AuthResponse(
                                                                                                        token,
                                                                                                        user.getId(),
                                                                                                        user.getUsername());
                                                                                        return ServerResponse.ok()
                                                                                                        .bodyValue(response);
                                                                                });
                                                        })
                                                        .switchIfEmpty(Mono.defer(() -> {
                                                                logger.warn("用户登录失败: username={}, reason=用户不存在",
                                                                                loginRequest.getUsername());
                                                                recordLoginLog(loginRequest.getUsername(), clientIp,
                                                                                "1", "用户不存在", userAgent);
                                                                return Mono.error(new RuntimeException("用户名或密码错误"));
                                                        }));
                                })
                                .onErrorResume(WebExchangeBindException.class, ex -> {
                                        String errorMessage = ex.getBindingResult().getFieldErrors().stream()
                                                        .map(FieldError::getDefaultMessage)
                                                        .collect(Collectors.joining(", "));
                                        logger.warn("用户登录请求参数验证失败: {}", errorMessage);
                                        return ServerResponse.badRequest().bodyValue(Map.of(
                                                        "code", HttpStatus.BAD_REQUEST.value(),
                                                        "message", errorMessage,
                                                        "timestamp", LocalDateTime.now()));
                                })
                                .onErrorResume(IllegalArgumentException.class, ex -> {
                                        logger.warn("用户登录请求参数错误: {}", ex.getMessage());
                                        return ServerResponse.badRequest().bodyValue(Map.of(
                                                        "code", HttpStatus.BAD_REQUEST.value(),
                                                        "message", ex.getMessage(),
                                                        "timestamp", LocalDateTime.now()));
                                })
                                .onErrorResume(RuntimeException.class, ex -> {
                                        if ("用户名或密码错误".equals(ex.getMessage())) {
                                                return ServerResponse.status(HttpStatus.UNAUTHORIZED)
                                                                .contentType(MediaType.APPLICATION_JSON)
                                                                .bodyValue(Map.of(
                                                                                "code", HttpStatus.UNAUTHORIZED.value(),
                                                                                "message", "用户名或密码错误",
                                                                                "timestamp", LocalDateTime.now()));
                                        }
                                        logger.error("用户登录发生未预期的错误", ex);
                                        return Mono.error(ex);
                                });
        }

        private void recordLoginLog(String username, String ip, String status, String message, String userAgent) {
                try {
                        SysLoginLog loginLog = new SysLoginLog();
                        loginLog.setUsername(username);
                        loginLog.setIp(ip);
                        loginLog.setLocation(ipLocationParser.parseLocation(ip));
                        loginLog.setStatus(status);
                        loginLog.setMessage(message);
                        loginLog.setLoginTime(LocalDateTime.now());

                        if (userAgent != null && !userAgent.isEmpty()) {
                                loginLog.setBrowser(userAgentParser.parseBrowser(userAgent));
                                loginLog.setOs(userAgentParser.parseOS(userAgent));
                        }

                        loginLogService.save(loginLog)
                                        .doOnSuccess(saved -> logger.debug("登录日志记录成功: username={}, status={}", username,
                                                        status))
                                        .doOnError(error -> logger.error("登录日志记录失败: {}", error.getMessage()))
                                        .subscribe();
                } catch (Exception e) {
                        logger.error("记录登录日志时发生异常: {}", e.getMessage());
                }
        }

        private String getClientIp(ServerRequest request) {
                String ip = request.headers().firstHeader("X-Forwarded-For");
                if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                        ip = request.headers().firstHeader("X-Real-IP");
                }
                if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                        ip = request.headers().firstHeader("Proxy-Client-IP");
                }
                if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                        ip = request.headers().firstHeader("WL-Proxy-Client-IP");
                }
                if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                        ip = request.remoteAddress().map(addr -> addr.getAddress().getHostAddress()).orElse("");
                }
                if (ip != null && ip.contains(",")) {
                        ip = ip.split(",")[0].trim();
                }
                return ip;
        }

        @Operation(summary = "用户注册", description = "注册新用户")
        public Mono<ServerResponse> register(ServerRequest request) {
                return request.bodyToMono(UserRegisterRequest.class)
                                .flatMap(registerRequest -> {
                                        logger.info("用户注册请求: username={}, email={}",
                                                        registerRequest.getUsername(), registerRequest.getEmail());

                                        return userService.findByUsername(registerRequest.getUsername())
                                                        .flatMap(existing -> {
                                                                logger.warn("用户注册失败: username={}, reason=用户名已存在",
                                                                                registerRequest.getUsername());
                                                                return Mono.<ServerResponse>error(
                                                                                new RuntimeException("用户名已存在"));
                                                        })
                                                        .switchIfEmpty(Mono.defer(() -> {
                                                                SysUser user = new SysUser();
                                                                user.setUsername(registerRequest.getUsername());
                                                                String encodedPassword = passwordEncoder
                                                                                .encode(registerRequest.getPassword());
                                                                logger.info("密码编码结果: {} (前缀: {})",
                                                                                encodedPassword.substring(0, 10),
                                                                                encodedPassword.substring(0, 7));
                                                                user.setPassword(encodedPassword);
                                                                user.setEmail(registerRequest.getEmail());
                                                                user.setCreatedAt(LocalDateTime.now());
                                                                if (user.getStatus() == null) {
                                                                        user.setStatus(StatusConstants.ENABLED);
                                                                }
                                                                return userService.createUser(user)
                                                                                .flatMap(u -> {
                                                                                        logger.info("用户注册成功: username={}, userId={}, password={}",
                                                                                                        u.getUsername(),
                                                                                                        u.getId(),
                                                                                                        u.getPassword().substring(
                                                                                                                        0,
                                                                                                                        10));
                                                                                        return ServerResponse
                                                                                                        .status(HttpStatus.CREATED)
                                                                                                        .bodyValue(u);
                                                                                });
                                                        }));
                                });
        }

        @Operation(summary = "用户登出", description = "用户登出系统")
        public Mono<ServerResponse> logout(ServerRequest request) {
                return ServerResponse.ok().build();
        }
}
