package org.xyzh.auth.service.impl;

import com.alibaba.fastjson2.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.xyzh.api.auth.service.AuthService;
import org.xyzh.api.system.service.SysUserService;
import org.xyzh.api.system.service.ModulePermissionService;
import org.xyzh.api.system.service.GuestService;
import org.xyzh.api.system.vo.SysUserVO;
import org.xyzh.api.system.vo.PermissionVO;
import org.xyzh.api.system.vo.UserDeptRoleVO;
import org.xyzh.common.dto.sys.TbGuestDTO;
import org.xyzh.auth.enums.UserStatus;
import org.xyzh.auth.strategy.LoginStrategyFactory;
import org.xyzh.auth.strategy.LoginStrategy;
import org.xyzh.auth.utils.CapcatUtils;
import org.xyzh.common.auth.utils.JwtTokenUtil;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.core.domain.LoginParam;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.dto.sys.TbSysUserDTO;
import org.xyzh.common.dto.sys.TbSysUserInfoDTO;
import org.xyzh.common.dto.sys.TbSysUserRoleDTO;
import org.xyzh.common.dto.sys.TbSysDeptDTO;
import org.xyzh.common.dto.sys.TbSysPermissionDTO;
import org.xyzh.common.dto.sys.TbSysViewDTO;
import org.xyzh.common.redis.service.RedisService;
import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.common.utils.crypto.AesEncryptUtil;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;
import java.util.List;
import java.util.ArrayList;

/**
 * @description    AuthServiceImpl.java文件描述 认证服务实现类
 * @filename    AuthServiceImpl.java
 * @author    yslg
 * @copyright yslg
 * @since 2025-11-09
 */
@DubboService(
    version = "1.0.0",
    group = "auth",
    timeout = 3000,
    retries = 0
)
public class AuthServiceImpl implements AuthService{

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

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private RedisService redisService;


