package com.artfess.base.controller;

import com.artfess.base.annotation.ApiGroup;
import com.artfess.base.cache.annotation.CacheEvict;
import com.artfess.base.cache.annotation.CachePut;
import com.artfess.base.cache.annotation.Cacheable;
import com.artfess.base.cache.annotation.FirstCache;
import com.artfess.base.conf.JwtConfig;
import com.artfess.base.conf.SaaSConfig;
import com.artfess.base.conf.SsoConfig;
import com.artfess.base.constants.ApiGroupConsts;
import com.artfess.base.constants.CacheKeyConst;
import com.artfess.base.constants.SystemConstants;
import com.artfess.base.exception.CertificateException;
import com.artfess.base.exception.ServerRejectException;
import com.artfess.base.feign.ApplicationFeignService;
import com.artfess.base.feign.UCFeignService;
import com.artfess.base.jwt.JwtAuthenticationRequest;
import com.artfess.base.jwt.JwtAuthenticationResponse;
import com.artfess.base.jwt.JwtTokenHandler;
import com.artfess.base.model.CommonResult;
import com.artfess.base.service.LoginLogService;
import com.artfess.base.service.LoginUserService;
import com.artfess.base.service.PwdStrategyService;
import com.artfess.base.service.SecurityMachinePersonService;
import com.artfess.base.util.AppUtil;
import com.artfess.base.util.Base64;
import com.artfess.base.util.BeanUtils;
import com.artfess.base.util.EncryptUtil;
import com.artfess.base.util.FluentUtil;
import com.artfess.base.util.HttpUtil;
import com.artfess.base.util.IPUtils;
import com.artfess.base.util.JsonUtil;
import com.artfess.base.util.MapUtil;
import com.artfess.base.util.StringUtil;
import com.artfess.base.util.XmlUtil;
import com.artfess.uc.api.model.IUser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.LockedException;
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.util.Assert;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@RestController
@Api(tags = "认证接口")
@ApiGroup(group = {ApiGroupConsts.GROUP_BPM, ApiGroupConsts.GROUP_FORM, ApiGroupConsts.GROUP_SYSTEM, ApiGroupConsts.GROUP_UC})
public class AuthenticationRestController {
    private static final Logger logger = LoggerFactory.getLogger(AuthenticationRestController.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
    ApplicationFeignService applicationFeignService;

    @Resource
    LoginLogService loginLogService;
    @Resource
    LoginUserService loginUserService;
    @Resource
    SaaSConfig saasConfig;
    @Resource
    JwtConfig jwtConfig;

    /**
     * 与前端约定的字符串秘钥
     */
    private static final String KEY = "Artfess@024";

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

    @CacheEvict(value = CacheKeyConst.EIP_UC_USER_ACCOUNT, key = "#userAccount")
    protected void delUserDetailsCache(String userAccount) {
    }

    @CacheEvict(value = CacheKeyConst.EIP_UC_USER_NAME, key = "#userAccount")
    protected void delUsernamesCache(String userAccount) {
    }

    @CachePut(value = CacheKeyConst.EIP_UC_USER_LOGIN_ERROR_NUM, key = "#account+'_'+#tenantId",
            firstCache = @FirstCache(expireTime = 300, timeUnit = TimeUnit.SECONDS))
    protected Integer putLoginErrorNumInCache(String tenantId, String account, Integer errorNum) {
        return errorNum;
    }

    @Cacheable(value = CacheKeyConst.EIP_UC_USER_LOGIN_ERROR_NUM, key = "#account+'_'+#tenantId")
    protected Integer getUserLoginErrorNumCache(String tenantId, String account) {
        return null;
    }

    @CacheEvict(value = CacheKeyConst.EIP_UC_USER_LOGIN_ERROR_NUM, key = "#account+'_'+#tenantId")
    protected void delUserLoginErrorNumCache(String tenantId, String account) {
    }

    @RequestMapping(value = "/auth", method = RequestMethod.POST, produces = {"application/json; charset=utf-8"})
    @ApiOperation(value = "登录系统", httpMethod = "POST", notes = "登录系统")
    public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtAuthenticationRequest authenticationRequest) throws AuthenticationException, CertificateException {
        // 如果是从总平台调转过来则不会携带密码
        boolean isBase64 = true;
        // 获取驾驶舱token-> 验证驾驶舱token -> 调用登录接口获取安全系统的token
        if (StringUtil.isNotEmpty(authenticationRequest.getToken()) && StringUtil.isNotEmpty(authenticationRequest.getUsername())) {
            // 使用用户账号 + 约定秘钥 MD5 加密比对
            final String token = authenticationRequest.getUsername() + KEY;
            try {
                // 系统自己加密的MD5
                String baseMd5 = EncryptUtil.md5Hex(token).toLowerCase();
                // 前端加密的MD5 Base64
                byte[] webBytes = Base64.decodeBase64(authenticationRequest.getToken());
                String webToken = new String(webBytes, StandardCharsets.UTF_8); // 转换为字符串
                // 解析得到MD5再比对
                if (baseMd5.equals(webToken)) {
                    Map<String, Object> userInfo = uCFeignService.getDetailByAccountOrId(authenticationRequest.getUsername());
                    if (null == userInfo || userInfo.get("user") == null) {
                        throw new RuntimeException("用户不存在或授权到期，请重新登录");
                    }
                } else {
                    throw new RuntimeException("token非法，请重新获取");
                }
                isBase64 = false;
            } catch (Exception e) {
                throw new RuntimeException(e);

            }
        }
        //判断版本授权信息
    /*  JsonNode jsonNode =  uCFeignService.checkSysAuthorization();
        String resultCode = jsonNode.get("message").asText();
        if(resultCode == null ||(!resultCode.split(":")[0].equals("SUCCESS") &&  !resultCode.split(":")[0].equals("NO_DECRYPT_WILL_DATE_LONG"))){
            throw new RuntimeException(resultCode.split(":")[1]);
        }*/

        String reqAccount = authenticationRequest.getUsername();
        String reqPassword = "";
        //清除用户缓存
        this.deleteUserDetailsCache(reqAccount);
        String errorMsg = "";

        final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
        PwdStrategyService service = AppUtil.getBean(PwdStrategyService.class);
        JsonNode json = service.getJsonDefault();
        AuthenticationRestController bean = AppUtil.getBean(getClass());
        if (BeanUtils.isNotEmpty(json)) {
            long autoUnlockTime = 5L;
            if (BeanUtils.isNotEmpty(json.get("autoUnlockTime"))) {
                autoUnlockTime = json.get("autoUnlockTime").asLong();
            }
            if (userDetails instanceof IUser) {
                IUser user = ((IUser) userDetails);
                if (2 == user.getLockedStatus()) {
                    LocalDateTime lockedTime = user.getLockedTime();
                    Long intervalMinutes = (LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() - lockedTime.toInstant(ZoneOffset.of("+8")).toEpochMilli()) / 60000L;
                    if (intervalMinutes >= autoUnlockTime) {
                        loginUserService.lockedUser(user.getAccount(), 1);
                        if (BeanUtils.isNotEmpty(user)) {
                            bean.delUserLoginErrorNumCache(user.getTenantId(), reqAccount);
                        } else {
                            bean.delUserLoginErrorNumCache("-1", reqAccount);
                        }
                    } else {
                        long sy = autoUnlockTime - intervalMinutes;
                        throw new RuntimeException("账号在锁定状态中,请于【" + sy + "分钟】后登录，或联系管理员解锁！");
                    }
                }
            }
        }


        if (isBase64) {
            try {
                //密码Base64解密
                reqPassword = Base64.getFromBase64(authenticationRequest.getPassword());
                authenticate(reqAccount, reqPassword);
            } catch (Exception e) {
                logger.error(String.format("Login failed account[%s].", reqAccount), e);
                errorMsg = "账号或密码错误";
                if (BeanUtils.isNotEmpty(e.getCause()) && e.getCause() instanceof CertificateException) {
                    CertificateException ce = (CertificateException) e.getCause();
                    errorMsg = ce.getMessage();
                }
                if (e instanceof LockedException) {
                    errorMsg = "账号被禁用或离职";
                }
            }
        }
        HttpServletRequest request = HttpUtil.getRequest();
        HttpSession session = request.getSession();
        String IP = IPUtils.getIpAddr(request);
        //验证密码输入错误次数
        if (StringUtil.isNotEmpty(errorMsg)) {
            if (errorMsg.equals("账号或密码错误") && !isAdmin(reqAccount)) {
                IUser user = null;
                if (userDetails instanceof IUser) {
                    user = ((IUser) userDetails);
                }
                //获取密码策略
                if (BeanUtils.isNotEmpty(json)) {

                    // 密码错误锁定状态（0：关闭【默认】 1：开启 ）
                    int lockStatus = json.get("lockStatus").asInt();
                    // 密码错误次数
                    int lockTimes = json.get("lockTimes").asInt();
                    // 启用策略	0:停用，1:启用
                    int enable = json.get("enable").asInt();
                    if (enable == 1 && lockStatus == 1) {
                        Integer loginTimes = 0;
                        if (BeanUtils.isNotEmpty(user)) {
                            loginTimes = bean.getUserLoginErrorNumCache(user.getTenantId(), reqAccount);
                        } else {
                            loginTimes = bean.getUserLoginErrorNumCache("-1", reqAccount);
                        }

                        // Integer loginTimes = (Integer) session.getAttribute("_loginTime_");
                        if (loginTimes == null) {
                            loginTimes = new Integer(1);
                        }
                        //  session.setAttribute("_loginTime_", loginTimes = Integer.valueOf(loginTimes.intValue() + 1));

                        if (BeanUtils.isNotEmpty(user)) {
                            bean.putLoginErrorNumInCache(user.getTenantId(), reqAccount, Integer.valueOf(loginTimes.intValue() + 1));
                        } else {
                            bean.putLoginErrorNumInCache("-1", reqAccount, Integer.valueOf(loginTimes.intValue() + 1));
                        }
                        String loginMsg = "，还剩：" + (lockTimes - loginTimes) + "次!";
                        if (loginTimes >= lockTimes) {
                            loginUserService.lockedUser(reqAccount, 2);
                            if (loginTimes == lockTimes) {
                                loginMsg = "，账号已锁定，请联系管理员！";
                            }
                            if (BeanUtils.isNotEmpty(user)) {
                                bean.delUserLoginErrorNumCache(user.getTenantId(), reqAccount);
                            } else {
                                bean.delUserLoginErrorNumCache("-1", reqAccount);
                            }

                        }
                        errorMsg = "账号或密码错误次数：" + loginTimes + "次" + loginMsg;
                        throw new RuntimeException(errorMsg);
                    }
                }

            }
            throw new RuntimeException(errorMsg);
        }

        //判断登录用户有没有绑定涉密计算机
        if (!isAdmin(reqAccount)) {
            SecurityMachinePersonService machineService = AppUtil.getBean(SecurityMachinePersonService.class);
            if (machineService != null) {
                List<String> machineIdList = machineService.queryPersonLimitByAccount(reqAccount);
                if (machineIdList != null && machineIdList.size() > 0) {
                    boolean allowded = false;
                    List<String> ipList = machineService.queryMachineIps(machineIdList);
                    if (ipList != null && ipList.size() > 0) {
                        for (String ip : ipList) {
                            if (ip.equals(IP)) {
                                allowded = true;
                                break;
                            }
                        }
                    }
                    if (!allowded) {
                        throw new RuntimeException("用户【" + reqAccount + "】已绑定涉密机器，不能在当前机器上登录!");
                    }
                }
            }
        }

        // 当前切中的方法
        boolean isMobile = HttpUtil.isMobile(request);

        // Reload password post-security so we can generate the token

        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());
        }

        //处理单用户登录
        handleSingleLogin(isMobile, MapUtil.getString(userAttrs, "tenantId"), account, token);
        //删除登录错误次数
        // session.removeAttribute("_loginTime_");
        bean.delUserLoginErrorNumCache(MapUtil.getString(userAttrs, "tenantId"), account);
        //修改登录时间
        loginUserService.updateLastLoginTime(account);
        //loginUserService.updateUserIp();
        return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId, jwtConfig.getExpirationLong(), loginStatus, userAttrs));
    }

    private boolean isAdmin(String account) {
        String tmp = SystemConstants.SYSTEM_ACCOUNT;
        String[] split = tmp.split(",");
        for (String _account : split) {
            if (_account.equals(account)) {
                return true;
            }
        }
        return false;
    }

    // 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;
    }

    @RequestMapping(value = "/sso/auth", method = RequestMethod.GET, produces = {"application/json; charset=utf-8"})
    @ApiOperation(value = "登录系统-单点登录", httpMethod = "GET", notes = "登录系统-单点登录")
    public ResponseEntity<?> ssoAuth(@RequestParam Optional<String> ticket, @RequestParam Optional<String> code, @RequestParam Optional<String> ssoMode, @RequestParam String service) throws AuthenticationException, 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 (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));
    }

    @RequestMapping(value = "/sso/weixin", method = RequestMethod.GET, produces = {"application/json; charset=utf-8"})
    @ApiOperation(value = "企业微信应用进入手机端-单点登录", httpMethod = "GET", notes = "企业微信应用进入手机端-单点登录")
    public ResponseEntity<?> ssoWeixin(@RequestParam Optional<String> code) throws AuthenticationException, IOException {
        String resultJson = HttpUtil.sendHttpsRequest(applicationFeignService.getUserInfoUrl("weChatWork", code.get()), "", "POST");
        logger.error("企业微信登录返回结果：" + resultJson);
        ObjectNode result = null;
        try {
            result = (ObjectNode) JsonUtil.toJsonNode(resultJson);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        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();
                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());
    }

    @RequestMapping(value = "/sso/weixinPublic", method = RequestMethod.GET, produces = {"application/json; charset=utf-8"})
    @ApiOperation(value = "微信公众号进入手机端", httpMethod = "GET", notes = "微信公众号进入手机端")
    public ResponseEntity<?> weixinPublic(@RequestParam Optional<String> code) throws AuthenticationException, IOException {
        String resultJson = HttpUtil.sendHttpsRequest(applicationFeignService.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());
    }

    @RequestMapping(value = "/sso/dingTalk", method = RequestMethod.GET, produces = {"application/json; charset=utf-8"})
    @ApiOperation(value = "钉钉进入手机端", httpMethod = "GET", notes = "微信公众号进入手机端")
    public ResponseEntity<?> dingTalk(@RequestParam Optional<String> code) throws AuthenticationException, IOException {
        String resultJson = HttpUtil.sendHttpsRequest(applicationFeignService.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());
    }

    @RequestMapping(value = "/sso/ykz", method = RequestMethod.GET, produces = {"application/json; charset=utf-8"})
    @ApiOperation(value = "愉快政单点登录", httpMethod = "GET", notes = "愉快政单点登录")
    public ResponseEntity<?> dingTalkYkz(@RequestParam Optional<String> code) throws AuthenticationException, IOException {
        //根据愉快政的临时授权码查询愉快政的登录用户信息
        String resultJson = applicationFeignService.getUserInfoForYkz(code.get());
        ObjectNode result = null;
        try {
            result = (ObjectNode) JsonUtil.toJsonNode(resultJson);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        boolean loginStatus = true;
        if (result.has("accountId")) {
            // String dingtalkId = result.get("accountId").asText();
            String dingtalkAccount = result.get("account").asText();
            String dingtalkEmployeeCode = result.get("employeeCode").asText();
            //根据愉快政的用户ID查询与平台绑定的用户信息
            JsonNode simpleUser = uCFeignService.getUserByDingtalkId(dingtalkEmployeeCode);
            if (BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()) {
                logger.info("查无与您愉快政账号【" + dingtalkAccount + "】绑定的系统账号！");
                loginStatus = false;
                return ResponseEntity.ok(new JwtAuthenticationResponse(dingtalkEmployeeCode, dingtalkAccount, loginStatus));
            }

            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 = "";

                Map<String, Object> userAttrs = new HashMap<String, Object>();
                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
                //修改登录时间
                loginUserService.updateLastLoginTime(account);
                //loginUserService.updateUserIp();
                return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId, jwtConfig.getExpirationLong(), loginStatus, userAttrs));
            } else {
                throw new RuntimeException("登录失败！账号:" + account + "不存在");
            }
        }
        throw new RuntimeException("愉快政验证失败 ： " + result.get("message").asText());
    }

    @RequestMapping(value = "/sso/ykzBand", method = RequestMethod.POST, produces = {"application/json; charset=utf-8"})
    @ApiOperation(value = "愉快政绑定并登录", httpMethod = "POST", notes = "愉快政绑定并登录")
    public ResponseEntity<?> dingTalkYkzBand(@RequestBody JwtAuthenticationRequest authenticationRequest, @RequestParam Optional<String> ykzEmployeeCode, @RequestParam Optional<String> ykzAccount) throws AuthenticationException {
        String reqAccount = authenticationRequest.getUsername();

        String reqPassword = "";
        //清除用户缓存
        this.deleteUserDetailsCache(reqAccount);
        String errorMsg = "";
        try {
            //密码Base64解密
            reqPassword = Base64.getFromBase64(authenticationRequest.getPassword());
            authenticate(reqAccount, reqPassword);
        } catch (Exception e) {
            logger.error(String.format("Login failed account[%s].", reqAccount), e);
            errorMsg = "账号或密码错误";
            if (BeanUtils.isNotEmpty(e.getCause()) && e.getCause() instanceof CertificateException) {
                CertificateException ce = (CertificateException) e.getCause();
                errorMsg = ce.getMessage();
            }
            if (e instanceof LockedException) {
                errorMsg = "账号被禁用或离职";
            }
        }
        HttpServletRequest request = HttpUtil.getRequest();
        HttpSession session = request.getSession();
        String IP = IPUtils.getIpAddr(request);
        //验证密码输入错误次数
        if (StringUtil.isNotEmpty(errorMsg)) {
            if (errorMsg.equals("账号或密码错误") && !isAdmin(reqAccount)) {
                //获取密码策略
                PwdStrategyService service = AppUtil.getBean(PwdStrategyService.class);
                if (service != null) {
                    JsonNode json = service.getJsonDefault();
                    if (BeanUtils.isNotEmpty(json)) {
                        // 密码错误锁定状态（0：关闭【默认】 1：开启 ）
                        int lockStatus = json.get("lockStatus").asInt();
                        // 密码错误次数
                        int lockTimes = json.get("lockTimes").asInt();
                        // 启用策略	0:停用，1:启用
                        int enable = json.get("enable").asInt();
                        if (enable == 1 && lockStatus == 1) {
                            Integer loginTimes = (Integer) session.getAttribute("_loginTime_");
                            if (loginTimes == null) {
                                loginTimes = new Integer(0);
                            }
                            session.setAttribute("_loginTime_", loginTimes = Integer.valueOf(loginTimes.intValue() + 1));
                            if (loginTimes >= lockTimes) {
                                loginUserService.lockedUser(reqAccount, 2);
                            }
                        }
                    }
                }
            }
            throw new RuntimeException(errorMsg);
        }

        //判断登录用户有没有绑定涉密计算机
        if (!isAdmin(reqAccount)) {
            SecurityMachinePersonService machineService = AppUtil.getBean(SecurityMachinePersonService.class);
            if (machineService != null) {
                List<String> machineIdList = machineService.queryPersonLimitByAccount(reqAccount);
                if (machineIdList != null && machineIdList.size() > 0) {
                    boolean allowded = false;
                    List<String> ipList = machineService.queryMachineIps(machineIdList);
                    if (ipList != null && ipList.size() > 0) {
                        for (String ip : ipList) {
                            if (ip.equals(IP)) {
                                allowded = true;
                                break;
                            }
                        }
                    }
                    if (!allowded) {
                        throw new RuntimeException("用户【" + reqAccount + "】已绑定涉密机器，不能在当前机器上登录!");
                    }
                }
            }
        }

        // 当前切中的方法
        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();

            JsonNode simpleUser = uCFeignService.getUserByDingtalkId(ykzEmployeeCode.get());
            if (simpleUser != null) {
                String bandUserAccount = simpleUser.get("account").asText();
                if (!bandUserAccount.equals(userDetails.getUsername())) {
                    throw new RuntimeException("愉快政账号【" + ykzAccount.get() + "】已绑定平台其他用户，请联系管理员！");
                }
            }
            ObjectNode userUnite = JsonUtil.getMapper().createObjectNode();
            userUnite.put("userId", userId);
            userUnite.put("dingtalkId", ykzEmployeeCode.get());
            uCFeignService.updateUserUnite(userUnite);

            request.setAttribute("loginUser", String.format("%s[%s]", userName, account));
            //校验密码策略是否需要修改密码
            loginStatus = checkUser(user, reqPassword);
            userAttrs.put("tenantId", user.getTenantId());
        }

        //处理单用户登录
        handleSingleLogin(isMobile, MapUtil.getString(userAttrs, "tenantId"), account, token);
        //删除登录错误次数
        session.removeAttribute("_loginTime_");
        //修改登录时间
        loginUserService.updateLastLoginTime(account);
        //loginUserService.updateUserIp();
        return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId, jwtConfig.getExpirationLong(), loginStatus, userAttrs));
    }


    @RequestMapping(value = "/sso/ykzEmployeeCode", method = RequestMethod.POST, produces = {"application/json; charset=utf-8"})
    @ApiOperation(value = "愉快政EmployeeCode登录", httpMethod = "POST", notes = "愉快政EmployeeCode登录")
    public ResponseEntity<?> dingTalkYkzEmployeeCode(@RequestParam Optional<String> ykzEmployeeCode) throws AuthenticationException {
        if (ykzEmployeeCode == null) {
            throw new RuntimeException("EmployeeCode不能为空！");
        }

        String employeeCodeEncrypt = ykzEmployeeCode.get();
        String employeeCode = "";
        try {
            employeeCode = EncryptUtil.decrypt(employeeCodeEncrypt, "AEKXtLARGEZENITH", "AES/ECB/PKCS7Padding");
        } catch (Exception e) {
            throw new RuntimeException("解码错误！", e);
        }

        boolean loginStatus = true;
        if (StringUtil.isNotEmpty(employeeCode)) {

            //根据愉快政的用户ID查询与平台绑定的用户信息
            JsonNode simpleUser = uCFeignService.getUserByYkzEmployeeCode(employeeCode);
            if (BeanUtils.isEmpty(simpleUser) || simpleUser.isNull()) {
                logger.info("查无与您愉快政账号绑定的系统账号！");
                loginStatus = false;
                throw new RuntimeException("查无与您愉快政账号绑定的系统账号！");
            }

            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 = "";

                Map<String, Object> userAttrs = new HashMap<String, Object>();
                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
                //修改登录时间
                loginUserService.updateLastLoginTime(account);
                //loginUserService.updateUserIp();
                return ResponseEntity.ok(new JwtAuthenticationResponse(token, userName, account, userId, jwtConfig.getExpirationLong(), loginStatus, userAttrs));
            } else {
                throw new RuntimeException("登录失败！账号:" + account + "不存在");
            }
        }
        throw new RuntimeException("登录验证失败 【employeeCode】为空！ ");
    }


    /**
     * @return
     * @throws IOException
     */
    @RequestMapping(value = "/sso/info", method = RequestMethod.GET, produces = {"application/json; charset=utf-8"})
    @ApiOperation(value = "单点登录配置", httpMethod = "GET", notes = "单点登录配置")
    public ResponseEntity<Map<String, Object>> isUseCas(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("enable", ssoConfig.isEnable());
        map.put("ssoUrl", ssoConfig.getSsoUrl());
        map.put("ssoLogoutUrl", ssoConfig.getSsoLogoutUrl());
        return ResponseEntity.ok(map);
    }

    @RequestMapping(value = "/refresh", method = RequestMethod.GET)
    @ApiOperation(value = "刷新token", httpMethod = "GET", notes = "刷新token")
    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);
        return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken, "", "", ""));
    }

    @RequestMapping(value = "/signout", method = RequestMethod.GET)
    @ApiOperation(value = "退出登录", httpMethod = "GET", notes = "使token的状态失效,必须设置jwt.single和jwt.stricky均为true")
    public CommonResult<String> 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);
        return new CommonResult<>("退出成功");
    }

    /**
     * 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 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();
                // 锁定后自动解锁时长
                long autoUnlockTime = json.get("autoUnlockTime").asLong();
                // 首次登录是否必须修改密码（0：否  1：是）
                int initUpdate = json.get("initUpdate").asInt();
                // 启用策略	0:停用，1:启用
                int enable = json.get("enable").asInt();

                if (2 == user.getLockedStatus()) {
                    LocalDateTime lockedTime = user.getLockedTime();
                    Long intervalMinutes = (LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() - lockedTime.toInstant(ZoneOffset.of("+8")).toEpochMilli()) / 60000L;
                    if (intervalMinutes >= autoUnlockTime) {
                        loginUserService.lockedUser(user.getAccount(), 1);
                    } else {
                        long sy = autoUnlockTime - intervalMinutes;
                        throw new RuntimeException("账号在锁定状态中,请于【" + sy + "分钟】后登录，或联系管理员解锁！");
                    }
                }

                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;
                            }
                        }
                    }*/

                    //密码首次修改
                    if (initUpdate == 1) {
                        if (null == user.getLastLoginTime()) {
                            return false;
                        }
                    }

                    //密码策略时间
                    LocalDateTime pwdCreateTime = user.getPwdCreateTime();
                    if (BeanUtils.isNotEmpty(pwdCreateTime) && duration > 0) {
                        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 void handleSingleLogin(boolean isMobile, String tenantId, String username, String token) {
        String userAgent = isMobile ? "mobile" : "pc";
        //如果是单用户登录
        if (jwtConfig.isSingle()) {
            // 非SaaS模式
            if (StringUtil.isEmpty(tenantId) && !saasConfig.isEnable()) {
                tenantId = "-1";
            }
            // 以当前登录设备、租户ID、用户账号为key将token存放到缓存中
            jwtTokenHandler.putTokenInCache(userAgent, tenantId, username, jwtConfig.getExpiration(), token);
        } else {
            HttpServletRequest request = HttpUtil.getRequest();
            String IP = IPUtils.getIpAddr(request);
            jwtTokenHandler.putTokenInCache(userAgent, tenantId, username, IP, jwtConfig.getExpiration(), token);
        }
        //处理用户登录日志
        loginLogService.log(username, isMobile ? "mobile" : "pc");
    }

    /**
     * 处理用户登出
     *
     * @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);
        }
    }
}