package com.hotent.auth.server.service.impl;

import cn.hutool.crypto.digest.MD5;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.hotent.auth.server.constants.CommonConstants;
import com.hotent.auth.server.service.AuthenticationService;
import com.hotent.base.conf.JwtConfig;
import com.hotent.base.conf.SaaSConfig;
import com.hotent.base.conf.SsoConfig;
import com.hotent.base.enums.ResponseErrorEnums;
import com.hotent.base.exception.BaseException;
import com.hotent.base.exception.CertificateException;
import com.hotent.base.exception.ServerRejectException;
import com.hotent.base.feign.BpmRuntimeFeignService;
import com.hotent.base.feign.PortalFeignService;
import com.hotent.base.feign.UCFeignService;
import com.hotent.base.jwt.*;
import com.hotent.base.login.custom.token.CustomAuthenticationToken;
import com.hotent.base.model.CommonResult;
import com.hotent.base.query.QueryFilter;
import com.hotent.base.service.LoginLogService;
import com.hotent.base.service.PwdStrategyService;
import com.hotent.base.util.AppUtil;
import com.hotent.base.util.BeanUtils;
import com.hotent.base.util.CacheEvictUtil;
import com.hotent.base.util.FluentUtil;
import com.hotent.base.util.HttpUtil;
import com.hotent.base.util.JsonUtil;
import com.hotent.base.util.MapUtil;
import com.hotent.base.util.RSAUtil;
import com.hotent.base.util.StringUtil;
import com.hotent.base.util.XmlUtil;
import com.hotent.uc.api.model.IUser;
import com.hotent.uc.manager.UserManager;
import com.hotent.uc.model.User;
import io.jsonwebtoken.Claims;
import io.swagger.annotations.ApiParam;
import org.apache.commons.lang.StringUtils;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 用户认证 服务实现类
 *
 * @author liangjc
 * @company 广州宏天软件股份有限公司
 * @email liangjc@jee-soft.cn
 * @date 2023-09-18 17:29
 */
@Service
public class AuthenticationServiceImpl implements AuthenticationService {
    private static final Logger logger = LoggerFactory.getLogger(AuthenticationServiceImpl.class);
    @Resource
    AuthenticationManager authenticationManager;
    @Resource
    JwtTokenHandler jwtTokenHandler;
    @Resource
    UserDetailsService userDetailsService;
    @Resource
    SsoConfig ssoConfig;
    @Value("${system.mode.demo:false}")
    protected boolean demoMode;
    @Resource
    UCFeignService uCFeignService;
    @Resource
    PortalFeignService portalFeignService;
    @Resource
    BpmRuntimeFeignService bpmRuntimeFeignService;
    @Resource
    LoginLogService loginLogService;
    @Autowired
    private UserManager userService;
    @Resource
    SaaSConfig saasConfig;
    @Resource
    JwtConfig jwtConfig;
    @Value("${qzSetting.isHiddenDem:false}")
    protected boolean isHiddenDem; //是否隐藏维度信息

    /**
     * 删除缓存的用户详情
     * <p>该方法没有方法体，通过注解在切面中删除缓存数据</p>
     *
     * @param userAccount
     */
    private void deleteUserDetailsCache(String userAccount) {
        CacheEvictUtil.deleteUserDetailsCache(userAccount);
    }

    private void deleteComtomDetailsCache(String customKey) {
        CacheEvictUtil.deleteComtomDetailsCache(customKey);
    }