    @DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false, retries = 0)
    private SysUserService userService;

    @DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false, retries = 0)
    private ModulePermissionService modulePermissionService;

    @DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false, retries = 0)
    private GuestService guestService;

    @Autowired
    private LoginStrategyFactory loginStrategyFactory;

    @Autowired
    private AesEncryptUtil aesEncryptUtil;

    @Override
    public ResultDomain<LoginDomain> getCaptcha(LoginParam loginParam) {
        try {
            String captchaType = loginParam.getLoginType(); // "image", "sms", "email"
            
            switch (captchaType) {
                case "image":
                    // 生成图片验证码
                    String captchaId = IdUtil.generateID();
                    String captchaCode = CapcatUtils.generateVerificationCode();
                    
                    // 存储验证码到Redis，有效期5分钟
                    String captchaKey = "captcha:image:" + captchaId;
                    redisService.set(captchaKey, captchaCode, 5, TimeUnit.MINUTES);
                    
                    LoginDomain captchaResult = new LoginDomain();
                    captchaResult.setToken(captchaId); // 使用token字段存储captchaId
                    captchaResult.setLoginType("image");
                    
                    return ResultDomain.success("图片验证码生成成功", captchaResult);
                    
                case "sms":
                    if (loginParam.getPhone() == null || loginParam.getPhone().trim().isEmpty()) {
                        return ResultDomain.failure("手机号不能为空");
                    }
                    
                    String smsSessionId = IdUtil.generateID();
                    String smsCode = CapcatUtils.generateVerificationCode();
                    
                    // 存储短信验证码到Redis，有效期5分钟
                    String smsKey = "captcha:sms:" + smsSessionId;
                    String smsValue = loginParam.getPhone() + ":" + smsCode;
                    redisService.set(smsKey, smsValue, 5, TimeUnit.MINUTES);
                    
                    LoginDomain smsResult = new LoginDomain();
                    smsResult.setToken(smsSessionId);
                    smsResult.setLoginType("sms");
                    
                    // 这里应该调用短信服务发送验证码，暂时先返回
                    logger.info("短信验证码已生成：phone={}, code={}", loginParam.getPhone(), smsCode);
                    
                    return ResultDomain.success("短信验证码发送成功", smsResult);
                    
                case "email":
                    if (loginParam.getEmail() == null || loginParam.getEmail().trim().isEmpty()) {
                        return ResultDomain.failure("邮箱不能为空");
                    }
                    
                    String emailSessionId = IdUtil.generateID();
                    String emailCode = CapcatUtils.generateVerificationCode();
                    
                    // 存储邮箱验证码到Redis，有效期5分钟
                    String emailKey = "captcha:email:" + emailSessionId;
                    String emailValue = loginParam.getEmail() + ":" + emailCode;
                    redisService.set(emailKey, emailValue, 5, TimeUnit.MINUTES);
                    
                    LoginDomain emailResult = new LoginDomain();
                    emailResult.setToken(emailSessionId);
                    emailResult.setLoginType("email");
                    
                    // 这里应该调用邮件服务发送验证码，暂时先返回
                    logger.info("邮箱验证码已生成：email={}, code={}", loginParam.getEmail(), emailCode);
                    
                    return ResultDomain.success("邮箱验证码发送成功", emailResult);
                    
                default:
                    return ResultDomain.failure("不支持的验证码类型");
            }
            
        } catch (Exception e) {
            logger.error("验证码生成失败", e);
            return ResultDomain.failure("验证码生成失败: " + e.getMessage());
        }
    }

    @Override
    public ResultDomain<LoginDomain> getLoginByToken(String token) {
        try {
            if (token == null || token.trim().isEmpty()) {
                return ResultDomain.failure("Token不能为空");
            }
            
            // 1. 验证Token格式和是否过期
            if (jwtTokenUtil.isTokenExpired(token)) {
                return ResultDomain.failure("Token已过期");
            }
            
            // 2. 从Redis获取登录信息
            String loginKey = "login:token:" + token;
            String loginJson = redisService.get(loginKey, String.class);
            if (loginJson == null) {
                return ResultDomain.failure("登录信息已失效");
            }
            
            LoginDomain loginDomain = JSON.parseObject(loginJson, LoginDomain.class);
            
            // 3. 验证Token中的用户ID与Redis中的是否匹配
            if (loginDomain.getUser() != null) {
                String tokenUserId = jwtTokenUtil.getUserIdFromToken(token);
                if (!loginDomain.getUser().getUserId().equals(tokenUserId)) {
                    return ResultDomain.failure("Token验证失败");
                }
            }
            
            return ResultDomain.success("获取登录信息成功", loginDomain);
            
        } catch (Exception e) {
            logger.error("获取登录信息失败", e);
            return ResultDomain.failure("获取登录信息失败: " + e.getMessage());
        }
    }

    @Override
    public ResultDomain<Boolean> isTokenValid(String token) {
        try {
            if (token == null || token.trim().isEmpty()) {
                return ResultDomain.success("Token验证", false);
            }
            
            // 1. 检查Token格式和是否过期
            if (jwtTokenUtil.isTokenExpired(token)) {
                return ResultDomain.success("Token验证", false);
            }
            
            // 2. 检查Redis中是否存在登录信息
            String loginKey = "login:token:" + token;
            boolean exists = redisService.hasKey(loginKey);
            
            return ResultDomain.success("Token验证", exists);
            
        } catch (Exception e) {
            logger.error("Token验证失败", e);
            return ResultDomain.success("Token验证", false);
        }
    }

    @Override
    public ResultDomain<LoginDomain> login(LoginParam loginParam) {
        String loginType = loginParam.getLoginType();
        String loginAttempt = IdUtil.generateID(); // 生成登录尝试ID
        
        try {
            logger.info("用户登录请求：loginType={}, username={}, attempt={}", loginType, loginParam.getUsername(), loginAttempt);
            
            // 1. 获取对应的登录策略
            LoginStrategy strategy = loginStrategyFactory.getStrategy(loginType);
            
            // 2. 验证登录参数
            if (!strategy.validate(loginParam)) {
                logLoginAttempt(loginParam, null, false, loginAttempt, "登录参数不正确");
                return ResultDomain.failure("登录参数不正确");
            }
            
            // 3. 查找用户
            SysUserVO user = strategy.findUser(loginParam);
            if (user == null) {
                logLoginAttempt(loginParam, null, false, loginAttempt, "用户不存在");
                return ResultDomain.failure("用户不存在");
            }
            
            // 4. 检查用户状态
            UserStatus userStatus = UserStatus.fromCode(user.getStatus());
            if (!userStatus.isNormal()) {
                logLoginAttempt(loginParam, user, false, loginAttempt, "用户状态异常: " + userStatus.getName());
                return ResultDomain.failure("用户状态异常: " + userStatus.getName());
            }
            
            // 5. 验证凭据
            if ("password".equals(loginType)) {
                String pwd = aesEncryptUtil.decrypt(loginParam.getPassword());
                if (!strategy.verifyCredential(pwd, user.getPassword())) {
                    logLoginAttempt(loginParam, user, false, loginAttempt, "密码错误");
                    return ResultDomain.failure("密码错误");
                }
            } else if ("email".equals(loginType) || "phone".equals(loginType)) {
                if (!strategy.verifyCaptchaWithSession(loginParam)) {
                    logLoginAttempt(loginParam, user, false, loginAttempt, "验证码验证失败");
                    return ResultDomain.failure("验证码错误或已过期");
                }
            }
            
            // 6. 获取用户详细信息
            ResultDomain<SysUserVO> userInfoResult = userService.getUserInfo(user.getUserId());
            if (!userInfoResult.getSuccess() || userInfoResult.getData() == null) {
                logLoginAttempt(loginParam, user, false, loginAttempt, "获取用户信息失败");
                return ResultDomain.failure("获取用户信息失败");
            }
            
            SysUserVO userInfo = userInfoResult.getData();
            
            // 7. 构建完整的登录域对象
            LoginDomain loginDomain = buildLoginDomain(userInfo, loginType, loginParam.getClientIp());
            if (loginDomain == null) {
                logLoginAttempt(loginParam, user, false, loginAttempt, "构建登录信息失败");
                return ResultDomain.failure("构建登录信息失败");
            }
            
            // 8. 生成 JWT Token
            String token = jwtTokenUtil.generateToken(loginDomain);
            loginDomain.setToken(token);
            
            // 9. 将登录信息存储到 Redis
            String loginKey = "login:token:" + token;
            redisService.set(loginKey, JSON.toJSONString(loginDomain), 24, TimeUnit.HOURS);
            
            // 10. 存储用户登录状态
            String userLoginKey = "login:user:" + user.getUserId();
            redisService.set(userLoginKey, token, 24, TimeUnit.HOURS);
            
            // 11. 记录成功日志
            logLoginAttempt(loginParam, user, true, loginAttempt, "登录成功");
            logger.info("用户登录成功：userId={}, username={}, loginType={}, attempt={}", user.getUserId(), userInfo.getUsername(), loginType, loginAttempt);
            
            return ResultDomain.success("登录成功", loginDomain);
            
        } catch (Exception e) {
            logger.error("用户登录失败：attempt={}", loginAttempt, e);
            logLoginAttempt(loginParam, null, false, loginAttempt, "系统异常: " + e.getMessage());
            return ResultDomain.failure("登录失败: " + e.getMessage());
        }
    }

    /**
     * 构建完整的LoginDomain对象
     * @param userInfo 用户信息
     * @param loginType 登录类型
     * @param clientIp 客户端IP地址
     * @return LoginDomain 登录域对象
     */
    public LoginDomain buildLoginDomain(SysUserVO userInfo, String loginType, String clientIp) {
        try {
            // 1. 转换为 DTO 对象
            TbSysUserDTO userDTO = SysUserVO.toDTO(userInfo);
            TbSysUserInfoDTO userInfoDTO = SysUserVO.toUserInfoDTO(userInfo);
            
            // 2. 获取用户角色和部门信息
            List<TbSysUserRoleDTO> userRoles = new ArrayList<>();
            List<TbSysDeptDTO> userDepts = new ArrayList<>();
            
            ResultDomain<UserDeptRoleVO> userDeptRoleResult = userService.getUserWithDeptRole(userInfo.getUserId());
            if (userDeptRoleResult.getSuccess() && userDeptRoleResult.getDataList() != null) {
                for (UserDeptRoleVO deptRole : userDeptRoleResult.getDataList()) {
                    // 添加用户角色
                    if (deptRole.getRoleId() != null) {
                        TbSysUserRoleDTO userRole = new TbSysUserRoleDTO();
                        userRole.setUserId(deptRole.getUserId());
                        userRole.setRoleId(deptRole.getRoleId());
                        userRole.setCreateTime(deptRole.getCreateTime());
                        userRole.setUpdateTime(deptRole.getUpdateTime());
                        userRoles.add(userRole);
                    }
                    
                    // 添加用户部门
                    if (deptRole.getDeptId() != null) {
                        TbSysDeptDTO userDept = new TbSysDeptDTO();
                        userDept.setDeptId(deptRole.getDeptId());
                        userDept.setName(deptRole.getDeptName());
                        userDept.setParentId(deptRole.getParentId());
                        userDept.setDescription(deptRole.getDeptDescription());
                        userDepts.add(userDept);
                    }
                }
            }
            
            // 3. 获取用户权限信息
            List<TbSysPermissionDTO> userPermissions = new ArrayList<>();
            List<TbSysViewDTO> userViews = new ArrayList<>();
            
            ResultDomain<PermissionVO> permissionsResult = modulePermissionService.getUserPermissions(userInfo.getUserId());
            if (permissionsResult.getSuccess() && permissionsResult.getDataList() != null) {
                for (PermissionVO permission : permissionsResult.getDataList()) {
                    // 添加权限信息
                    if (permission.getPermissionId() != null) {
                        TbSysPermissionDTO permissionDTO = PermissionVO.toPermissionDTO(permission);
                        if (permissionDTO != null) {
                            userPermissions.add(permissionDTO);
                        }
                    }
                    
                    // 添加视图信息  
                    if (permission.getViewId() != null) {
                        TbSysViewDTO viewDTO = PermissionVO.toViewDTO(permission);
                        if (viewDTO != null) {
                            userViews.add(viewDTO);
                        }
                    }
                }
            }
            
            // 4. 创建登录域对象
            LoginDomain loginDomain = new LoginDomain();
            loginDomain.setUser(userDTO);
            loginDomain.setUserInfo(userInfoDTO);
            loginDomain.setUserRoles(userRoles);
            loginDomain.setUserDepts(userDepts);
            loginDomain.setUserPermissions(userPermissions);
            loginDomain.setUserViews(userViews);
            loginDomain.setLoginType(loginType);
            loginDomain.setIpAddress(clientIp);
            
            return loginDomain;
            
        } catch (Exception e) {
            logger.error("构建LoginDomain失败：userId={}", userInfo.getUserId(), e);
            return null;
        }
    }
    
    /**
     * 记录登录尝试日志
     * @param loginParam 登录参数
     * @param user 用户信息（可能为null）
     * @param success 是否成功
     * @param attemptId 尝试ID
     * @param message 消息
     */
    private void logLoginAttempt(LoginParam loginParam, SysUserVO user, boolean success, String attemptId, String message) {
        try {
            String userId = user != null ? user.getUserId() : "unknown";
            String username = loginParam.getUsername() != null ? loginParam.getUsername() : 
                            (loginParam.getPhone() != null ? loginParam.getPhone() : 
                            (loginParam.getEmail() != null ? loginParam.getEmail() : "unknown"));
            
            if (success) {
                logger.info("登录成功 - 用户ID: {}, 用户名: {}, 登录类型: {}, 尝试ID: {}, 消息: {}", 
                          userId, username, loginParam.getLoginType(), attemptId, message);
            } else {
                logger.warn("登录失败 - 用户名: {}, 登录类型: {}, 尝试ID: {}, 原因: {}", 
                          username, loginParam.getLoginType(), attemptId, message);
            }
            
        } catch (Exception e) {
            logger.error("记录登录日志失败：attemptId={}", attemptId, e);
        }
    }

    @Override
    public ResultDomain<LoginDomain> refreshToken(String token, String clientIp) {
        try {
            if (token == null || token.trim().isEmpty()) {
                return ResultDomain.failure("Token不能为空");
            }

            // 1. 验证当前Token是否有效
            if (jwtTokenUtil.isTokenExpired(token)) {
                return ResultDomain.failure("Token已过期");
            }

            // 2. 从JWT中提取用户ID和登录类型
            String userId = jwtTokenUtil.getUserIdFromToken(token);
            String loginType = jwtTokenUtil.getClaimFromToken(token, claims -> claims.get("loginType", String.class));

            if (userId == null || userId.trim().isEmpty()) {
                return ResultDomain.failure("Token信息不完整");
            }

            logger.info("Token刷新请求：userId={}, loginType={}", userId, loginType);

            // 3. 尝试从Redis获取登录信息（优先使用缓存）
            String loginKey = "login:token:" + token;
            String loginJson = redisService.get(loginKey, String.class);
            LoginDomain oldLoginDomain = null;

            if (loginJson != null) {
                oldLoginDomain = JSON.parseObject(loginJson, LoginDomain.class);
                if (loginType == null && oldLoginDomain != null) {
                    loginType = oldLoginDomain.getLoginType();
                }
            }

            // 4. 根据登录类型获取最新用户信息
            LoginDomain newLoginDomain;

            if ("wechat_miniprogram".equals(loginType)) {
                // 来客（小程序）用户：从guest表获取信息
                TbGuestDTO guestDTO = new TbGuestDTO();
                guestDTO.setUserId(userId);
                ResultDomain<TbGuestDTO> guestResult = guestService.selectGuestOne(guestDTO);
                if (!guestResult.getSuccess() || guestResult.getData() == null) {
                    return ResultDomain.failure("获取来客信息失败");
                }

                TbGuestDTO guest = guestResult.getData();
                newLoginDomain = buildGuestLoginDomain(guest, loginType);

            } else {
                // 普通用户：从sys_user表获取信息
                ResultDomain<SysUserVO> userInfoResult = userService.getUserInfo(userId);
                if (!userInfoResult.getSuccess() || userInfoResult.getData() == null) {
                    return ResultDomain.failure("获取用户信息失败");
                }

                SysUserVO userInfo = userInfoResult.getData();
                String effectiveLoginType = (loginType != null) ? loginType : "password";
                newLoginDomain = buildLoginDomain(userInfo, effectiveLoginType, clientIp);
            }

            if (newLoginDomain == null) {
                return ResultDomain.failure("构建登录信息失败");
            }

            // 5. 生成新Token
            String newToken = jwtTokenUtil.generateToken(newLoginDomain);
            newLoginDomain.setToken(newToken);

            // 6. 删除旧的Token信息（如果存在）
            if (loginJson != null) {
                redisService.delete(loginKey);
            }

            // 7. 存储新的登录信息
            String newLoginKey = "login:token:" + newToken;
            redisService.set(newLoginKey, JSON.toJSONString(newLoginDomain), 24, TimeUnit.HOURS);

            // 8. 更新用户登录状态
            String userLoginKey = "login:user:" + userId;
            redisService.set(userLoginKey, newToken, 24, TimeUnit.HOURS);

            logger.info("Token刷新成功：userId={}, loginType={}, oldToken={}, newToken={}",
                       userId,
                       loginType,
                       token.substring(0, Math.min(10, token.length())) + "...",
                       newToken.substring(0, Math.min(10, newToken.length())) + "...");

            return ResultDomain.success("Token刷新成功", newLoginDomain);

        } catch (Exception e) {
            logger.error("Token刷新失败", e);
            return ResultDomain.failure("Token刷新失败: " + e.getMessage());
        }
    }

    /**
     * 从来客信息构造LoginDomain（用于token刷新）
     */
    private LoginDomain buildGuestLoginDomain(TbGuestDTO guest, String loginType) {
        try {
            LoginDomain loginDomain = new LoginDomain();

            // 构造TbSysUserDTO，status设为guest
            TbSysUserDTO userDTO = new TbSysUserDTO();
            userDTO.setUserId(guest.getUserId());
            userDTO.setPhone(guest.getPhone());
            userDTO.setEmail(guest.getEmail());
            userDTO.setWechatId(guest.getWechatId());
            userDTO.setStatus("guest");  // 来客特殊状态
            loginDomain.setUser(userDTO);

            // 构造TbSysUserInfoDTO
            TbSysUserInfoDTO userInfoDTO = new TbSysUserInfoDTO();
            userInfoDTO.setUserId(guest.getUserId());
            userInfoDTO.setUsername(guest.getName() != null ? guest.getName() : "来客");
            loginDomain.setUserInfo(userInfoDTO);

            // 设置角色信息
            List<TbSysUserRoleDTO> userRoles = new ArrayList<>();
            TbSysUserRoleDTO userRole = new TbSysUserRoleDTO();
            userRole.setUserId(guest.getUserId());
            userRole.setRoleId("role_guest");
            userRole.setDeptId("dept_root");
            userRoles.add(userRole);
            loginDomain.setUserRoles(userRoles);

            // 获取用户权限信息
            List<TbSysPermissionDTO> userPermissions = new ArrayList<>();
            List<TbSysViewDTO> userViews = new ArrayList<>();

            ResultDomain<PermissionVO> permissionsResult = modulePermissionService.getUserPermissions(guest.getUserId());
            if (permissionsResult.getSuccess() && permissionsResult.getDataList() != null) {
                for (PermissionVO permission : permissionsResult.getDataList()) {
                    if (permission.getPermissionId() != null) {
                        TbSysPermissionDTO permissionDTO = PermissionVO.toPermissionDTO(permission);
                        if (permissionDTO != null) {
                            userPermissions.add(permissionDTO);
                        }
                    }
                    if (permission.getViewId() != null) {
                        TbSysViewDTO viewDTO = PermissionVO.toViewDTO(permission);
                        if (viewDTO != null) {
                            userViews.add(viewDTO);
                        }
                    }
                }
            }
            loginDomain.setUserPermissions(userPermissions);
            loginDomain.setUserViews(userViews);

            // 设置登录类型
            loginDomain.setLoginType(loginType != null ? loginType : "wechat_miniprogram");

            logger.info("构建来客LoginDomain成功：userId={}, name={}", guest.getUserId(), guest.getName());

            return loginDomain;

        } catch (Exception e) {
            logger.error("构建来客LoginDomain失败：userId={}", guest.getUserId(), e);
            return null;
        }
    }

    @Override
    public ResultDomain<LoginDomain> logout(String token) {
        try {
            if (token == null || token.trim().isEmpty()) {
                return ResultDomain.failure("Token不能为空");
            }
            
            // 1. 从Redis获取登录信息
            String loginKey = "login:token:" + token;
            String loginJson = redisService.get(loginKey, String.class);
            if (loginJson == null) {
                return ResultDomain.success("登出成功", (LoginDomain) null); // 已经过期的token，直接返回成功
            }
            
            LoginDomain loginDomain = JSON.parseObject(loginJson, LoginDomain.class);
            
            // 2. 删除Redis中的登录信息
            redisService.delete(loginKey);
            
            // 3. 删除用户登录状态
            if (loginDomain.getUser() != null) {
                String userLoginKey = "login:user:" + loginDomain.getUser().getUserId();
                redisService.delete(userLoginKey);
            }
            
            logger.info("用户登出成功：userId={}, token={}", 
                loginDomain.getUser() != null ? loginDomain.getUser().getUserId() : "unknown", 
                token.substring(0, Math.min(10, token.length())) + "...");
                
            return ResultDomain.success("登出成功", (LoginDomain)null);
            
        } catch (Exception e) {
            logger.error("用户登出失败", e);
            return ResultDomain.failure("登出失败: " + e.getMessage());
        }
    }

}