    @Override
    public ResponseEntity<JwtAuthenticationResponse> createAuthenticationToken(JwtAuthenticationRequest authenticationRequest) throws Exception {
        String reqAccount = authenticationRequest.getUsername();
        String reqPassword = "";

        String randImageId = authenticationRequest.getRandImageId();
        String randImageCode = authenticationRequest.getRandImageCode();

//        if (StringUtils.isBlank(randImageId) || StringUtils.isBlank(randImageCode)){
//            throw new BaseException("验证码不能为空");
//        }
//        if (!checkCaptchaCode(randImageId, randImageCode)){
//            throw new BaseException("验证码校验失败");
//        }
        //清除用户缓存
        this.deleteUserDetailsCache(reqAccount);
        try {
            //密码rsa解密
            reqPassword = RSAUtil.Decrypt(authenticationRequest.getPassword());
        } catch (Exception e) {
            throw new RuntimeException("解密密码异常，请检查RSA公钥和私钥配置");
        }
        try {
            authenticate(reqAccount, reqPassword);
        } catch (Exception e) {
            logger.error(String.format("Login failed account[%s].", reqAccount), e);
            throw new BaseException("用户名或密码错误");
        }
        // 当前切中的方法
        HttpServletRequest request = HttpUtil.getRequest();
        boolean isMobile = HttpUtil.isMobile(request);
        // Reload password post-security so we can generate the token
        final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
        final String token = jwtTokenHandler.generateToken(userDetails);
        String userName = userDetails.getUsername();
        String account = "";
        String userId = "";
        boolean loginStatus = true;
        Map<String, Object> userAttrs = new HashMap<String, Object>();
        if (userDetails instanceof IUser) {
            IUser user = ((IUser) userDetails);
            userName = user.getFullname();
            account = user.getAccount();
            userId = user.getUserId();
            request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
            //校验密码策略
            loginStatus = checkUser(user, reqPassword);
            userAttrs.put("tenantId", user.getTenantId());
            //是否超级管理员
            userAttrs.put("isAdmin", user.isAdmin());
            //是否隐藏维度
            if(user.isAdmin()) {
                userAttrs.put("isHiddenDem", "false"); //系统管理员不隐藏维度
            } else {
                userAttrs.put("isHiddenDem", isHiddenDem);
            }
        }
        User byAccount = userService.getByAccount(account);
        if (byAccount!=null) {
            userAttrs.put("dataScope",byAccount.getDataScope());
        }
        //处理单用户登录
        Map<String,Object> userMap=handleSingleLogin(isMobile, MapUtil.getString(userAttrs, "tenantId"), account, token);
        userAttrs.put("isMoreLogin",userMap);
        return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId, jwtConfig.getExpirationLong(), loginStatus, userAttrs));
    }

    @Override
    public ResponseEntity<?> createCustomToken(JwtCustomAuthenticationRequest authenticationRequest) {
        String customKey = authenticationRequest.getCustomKey();
        String customSecret = "";
        this.deleteComtomDetailsCache(customKey);
        try {
            customSecret = RSAUtil.Decrypt(authenticationRequest.getCustomSecret());
        } catch (Exception e) {
            throw new RuntimeException("解密密码异常，请检查RSA公钥和私钥配置");
        }
        try {
            authenticateCustom(customKey, customSecret);
        } catch (Exception e) {
            logger.error(String.format("get failed customKey[%s].", customKey), e);
            throw new BaseException("账号或秘钥错误");
        }
        String customId = "";
        String demId = "";
        JsonNode jsonNode = uCFeignService.loadCustomerUserByUsername(customKey);
        if (!Objects.isNull(jsonNode)){
            if (jsonNode.has("id")) {
                customId = jsonNode.get("id").asText();
            }
            if (jsonNode.has("demId")) {
                demId = jsonNode.get("demId").asText();
            }
        }

        Map<String, Object> claims = new HashMap<>();
        claims.put("customId", customId);
        claims.put("demId", demId);
        claims.put("userType", CommonConstants.USER_TYPE_CUSTOM);
        claims.put("customKey", customKey);
        final String token = jwtTokenHandler.generateToken(claims, customKey);
        jwtTokenHandler.putCustomTokenInCache(customKey, jwtConfig.getExpiration(), token);

        return ResponseEntity.ok(new JwtCustomAuthenticationResponse(customKey, CommonConstants.USER_TYPE_CUSTOM, jwtConfig.getExpirationLong(), token));
    }

    @Override
    public ResponseEntity<?> ssoAuth(Optional<String> ticket, Optional<String> code, Optional<String> ssoMode, String service) throws IOException {
        Assert.isTrue(ssoConfig.isEnable(), "当前服务未开启单点登录");
        String username = null;
        String mode = ssoConfig.getMode();
        if (ssoMode.isPresent()) {
            mode = ssoMode.get();
        }
        // 使用cas认证
        if (ticket.isPresent() && SsoConfig.MODE_CAS.equals(mode)) {
            username = getUserNameWithCas(ticket.get(), service);
        }
        // 使用oauth认证
        else if (code.isPresent() && SsoConfig.MODE_OAUTH.equals(mode)) {
            username = getUserNameWithOauth(code.get(), service);
        }
        // 使用jwt认证
        else if (ticket.isPresent() && code.isPresent() && SsoConfig.MODE_JWT.equals(mode)) {
            username = jwtTokenHandler.getUsernameFromToken(ticket.get());
        } else {
            throw new ServerRejectException("单点登录模式匹配异常");
        }

        //清除用户缓存
        this.deleteUserDetailsCache(username);

        // 当前切中的方法
        HttpServletRequest request = HttpUtil.getRequest();
        boolean isMobile = HttpUtil.isMobile(request);
        // Reload password post-security so we can generate the token
        final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        final String token = jwtTokenHandler.generateToken(userDetails);
        String userName = userDetails.getUsername();
        String account = "";
        String userId = "";
        Map<String, Object> userAttrs = new HashMap<String, Object>();
        if (userDetails instanceof IUser) {
            IUser user = ((IUser) userDetails);
            userName = user.getFullname();
            account = user.getAccount();
            userId = user.getUserId();
            request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
            userAttrs.put("tenantId", user.getTenantId());
        }
        //获取超时时间
        logger.debug("通过单点认证登录成功。");
        //处理单用户登录
        if (!(code.isPresent() && SsoConfig.MODE_JWT.equals(mode))) {
            handleSingleLogin(isMobile, MapUtil.getString(userAttrs, "tenantId"), account, token);
        }
        // Return the token
        return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId, jwtConfig.getExpirationLong(), userAttrs));
    }

    @Override
    public ResponseEntity<?> ssoWeixin(Optional<String> code) {
        String resultJson = HttpUtil.sendHttpsRequest(portalFeignService.getUserInfoUrl("weChatWork", code.orElse("")), "", "POST");
        logger.error("企业微信登录返回结果：" + resultJson);
        ObjectNode result = null;
        try {
            result = (ObjectNode) JsonUtil.toJsonNode(resultJson);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        Objects.requireNonNull(result);
        String errcode = result.get("errcode").asText();
        if ("0".equals(errcode)) {
            String wxWorkId = result.get("UserId").asText();

            JsonNode simpleUser = uCFeignService.getUserByWxWorkId(wxWorkId);
            if (BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()) {
                throw new RuntimeException("查无与您企微账号[userid:" + wxWorkId + "]绑定的eip账号");
            }
            String account = simpleUser.get("account").asText();
            try {
                //清除用户缓存
                this.deleteUserDetailsCache(account);

                // 当前切中的方法
                HttpServletRequest request = HttpUtil.getRequest();
                Objects.requireNonNull(request);
                boolean isMobile = HttpUtil.isMobile(request);
                // Reload password post-security so we can generate the token
                final UserDetails userDetails = userDetailsService.loadUserByUsername(account);
                final String token = jwtTokenHandler.generateToken(userDetails);
                String userName = userDetails.getUsername();
                String userId = "";
                String tenantId = "";
                if (userDetails instanceof IUser) {
                    IUser user = ((IUser) userDetails);
                    userName = user.getFullname();
                    userId = user.getUserId();
                    tenantId = user.getTenantId();
                    request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
                }
                logger.debug("通过单点认证登录成功。");
                //处理单用户登录
                handleSingleLogin(isMobile, tenantId, account, token);
                // Return the token
                return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId));
            } catch (Exception e) {
                throw new RuntimeException("企业微信登录失败 ,eip用户账号:" + account);
            }
        }
        throw new RuntimeException("企业微信登录失败 ： " + result.get("errmsg").asText());
    }

    @Override
    public ResponseEntity<?> weixinPublic(Optional<String> code) {
        String resultJson = HttpUtil.sendHttpsRequest(portalFeignService.getUserInfoUrl("weChatOffAcc", code.get()), "", "POST");
        ObjectNode result = null;
        try {
            result = (ObjectNode) JsonUtil.toJsonNode(resultJson);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        if (result.has("openid")) {
            String openid = result.get("openid").asText();
            CommonResult<JsonNode> r = uCFeignService.getUserByOpenId(openid);
            if (r.getState()) {
                JsonNode node = r.getValue();
                if (StringUtil.isNotEmpty(openid) && BeanUtils.isEmpty(node)) {
                    return ResponseEntity.ok(new JwtAuthenticationResponse(openid));
                }
                String account = node.get("account").asText();
                //清除用户缓存
                this.deleteUserDetailsCache(account);
                // 当前切中的方法
                HttpServletRequest request = HttpUtil.getRequest();
                boolean isMobile = HttpUtil.isMobile(request);
                // Reload password post-security so we can generate the token
                final UserDetails userDetails = userDetailsService.loadUserByUsername(account);
                final String token = jwtTokenHandler.generateToken(userDetails);
                String userName = userDetails.getUsername();
                String userId = "";
                String tenantId = "";
                if (userDetails instanceof IUser) {
                    IUser user = ((IUser) userDetails);
                    userName = user.getFullname();
                    userId = user.getUserId();
                    tenantId = user.getTenantId();
                    request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
                }
                //处理单用户登录
                handleSingleLogin(isMobile, tenantId, account, token);
                // Return the token
                return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId));
            } else {
                if (StringUtil.isNotEmpty(openid)) {
                    return ResponseEntity.ok(new JwtAuthenticationResponse(openid));
                }
            }
        }
        throw new RuntimeException("微信登录失败 ： " + result.get("errmsg").asText());
    }

    @Override
    public ResponseEntity<?> dingTalk(Optional<String> code) throws AuthenticationException {
        String resultJson = HttpUtil.sendHttpsRequest(portalFeignService.getUserInfoUrl("dingtalk", code.get()), "", "GET");
        ObjectNode result = null;
        try {
            result = (ObjectNode) JsonUtil.toJsonNode(resultJson);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        if (result.has("userid")) {

            String dingtalkId = result.get("userid").asText();

            JsonNode simpleUser = uCFeignService.getUserByDingtalkId(dingtalkId);
            if (BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()) {
                throw new RuntimeException("查无与您钉钉账号[userid:" + dingtalkId + "]绑定的eip账号");
            }
            String account = simpleUser.get("account").asText();

            final UserDetails userDetails = userDetailsService.loadUserByUsername(account);
            if (BeanUtils.isNotEmpty(userDetails)) {
                //清除用户缓存
                this.deleteUserDetailsCache(account);
                // Reload password post-security so we can generate the token
                // 当前切中的方法
                HttpServletRequest request = HttpUtil.getRequest();
                boolean isMobile = HttpUtil.isMobile(request);
                final String token = jwtTokenHandler.generateToken(userDetails);
                String userName = userDetails.getUsername();
                String userId = "";
                String tenantId = "";
                if (userDetails instanceof IUser) {
                    IUser user = ((IUser) userDetails);
                    userName = user.getFullname();
                    userId = user.getUserId();
                    tenantId = user.getTenantId();
                    request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
                }
                //处理单用户登录
                handleSingleLogin(isMobile, tenantId, account, token);
                // Return the token
                return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId));
            } else {
                throw new RuntimeException("钉钉登录失败！eip账号:" + account + "不存在");
            }
        }
        throw new RuntimeException("钉钉登录失败 ： " + result.get("errmsg").asText());
    }

    @Override
    public CommonResult<?> miniprogram(Optional<String> code) throws AuthenticationException {
        String url = portalFeignService.getUserInfoUrl("miniprogram", code.get());
        String resultJson = HttpUtil.sendHttpsRequest(url, "", "POST");
        ObjectNode result = null;
        try {
            result = (ObjectNode) JsonUtil.toJsonNode(resultJson);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        if (result.has("openid")) {
            String openid = result.get("openid").asText();
            JsonNode simpleUser = uCFeignService.getUserByMpOpenId(openid);
            if (BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()) {
                return new CommonResult<String>(false, "系统内无openid对应的用户", openid);
            }
            String account = simpleUser.get("account").asText();
            //清除用户缓存
            this.deleteUserDetailsCache(account);
            // 当前切中的方法
            HttpServletRequest request = HttpUtil.getRequest();
            // Reload password post-security so we can generate the token
            final UserDetails userDetails = userDetailsService.loadUserByUsername(account);
            final String token = jwtTokenHandler.generateToken(userDetails);
            String userName = userDetails.getUsername();
            String userId = "";
            String tenantId = "";
            if (userDetails instanceof IUser) {
                IUser user = ((IUser) userDetails);
                userName = user.getFullname();
                userId = user.getUserId();
                tenantId = user.getTenantId();
                request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
            }
            //处理单用户登录
            handleSingleLogin(HttpUtil.isMobile(request), tenantId, account, token);
            // Return the token
            return new CommonResult<JwtAuthenticationResponse>(true, "登陆成功",
                    new JwtAuthenticationResponse(token, userName, account, userId));
        }
        throw new RuntimeException("小程序登陆失败 ： " + (result == null ? "" : JsonUtil.getString(result, "errmsg")));
    }

    @Override
    public CommonResult<?> dingtalkScan(Optional<String> code, Optional<String> type) {
        CommonResult<String> result = null;
        if (type.get().equals("dingtalk")) {
            result = portalFeignService.getDingtalkIdFromScanCode(code.get());
        } else if (type.get().equals("wechatWork")) {
            result = portalFeignService.getWxWorkIdFromScanCode(code.get());
        }

        if (result != null && result.getState()) {
            String userid = result.getValue();

            JsonNode simpleUser = null;
            if (type.get().equals("dingtalk")) {
                simpleUser = uCFeignService.getUserByDingtalkId(userid);
            } else if (type.get().equals("wechatWork")) {
                simpleUser = uCFeignService.getUserByWxWorkId(userid);
            }

            if (BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()) {
                String msg = "扫码登录失败，查无与您" + (type.get().equals("dingtalk") ? "钉钉" : "企业微信")
                        + "账号[userid:" + userid + "]绑定的eip账号";
                return new CommonResult<String>(false, msg);
            }
            String account = simpleUser.get("account").asText();

            final UserDetails userDetails = userDetailsService.loadUserByUsername(account);
            if (BeanUtils.isNotEmpty(userDetails)) {
                //清除用户缓存
                this.deleteUserDetailsCache(account);
                // Reload password post-security so we can generate the token
                // 当前切中的方法
                HttpServletRequest request = HttpUtil.getRequest();
                boolean isMobile = HttpUtil.isMobile(request);
                final String token = jwtTokenHandler.generateToken(userDetails);
                String userName = userDetails.getUsername();
                String userId = "";
                String tenantId = "";
                if (userDetails instanceof IUser) {
                    IUser user = ((IUser) userDetails);
                    userName = user.getFullname();
                    userId = user.getUserId();
                    tenantId = user.getTenantId();
                    request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
                }
                //处理单用户登录
                handleSingleLogin(isMobile, tenantId, account, token);
                // Return the token
                return new CommonResult<JwtAuthenticationResponse>(true, "扫码登录成功",
                        new JwtAuthenticationResponse(token, userName, account, userId));
            } else {
                return new CommonResult<String>(false, "扫码登录失败！eip账号:" + account + "不存在");
            }
        }
        return new CommonResult<String>(false, "扫码登录失败 ： " + (result != null ? result.getMessage() : ""));
    }

    @Override
    public Map<String, Object> isUseCas() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("enable", ssoConfig.isEnable());
        map.put("ssoUrl", ssoConfig.getSsoUrl());
        map.put("ssoLogoutUrl", ssoConfig.getSsoLogoutUrl());
        return map;
    }

    @Override
    public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) {
        String authToken = request.getHeader(jwtConfig.getHeader());
        final String token = authToken.substring(7);
        String tenantId = jwtTokenHandler.getTenantIdFromToken(token);
        String account = jwtTokenHandler.getUsernameFromToken(token);
        String refreshedToken = jwtTokenHandler.refreshToken(token);
        boolean isMobile = HttpUtil.isMobile(request);
        // 处理单用户登录 更新单用户登录的token
        handleSingleLogin(isMobile, tenantId, account, refreshedToken);
        long expiration = jwtConfig.getExpirationLong();
        return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken, account, account, "", expiration, null));
    }

    @Override
    public void signout(HttpServletRequest request) {
        String authToken = request.getHeader(jwtConfig.getHeader());
        final String token = authToken.substring(7);
        String tenantId = jwtTokenHandler.getTenantIdFromToken(token);
        String account = jwtTokenHandler.getUsernameFromToken(token);
        boolean isMobile = HttpUtil.isMobile(request);
        handleLogout(isMobile, tenantId, account);
    }

    @Override
    public ResponseEntity<?> productLoginQrCode(String type) throws AuthenticationException {
        return ResponseEntity.ok(uCFeignService.productQrCode(type));
    }

    @Override
    public CommonResult<?>  dingAuthByCode(String code, String authCode,String state) throws AuthenticationException {
        if(StringUtil.isNotEmpty(state)){
            if(state.contains("_")){
                String buinessId=state.split("_")[0];//扫码id
                String scanType=state.split("_")[1];//0=钉钉1=微信
                if(scanType.equals("0")){
                    JsonNode simpleUser= uCFeignService.dingAuthByCode(code);
                    if (BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()) {
                        String msg = "扫码登录失败，没有匹配到您的账号。";
                        return new CommonResult<String>(false, msg);
                    }
                    String account = simpleUser.get("account").asText();
                    uCFeignService.updateScanLoginInfo(buinessId,simpleUser.get("id").asText());
                    return new CommonResult<JwtAuthenticationResponse>(true, "扫码登录成功");
                }
            }
        }
        return new CommonResult<JwtAuthenticationResponse>(false, "扫码登录失败");
    }

    @Override
    public String dingAuthByCodeReHtml(String code, String authCode, String state) throws AuthenticationException {
        return "<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>\n" +
                "    <meta charset=\"utf-8\">\n" +
                "    <meta name=\"viewport\" content=\"initial-scale=1.0, maximum-scale=1.0, user-scalable=no\" />\n" +
                "    <title></title>\n" +
                "    <script type=\"text/javascript\">\n" +
                "    \t\n" +
                "   \t\tdocument.addEventListener('plusready', function(){\n" +
                "   \t\t\t\n" +
                "   \t\t});\n" +
                "   \t\t\n" +
                "    </script>\n" +
                "</head>\n" +
                "<body>\n" +
                "\t<div style=\"color: red;\">返回参数</div>\n" +
                "</body>\n" +
                "</html>";
    }

    @Override
    public CommonResult<?> queryScanLoginInfoById(String busId) throws AuthenticationException {

        JsonNode simpleUser=uCFeignService.queryScanLoginInfo(busId);
        if (BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()) {
            String msg = "扫码登录失败";
            return new CommonResult<String>(true, msg,"0");
        }
        String account = simpleUser.get("account").asText();
        final UserDetails userDetails = userDetailsService.loadUserByUsername(account);
        if (BeanUtils.isNotEmpty(userDetails)) {
            //清除用户缓存
            this.deleteUserDetailsCache(account);
            // Reload password post-security so we can generate the token
            // 当前切中的方法
            HttpServletRequest request = HttpUtil.getRequest();
            boolean isMobile = HttpUtil.isMobile(request);
            final String token = jwtTokenHandler.generateToken(userDetails);
            String userName = userDetails.getUsername();
            String userId = "";
            String tenantId = "";
            if (userDetails instanceof IUser) {
                IUser user = ((IUser) userDetails);
                userName = user.getFullname();
                userId = user.getUserId();
                tenantId = user.getTenantId();
                request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
            }
            //处理单用户登录
            handleSingleLogin(isMobile, tenantId, account, token);
            // Return the token
            return new CommonResult<JwtAuthenticationResponse>(true, "扫码登录成功",
                    new JwtAuthenticationResponse(token, userName, account, userId));
        }
        return null;
    }

    @Override
    public CommonResult<?> scanLoginCode(String clientId,String busId) throws AuthenticationException {
        if (StringUtils.isBlank(clientId)){
            throw new BaseException("客户端ID不能为空");
        }
        CommonResult result = portalFeignService.getPortalBizSystemUrlBySysCode(clientId);
        String redirectUrl = null;
        if (result != null && result.getCode()==200 && result.getValue()!=null) {
            redirectUrl = (String)result.getValue();
        }
        if (StringUtils.isBlank(redirectUrl)) {
            throw new BaseException("未配置登录地址，请联系管理员");
        }

        JsonNode simpleUser=uCFeignService.queryScanLoginInfo(busId);
        if (BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()) {
            return new CommonResult<String>(false, "扫码登录失败");
        }
        String account = simpleUser.get("account").asText();
        if (StringUtils.isBlank(account)) {
            return new CommonResult<String>(false, "扫码登录失败");
        }
        String shortCode = MD5.create().digestHex("code_scan_login:" + account + "_" + System.currentTimeMillis()).substring(0, 4);
        logger.info("debug==>登录方式:【扫码登录】;创建code成功 参数：account：【" + account + "】code:" + shortCode);
        jwtTokenHandler.addShortCache(shortCode,account);
        Map<String, String> map = new HashMap<>();
        map.put("code",shortCode);
        map.put("url",redirectUrl);
        return new CommonResult<>(true, "获取shortCode成功！",map);
    }

    @Override
    public CommonResult<?> sendMsgYzmCode(String mobile,String use,String randImageId,String  randImageCode) throws AuthenticationException {
        if (StringUtils.isBlank(randImageId) || StringUtils.isBlank(randImageCode)){
            throw new BaseException("验证码不能为空");
        }
        if (!checkCaptchaCode(randImageId, randImageCode)){
            throw new BaseException("验证码校验失败");
        }
//        System.out.println("debug==>手机号登录:【验证码校验】 参数：randImageId：【" + randImageId + "】  randImageCode:" + randImageCode);
        JsonNode  json=uCFeignService.sendMsgYzmCode(mobile,use);
        String msg = json.get("msg").asText();
        String code = json.get("code").asText();
        if(code.equals("500")){
            return new CommonResult<String>(false, msg);
        }
        return new CommonResult<String>(true, msg);
    }

    @Override
    public CommonResult<?> msgYzmLogin(String mobile,String code,String use) throws AuthenticationException {
        JsonNode simpleUser =uCFeignService.msgYzmLogin(mobile,code,use);
        if (BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()) {
            String msg ="手机号验证码登录失败";
            return new CommonResult<String>(false, msg);
        }
        String account = simpleUser.get("account").asText();

        final UserDetails userDetails = userDetailsService.loadUserByUsername(account);
        if (BeanUtils.isNotEmpty(userDetails)) {
            //清除用户缓存
            this.deleteUserDetailsCache(account);
            // Reload password post-security so we can generate the token
            // 当前切中的方法
            HttpServletRequest request = HttpUtil.getRequest();
            boolean isMobile = HttpUtil.isMobile(request);
            final String token = jwtTokenHandler.generateToken(userDetails);
            String userName = userDetails.getUsername();
            String userId = "";
            String tenantId = "";
            if (userDetails instanceof IUser) {
                IUser user = ((IUser) userDetails);
                userName = user.getFullname();
                userId = user.getUserId();
                tenantId = user.getTenantId();
                request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
            }
            //处理单用户登录
            handleSingleLogin(isMobile, tenantId, account, token);
            uCFeignService.removeMsgYzmCode(mobile,use);
            // Return the token
            return new CommonResult<JwtAuthenticationResponse>(true, "手机号验证码登录成功",
                    new JwtAuthenticationResponse(token, userName, account, userId));
        } else {
            return new CommonResult<String>(false, "手机号验证码登录失败");
        }
    }


    @Override
    public CommonResult<?> msgYzmLoginCode(String clientId,String mobile,String code,String use) throws AuthenticationException {
        if (StringUtils.isBlank(clientId)){
            throw new BaseException("客户端ID不能为空");
        }
        CommonResult result = portalFeignService.getPortalBizSystemUrlBySysCode(clientId);
        String redirectUrl = null;
        if (result != null && result.getCode()==200 && result.getValue()!=null) {
            redirectUrl = (String)result.getValue();
        }
        if (StringUtils.isBlank(redirectUrl)) {
            throw new BaseException("未配置登录地址，请联系管理员");
        }

        JsonNode simpleUser =uCFeignService.msgYzmLogin(mobile,code,use);
        if (BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()) {
            return new CommonResult<String>(false, "手机号验证码登录失败");
        }
        String account = simpleUser.get("account").asText();
        if (StringUtils.isBlank(account)) {
            return new CommonResult<String>(false, "手机号验证码登录失败");
        }
        String shortCode = MD5.create().digestHex("code_mobile_login:" + account + "_" + System.currentTimeMillis()).substring(0, 4);
        logger.info("debug==>登录方式:【短信验证码】;创建code成功 参数：account：【" + account + "】mobile：【"+mobile+"】  code:" + shortCode);
        jwtTokenHandler.addShortCache(shortCode,account);
        uCFeignService.removeMsgYzmCode(mobile,use);
        Map<String, String> map = new HashMap<>();
        map.put("code",shortCode);
        map.put("url",redirectUrl);
        return new CommonResult<>(true, "获取shortCode成功！",map);
    }

    @Override
    public CommonResult<?> forgotPwd(String account, String mobile, String validateCode,
                                     String newPsw, String reNewPsw) throws AuthenticationException, IOException {
        logger.info("<忘记密码:forgotPwd>account:"+account);
        logger.info("<忘记密码:forgotPwd>mobile:"+mobile);
        logger.info("<忘记密码:forgotPwd>validateCode:"+validateCode);
        logger.info("<忘记密码:forgotPwd>newPsw:"+newPsw);
        logger.info("<忘记密码:forgotPwd>reNewPsw:"+reNewPsw);

        JsonNode json = uCFeignService.forgotPwd(account, mobile, validateCode,
                newPsw, reNewPsw);
        return JsonUtil.toBean(json.toString(), CommonResult.class);
    }


    @Override
    public CommonResult<?> removeMsgYzmCode(String mobile,String use) throws AuthenticationException {
        JsonNode simpleUser = uCFeignService.removeMsgYzmCode(mobile,use);
        return null;
    }

    @Override
    public ResponseEntity<?> refreshCustomAuthenticationToken(HttpServletRequest request) {
        String authToken = request.getHeader(jwtConfig.getHeader());
        final String token = authToken.substring(7);
        Claims claims = jwtTokenHandler.getClaimsFromToken(token);
        String customKey = claims.get("customKey", String.class);
        String refreshToken = jwtTokenHandler.refreshToken(claims, customKey);
        jwtTokenHandler.putCustomTokenInCache(customKey, jwtConfig.getExpiration(), token);

        return ResponseEntity.ok(new JwtCustomAuthenticationResponse(customKey, CommonConstants.USER_TYPE_CUSTOM, jwtConfig.getExpirationLong(), refreshToken));
    }

    @Override
    public CommonResult<?> getDingUserByCode(String code, String corpId, String appId) throws AuthenticationException {
        JsonNode jsonNode=portalFeignService.getDingUserByCode(code,corpId,appId);
        String codes=jsonNode.get("code").asText();
        if(codes.equals("200")){
            JsonNode jsonNodeUser=jsonNode.get("value");
            String account = jsonNodeUser.get("account").asText();
            final UserDetails userDetails = userDetailsService.loadUserByUsername(account);
            if (BeanUtils.isNotEmpty(userDetails)) {
                //清除用户缓存
                this.deleteUserDetailsCache(account);
                // Reload password post-security so we can generate the token
                // 当前切中的方法
                HttpServletRequest request = HttpUtil.getRequest();
                boolean isMobile = HttpUtil.isMobile(request);
                final String token = jwtTokenHandler.generateToken(userDetails);
                String userName = userDetails.getUsername();
                String userId = "";
                String tenantId = "";
                if (userDetails instanceof IUser) {
                    IUser user = ((IUser) userDetails);
                    userName = user.getFullname();
                    userId = user.getUserId();
                    tenantId = user.getTenantId();
                    request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
                }
                //处理单用户登录
                handleSingleLogin(isMobile, tenantId, account, token);
                // Return the token
                return new CommonResult<JwtAuthenticationResponse>(true, "手机号验证码登录成功",
                        new JwtAuthenticationResponse(token, userName, account, userId));
            } else {
                return new CommonResult<String>(false, "手机号验证码登录失败");
            }
        }

        return null;
    }

    @Override
    public  CommonResult<?> fingerPrintLogin(String type, String loginValue, String deviceId) throws AuthenticationException {
        String tempdeviceId="";
        try {
            //密码rsa解密
            tempdeviceId = RSAUtil.Decrypt(deviceId);
        } catch (Exception e) {
            throw new RuntimeException("解密密码异常，请检查RSA公钥和私钥配置");
        }

        JsonNode  jsonNode=uCFeignService.fingerPrintLogin(type,loginValue,tempdeviceId);
        if(jsonNode!=null){
            String account = jsonNode.get("account").asText();
            final UserDetails userDetails = userDetailsService.loadUserByUsername(account);
            if (BeanUtils.isNotEmpty(userDetails)) {
                //清除用户缓存
                this.deleteUserDetailsCache(account);
                // Reload password post-security so we can generate the token
                // 当前切中的方法
                HttpServletRequest request = HttpUtil.getRequest();
                boolean isMobile = HttpUtil.isMobile(request);
                final String token = jwtTokenHandler.generateToken(userDetails);
                String userName = userDetails.getUsername();
                String userId = "";
                String tenantId = "";
                if (userDetails instanceof IUser) {
                    IUser user = ((IUser) userDetails);
                    userName = user.getFullname();
                    userId = user.getUserId();
                    tenantId = user.getTenantId();
                    request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
                }
                //处理单用户登录
                handleSingleLogin(isMobile, tenantId, account, token);
                // Return the token
                return new CommonResult<JwtAuthenticationResponse>(true, "指纹登录成功",
                        new JwtAuthenticationResponse(token, userName, account, userId));
            } else {
                return new CommonResult<String>(false, "指纹登录成功失败");
            }
        }

        return null;
    }

    @Override
    public CommonResult<?> getAppByNoAuth() throws AuthenticationException {
        ArrayNode arrayNode=portalFeignService.getAppByNoAuth("appType");
        return new CommonResult<>(true,"获取成功",arrayNode);
    }

    @Override
    public CommonResult<?> loadMySysAppOftenByNoAuth() throws AuthenticationException {
        ArrayNode arrayNode=portalFeignService.loadMySysAppOftenByNoAuth("1");
        return new CommonResult<>(true,"获取成功",arrayNode);
    }

    @Override
    public CommonResult<?> getMyOftenFlowByNoAuth() throws AuthenticationException {
        ObjectNode arrayNode= null;
        try {
            arrayNode = bpmRuntimeFeignService.getMyOftenFlowByNoAuth();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new CommonResult<>(true,"获取成功",arrayNode);
    }

    @Override
    public CommonResult<?> messagelistNoUser(@ApiParam(name="queryFilter",value="通用查询对象")@RequestBody QueryFilter queryFilter) throws AuthenticationException {
        ObjectNode arrayNode=portalFeignService.messagelistNoUser(queryFilter);
        return new CommonResult<>(true,"获取成功",arrayNode);
    }

    @Override
    public CommonResult<?> getWelcomeDage(String orgId) throws AuthenticationException {
        JsonNode json=uCFeignService.getWelcomeData(orgId);
        if(json==null){
            return new CommonResult<>(true,"没有数据", false);
        }

        return new CommonResult<>(true,"获取成功", uCFeignService.getWelcomeData(orgId));
    }

    /**
     * Authenticates the user. If something is wrong, an {@link AuthenticationException} will be thrown
     */
    private void authenticate(String username, String password) throws AuthenticationException, CertificateException {
        Objects.requireNonNull(username);
        Objects.requireNonNull(password);
        authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
    }

    private void authenticateCustom(String customKey, String customSecret) {
        Objects.requireNonNull(customKey);
        Objects.requireNonNull(customSecret);
        authenticationManager.authenticate(new CustomAuthenticationToken(customKey, customSecret));
    }

    private boolean checkUser(IUser user, String password) {
        if (!user.isAdmin()) {
            //非系统管理员
            PwdStrategyService service = AppUtil.getBean(PwdStrategyService.class);
            if (service == null) {
                return true;
            }
            JsonNode json = service.getJsonDefault();
            if (BeanUtils.isNotEmpty(json)) {
                // 初始化密码
                String initPwd = json.get("initPwd").asText();
                // 密码策略
                int pwdRule = json.get("pwdRule").asInt();
                // 密码长度
                int pwdLength = json.get("pwdLength").asInt();
                // 密码可用时长
                int duration = json.get("duration").asInt();
                // 启用策略	0:停用，1:启用
                int enable = json.get("enable").asInt();
                Objects.requireNonNull(password);
                if (enable == 1) {
                    if (password.equals(initPwd)) {
                        return false;
                    }
                    if (password.length() < pwdLength) {
                        return false;
                    }

                    if (pwdRule != 1) {
                        if (pwdRule == 2) {//必须包含数字、字母
                            String regex = "^(?![a-zA-z]+$)(?!\\d+$)(?![!@#$%^&*]+$)[a-zA-Z\\d!@#$%^&*]+$";
                            boolean result = password.matches(regex);
                            if (!result) {
                                return false;
                            }
                        } else if (pwdRule == 3) {//必须包含数字、字母、特殊字符
                            String regex = "^(?=.*?[A-Za-z])(?=.*?\\d)(?=.*?[~!@#$%^&*()_+`\\-={}:\";'<>?,.\\/])[a-zA-Z\\d~!@#$%^&*()_+`\\-={}:\";'<>?,.\\/]*$";
                            boolean result = password.matches(regex);
                            if (!result) {
                                return false;
                            }
                        } else if (pwdRule == 4) {//必须包含数字、大小字母、特殊字符
                            String regex = "^(?=.*?[a-z])(?=.*?[A-Z])(?=.*?\\d)(?=.*?[~!@#$%^&*()_+`\\-={}:\";'<>?,.\\/])[a-zA-Z\\d~!@#$%^&*()_+`\\-={}:\";'<>?,.\\/]*$";
                            boolean result = password.matches(regex);
                            if (!result) {
                                return false;
                            }
                        }
                    }

                    //密码策略时间
                    LocalDateTime pwdCreateTime = user.getPwdCreateTime();
                    if (BeanUtils.isNotEmpty(pwdCreateTime)) {
                        LocalDateTime currenTime = LocalDateTime.now();
                        int size = (int) (currenTime.toLocalDate().toEpochDay() - pwdCreateTime.toLocalDate().toEpochDay());
                        return size <= duration;
                    }
                }
            }
        }
        return true;
    }

    /**
     * 处理单用户登录
     *
     * @param isMobile
     * @param username
     * @param token
     */
    private Map<String,Object> handleSingleLogin(boolean isMobile, String tenantId, String username, String token) {
        Map<String,Object> result=new HashMap<>();
        //如果是单用户登录
        if (jwtConfig.isSingle()) {
            String userAgent = isMobile ? "mobile" : "pc";
            // 非SaaS模式
            if (StringUtil.isEmpty(tenantId) && !saasConfig.isEnable()) {
                tenantId = "-1";
            }
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            // 从缓存中获取token
            String oldToken = jwtTokenHandler.getTokenFromCache(userAgent, tenantId, username, jwtConfig.getExpiration());
            if(StringUtil.isNotEmpty(oldToken)) {
                if(jwtTokenHandler.validateToken(oldToken, userDetails) && !oldToken.equals(token)){
//                    throw new BaseException(ResponseErrorEnums.KICK_OFF_BY_ANOTHER);
                    System.out.println(username+":"+userAgent+"端重复登录了");
                    result.put("flag",true);
                    result.put("msg","当前账号已在另一地方登录，若不是本人操作，请注意账号安全");
                }
            }else{
                result.put("flag",false);
                result.put("msg","无多处登录");
            }
            // 以当前登录设备、租户ID、用户账号为key将token存放到缓存中
            jwtTokenHandler.putTokenInCache(userAgent, tenantId, username, jwtConfig.getExpiration(), token);
        }
        //处理用户登录日志
        uCFeignService.loginLog(username, isMobile ? "mobile" : "pc");
        return result;
    }

    /**
     * 处理用户登出
     *
     * @param tenantId
     * @param account
     */
    private void handleLogout(boolean isMobile, String tenantId, String account) {
        //如果是单用户登录
        if (jwtConfig.isSingle()) {
            String userAgent = isMobile ? "mobile" : "pc";
            // 非SaaS模式
            if (StringUtil.isEmpty(tenantId) && !saasConfig.isEnable()) {
                tenantId = "-1";
            }
            jwtTokenHandler.removeFromCache(userAgent, tenantId, account);
        }
    }

    // cas验证ticket并获取当前登录用户账号
    private String getUserNameWithCas(String ticket, String service) throws IOException {
        String casUserDetail = "";
        String username = null, errorCode = "";
        try {
            casUserDetail = FluentUtil.get(String.format("%s/p3/serviceValidate?ticket=%s&service=%s", ssoConfig.getCasUrl(), ticket, service), "");
            String json = XmlUtil.toJson(casUserDetail);
            JsonNode jsonNode = JsonUtil.toJsonNode(json);

            if (jsonNode.has("authenticationSuccess")) {
                username = jsonNode.get("authenticationSuccess").get("user").asText();
            } else if (jsonNode.has("authenticationFailure")) {
                errorCode = jsonNode.get("authenticationFailure").get("code").asText();
                throw new RuntimeException(errorCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.info("获取cas认证信息失败：" + casUserDetail);
            throw new RuntimeException("获取cas认证信息失败： " + e.getMessage());
        }
        return username;
    }

    // oauth验证code并获取当前登录用户账号
    private String getUserNameWithOauth(String code, String service) {
        String userName = null;
        String oauthTokenUrl = ssoConfig.getOauthTokenUrl();
        String stufix = String.format("&code=%s&redirect_uri=%s", code, service);
        try {
            String header = ssoConfig.getOauthBasicHeader();
            String tokenResult = FluentUtil.post(oauthTokenUrl + stufix, header, null, ContentType.APPLICATION_FORM_URLENCODED);
            JsonNode jsonNode = JsonUtil.toJsonNode(tokenResult);
            if (jsonNode != null && jsonNode.isObject()) {
                String token = jsonNode.get(ssoConfig.getOauthAccesstokenKey()).asText();
                String oauthCheckUrl = ssoConfig.getOauthCheckUrl();
                String checkResult = FluentUtil.post(oauthCheckUrl + token, null, null, ContentType.APPLICATION_FORM_URLENCODED);
                JsonNode checkJNode = JsonUtil.toJsonNode(checkResult);
                if (checkJNode != null && checkJNode.isObject()) {
                    userName = checkJNode.get(ssoConfig.getOauthUsernameKey()).asText();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("获取oauth认证信息失败", e);
        }
        return userName;
    }


    @Override
    public ResponseEntity<?> exchangeAuthenticationTokenCode(String shortCode) {
        String reqAccountAndPassword = jwtTokenHandler.getShortCache(shortCode);
        if (StringUtil.isEmpty(reqAccountAndPassword)) {
            throw new BaseException("code无效！");
        }
        String reqAccount = reqAccountAndPassword;
        String reqPassword = "";
        if (reqAccountAndPassword.contains("@@@###@@@")){
            String[] split = reqAccountAndPassword.split("@@@###@@@");
            reqAccount = split[0];
            reqPassword = split[1];
        }

        jwtTokenHandler.removeShortCache(shortCode);
        //清除用户缓存
        this.deleteUserDetailsCache(reqAccount);
        // 当前切中的方法
        HttpServletRequest request = HttpUtil.getRequest();
        boolean isMobile = HttpUtil.isMobile(request);
        // Reload password post-security so we can generate the token
        final UserDetails userDetails = userDetailsService.loadUserByUsername(reqAccount);
        final String token = jwtTokenHandler.generateToken(userDetails);
        String userName = userDetails.getUsername();
        String account = "";
        String userId = "";
        boolean loginStatus = true;
        Map<String, Object> userAttrs = new HashMap<>();
        if (userDetails instanceof IUser) {
            IUser user = ((IUser) userDetails);
            userName = user.getFullname();
            account = user.getAccount();
            userId = user.getUserId();
            request.setAttribute("loginUser", String.format("%s[%s]", userName, account));

            if (StringUtils.isNotBlank(reqPassword)) {
                //注意：
                // 1. 使用用户名密码登录时，需要校验密码策略；
                // 2. 使用短信验证码，钉钉扫码等方式登录时，reqPassword为空，不校验密码策略。
                loginStatus = checkUser(user, reqPassword);
            }

            userAttrs.put("tenantId", user.getTenantId());
            //是否超级管理员
            userAttrs.put("isAdmin", user.isAdmin());
            //是否隐藏维度
            if(user.isAdmin()) {
                userAttrs.put("isHiddenDem", "false"); //系统管理员不隐藏维度
            } else {
                userAttrs.put("isHiddenDem", isHiddenDem);
            }
        }
        //处理单用户登录
        Map<String,Object> userMap=handleSingleLogin(isMobile, MapUtil.getString(userAttrs, "tenantId"), account, token);
        userAttrs.put("isMoreLogin",userMap);
        return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId, jwtConfig.getExpirationLong(), loginStatus, userAttrs));

    }

    public boolean checkCaptchaCode(String codeId, String requestCode) {
        String captchaCache = jwtTokenHandler.getCaptchaCache(codeId);
        if (StringUtil.isNotEmpty(captchaCache) && captchaCache.equals(requestCode)) {
            jwtTokenHandler.removeCaptchaCache(codeId);
            return true;
        }
        return false;
    }
}
