package com.njcn.shiro.realm;


import com.njcn.enums.LogTypeEnum;
import com.njcn.enums.ProjectEnum;
import com.njcn.enums.RoleGroupEnum;
import com.njcn.enums.UserStatusEnum;
import com.njcn.enums.app.UserCodeEnum;
import com.njcn.mapper.configuration.SysMapper;
import com.njcn.mapper.user.AppUserMapper;
import com.njcn.mapper.user.SecretKeyMapper;
import com.njcn.mapper.user.UserMapper;
import com.njcn.pojo.commons.RedisDB;
import com.njcn.pojo.configuration.SysSet;
import com.njcn.pojo.data.MenuEntity;
import com.njcn.pojo.log.UserLogin;
import com.njcn.pojo.system.SubSystem;
import com.njcn.pojo.user.*;
import com.njcn.service.log.UserLogDetailService;
import com.njcn.service.log.UserLoginService;
import com.njcn.service.system.SystemService;
import com.njcn.service.user.UserService;
import com.njcn.serviceimpl.configuration.SysServiceImpl;
import com.njcn.shiro.token.ShiroToken;
import com.njcn.shiro.token.TokenManager;
import com.njcn.sso.pojo.user.User;
import com.njcn.utils.*;
import com.njcn.utils.SM.SM4Utils;

import com.njcn.utils.redis.JedisManager;
import jdk.nashorn.internal.parser.Token;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;

public class DataRealm extends AuthorizingRealm {

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

    public static final String ROLE_NAME_KEY = "njcn-roleName";

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private SysServiceImpl sysService;

    @Autowired
    private SysMapper sysSetMapper;

    @Autowired
    private SecretKeyMapper secretKeyMapper;

    @Autowired
    UserLoginService userLoginService;

    @Autowired
    private UserUtil userUtil;

    @Autowired
    private AppConfig appConfig;

    @Autowired
    private UserService userService;

    @Autowired
    private UserLogDetailService userLogDetailService;

    @Resource
    private SystemService systemService;

    @Resource
    private AppUserMapper appUserMapper;

    @Resource
    private JedisManager jedisManager;


    /**
     * 认证信息，主要针对用户登录，
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        ShiroToken token = (ShiroToken) authcToken;
        String username = token.getUsername();
        String password = token.getPswd();
        //判断是否为认证中心登录
        String item = token.getItem();
        if (ProjectEnum.SSO.getItem().equalsIgnoreCase(item)) {
            return ssoDoGetAuthenticationInfo(username, password, token);
        } else if (ProjectEnum.APP.getItem().equalsIgnoreCase(item)) {
            return appDoGetAuthenticationInfo(username, token.getType(), token.getDevCode(), password, token);
        } else {
            return subSystemDoGetAuthenticationInfo(username, password, token);
        }
    }

    /**
     * app用户执行登录过程
     */
    private AuthenticationInfo appDoGetAuthenticationInfo(String username, String type, String devCode, String password, ShiroToken token) throws AccountException {
        //首先判断有没有该用户
        AppUser appUser = new AppUser();
        appUser.setPhone(username);
        appUser = appUserMapper.selectOne(appUser);
        if (null == appUser) {
            throw new AccountException(UserCodeEnum.LOGIN_WRONG_PWD.getMsg());
        }
        //错误次数
        int errorTime = appUser.getLoginErrorTimes();
        Date firstErrorTime = appUser.getLoginFirstErrorTime();
        if (errorTime > 5) {
            Long now = System.currentTimeMillis();
            Long errorLong = null == firstErrorTime ? now : firstErrorTime.getTime();
            Long minus = now - errorLong;
            if (minus < 30 * 60 * 1000) {
                throw new AccountException(UserCodeEnum.LOGIN_LOCKED.getMsg());
            } else {
                //超过半小时，将错误次数置为0
                errorTime = 0;
            }
        }
        if (type.equalsIgnoreCase("0")) {
            //验证码登录
            String redisCode = jedisManager.getValueByKey(RedisDB.CODE_DB, username + devCode);
            if (StringUtils.isEmpty(redisCode) || !redisCode.equalsIgnoreCase(password)) {
                if (errorTime == 0) {
                    appUser.setLoginFirstErrorTime(new Date());
                }
                errorTime = errorTime + 1;
                appUser.setLoginErrorTimes(errorTime);
                appUserMapper.updateByPrimaryKey(appUser);
                throw new AccountException(UserCodeEnum.LOGIN_WRONG_PWD.getMsg());
            } else {
                jedisManager.deleteByKey(RedisDB.CODE_DB, username + devCode);
            }
        } else {
            //密码登录
            String strpwd;
            try {
                strpwd = AESUtil.aesPKCS5PaddingEncrypt(password);
            } catch (Exception e) {
                throw new AccountException(UserCodeEnum.LOGIN_WRONG_PWD.getMsg());
            }
            AppUser temp = new AppUser();
            temp.setPhone(username);
            temp.setPassword(strpwd);
            temp = appUserMapper.selectOne(temp);
            if (null == temp) {
                if (errorTime == 0) {
                    appUser.setLoginFirstErrorTime(new Date());
                }
                errorTime = errorTime + 1;
                appUser.setLoginErrorTimes(errorTime);
                appUserMapper.updateByPrimaryKey(appUser);
                throw new AccountException(UserCodeEnum.LOGIN_WRONG_PWD.getMsg());
            }
        }
        appUser.setLoginErrorTimes(0);
        appUser.setLoginTime(new Date());
        appUser.setDevCode(devCode);
        logger.info("更新手机id："+devCode);
        appUserMapper.updateByPrimaryKey(appUser);
        return new SimpleAuthenticationInfo(appUser, token.getPassword(), getName());
    }

    /**
     * 根据用户名密码认证登录信息--子系统
     */
    private AuthenticationInfo subSystemDoGetAuthenticationInfo(String username, String password, ShiroToken token) {
        //认证已经认证过了，子系统只需要将用户信息获取出来存在session中便可
        User user = new User();
        user.setLoginName(username);
        user.setPassword(password);
        user = userMapper.selectOne(user);
        //更新最后一次登录时间
        user.setLoginTime(new Date());
        userMapper.updateByPrimaryKeyBySql(user);
        user.setItem(token.getItem());
        user.setIp(token.getIp());
        return new SimpleAuthenticationInfo(user, token.getPassword(), getName());
    }

    /**
     * 根据用户名密码认证登录信息--SSO中心
     * <p>
     * 用户认证的先后顺序
     * 1：判断用户密码错误是否已经达到上限5rd
     * 2：认证用户密码是否正确
     * 3：判断用户的状态是否为非激活状态 0 注销 2 锁定 3 未审核 4 休眠 5 修改密码
     * 4：都满足条件后，判断密码是否过期
     * 5：用户是否达到休眠条件
     * 6：用户是否达到注销条件
     */
    private AuthenticationInfo ssoDoGetAuthenticationInfo(String username, String password, ShiroToken token) {
        User user = new User();
        user.setLoginName(username);
        user = userMapper.selectOne(user);
        if (null == user) {
            throw new UnknownAccountException(UserStatusEnum.ERROR_ACCOUNT.getMessage());
        } else {
            Calendar now = Calendar.getInstance();
            //判断用户是否为临时用户
            boolean isCasual = judgeSleepTempUser(user);
            //获取系统配置
            SysSet sysSet = new SysSet();
            sysSet.setState(1);
            sysSet = sysSetMapper.selectOne(sysSet);
            if (null == sysSet) {
                throw new AccountException("缺失系统配置信息");
            }
            //该用户的加密内容
            SecretKey secretk = new SecretKey();
            secretk.setUserIndex(user.getUserIndex());
            secretk = secretKeyMapper.selectOne(secretk);
            if (null == secretk) {
                try {
                    userLogDetailService.saveUserLog(username, username, "密码已破坏", "失败", appConfig.getSystemFlag(), token.getIp(), LogTypeEnum.SYSTEM.toString(), 2);
                } catch (javax.security.auth.login.AccountException e) {
                    logger.error(e.getMessage());
                }
                throw new AccountException("用户密码被破坏");
            } else {
                //针对所有用户的登录限制
                String standBy = secretk.getStandBy();
                String secretkey = secretk.getSecretKey();
                // 开始校验用户的密码是否正确
                String mPwd;//国密4加密的密码
                String strpwd;//国密4加密的（密码+工作秘钥）
                try {
                    SM4Utils sm4 = new SM4Utils(secretkey);
                    mPwd = sm4.encryptData_ECB(password);//SM4加密密码
                    strpwd = sm4.encryptData_ECB(mPwd + secretkey);//SM4加密(密码+工作秘钥）
                } catch (Exception ex) {
                    throw new AccountException("用户密码国密加密失败");
                }
                // 两次MD加密是否一致，一致后继续验证
                if (standBy.equalsIgnoreCase(mPwd)) {
                    // 验证3des加密后是否一致
                    if (!user.getPassword().equalsIgnoreCase(strpwd)) {
                        dealWrongPwd(user, sysSet);
                        throw new AccountException(UserStatusEnum.ERROR_ACCOUNT.getMessage());
                    } else {
                        //判断是否为超级管理员
                        RoleGroup roleGroup = userUtil.getRoleGroupByUserIndex(user.getUserIndex());
                        user.setIp(token.getIp());
                        if (!roleGroup.getRoleGpName().equalsIgnoreCase(RoleGroupEnum.ROOT.getEname())) {
                            //判断当前是不是存在子系统激活
                            if (CollectionUtils.isEmpty(systemService.getActiveSystem())) {
                                throw new AccountException(UserStatusEnum.NO_SUB_SYSTEM.getMessage());
                            }
                            if (user.getState() == UserStatusEnum.LOCKED.getCode()){
                                //先判断是否满足锁定时间，再看情况
                                Calendar calendar1 = Calendar.getInstance();
                                calendar1.setTime(user.getLockTime());
                                calendar1.add(Calendar.MINUTE, sysSet.getLockTime());
                                if (now.after(calendar1)) {
                                    userMapper.updateLockState(1, user.getUserIndex());
                                    user.setState(1);
                                }else{
                                    throw new AccountException(UserStatusEnum.LOCKED.getMessage());
                                }
                            }
                            if (user.getState() == UserStatusEnum.DESTROY.getCode()) {
                                throw new AccountException(UserStatusEnum.DESTROY.getMessage());
                            }  else if (user.getState() == UserStatusEnum.UNCHECK.getCode()) {
                                throw new AccountException(UserStatusEnum.UNCHECK.getMessage());
                            } else if (user.getState() == UserStatusEnum.SLEEP.getCode()) {
                                throw new AccountException(UserStatusEnum.SLEEP.getMessage());
                            } else if (user.getState() == UserStatusEnum.OVERDUE.getCode()) {
                                throw new AccountException(UserStatusEnum.OVERDUE.getMessage());
                            } else if (user.getLoginTime().toString().equals(user.getRegisterTime().toString())) { //add by sw 用户首次登录提示修改密码
                                throw new AccountException(UserStatusEnum.FIRST_LOGIN.getMessage());
                            } else {
                                judgeIp(user, sysSet, now, isCasual);
                            }
                        }

                    }
                } else {
                    dealWrongPwd(user, sysSet);
                    throw new AccountException(UserStatusEnum.ERROR_ACCOUNT.getMessage());
                }
            }
        }
        //获取上次登陆时间
        String lastTime = "";
        try {
            user.setLoginTimes(PubUtils.date2String(new Date(), "yyyy-MM-dd HH:mm:ss"));
            lastTime = PubUtils.date2String(user.getLoginTime(), "yyyy-MM-dd HH:mm:ss");
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        //获取上次登陆的IP
        UserLogin userLogin = userLoginService.getUserLogin(user.getUserIndex());
        if (null == userLogin) {
            user.setLastIp("无记录");
        } else {
            user.setLastIp(userLogin.getIP());
        }
        user.setItem(token.getItem());
        //更新登录时间
        user.setLoginTime(new Date());
        user.setLoginErrorTimes(0);
        userMapper.updateByPrimaryKeyBySql(user);
        user.setLastLoginTime(StringUtils.isNotBlank(lastTime) ? lastTime : user.getLoginTimes());
        return new SimpleAuthenticationInfo(user, token.getPassword(), getName());
    }

    /**
     * 用户名密码不匹配后的处理
     *
     * @param user   用户信息
     * @param sysSet 系统配置
     */
    private void dealWrongPwd(User user, SysSet sysSet) {
        //判断该用户密码错误次数是否达到上限
        if (user.getLoginErrorTimes() + 1 >= sysSet.getLimitTimes()) {
            //更新用户数据，锁定用户
            userService.updateUserLocked(user.getLoginName());
            throw new AccountException(UserStatusEnum.LOCKED.getMessage());
        } else {
            //更新用户密码错误次数
            userService.updateUserPasswordError(user.getLoginName());
        }

    }


    /**
     * 判断是否为临时用户
     */
    private boolean judgeSleepTempUser(User user) {
        if (user.getCasualUser() == 0) {
            return false;
        }
        return true;
    }


    /**
     * 判断IP是否合法
     *
     * @param user   用户信息
     * @param sysSet 系统配置
     */
    private Boolean judgeIp(User user, SysSet sysSet, Calendar now, boolean isCasual) {
        Boolean matchs;
        //针对用户自定义的设置判断
        if (StringUtils.isBlank(user.getIp())) {
            throw new AccountException(UserStatusEnum.ERROR_IP.getMessage());
        } else {
            String ipSection = user.getLimitIpStart() + "-" + user.getLimitIpEnd();
            if (!PubUtils.ipExistsInRange(user.getIp(), ipSection)) {
                throw new AccountException(UserStatusEnum.ERROR_IP.getMessage());
            } else {
                matchs = judgeTime(user, sysSet, now, isCasual);
            }
        }
        return matchs;
    }

    /**
     * 判断当前用户是否在时间段内登录
     *
     * @param sysSet 系统配置
     */
    private Boolean judgeTime(User user, SysSet sysSet, Calendar now, boolean isCasual) {
        Boolean matchs;
        Calendar calendar = Calendar.getInstance();
        int nowHour = calendar.get(Calendar.HOUR_OF_DAY);
        String limitTime = user.getLimitTime();
        if (StringUtils.isBlank(limitTime)) {
            //没有做限制
            matchs = judgePwdTimeValidity(user, sysSet, now, isCasual);
        } else {
            String[] strarray = limitTime.split("-");
            if (nowHour >= Integer.parseInt(strarray[0]) && nowHour < Integer.parseInt(strarray[1])) {
                //符合限制
                matchs = judgePwdTimeValidity(user, sysSet, now, isCasual);
            } else {
                throw new AccountException(UserStatusEnum.ERROR_LOGIN_TIME.getMessage());
            }
        }
        return matchs;
    }

    /**
     * 判断用户密码是否已经过有效期
     *
     * @param user   用户信息
     * @param sysSet 系统配置
     */
    private Boolean judgePwdTimeValidity(User user, SysSet sysSet, Calendar now, boolean isCasual) {
        Boolean matchs;
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(user.getPsdvalidity());
        calendar.add(Calendar.MONTH, sysSet.getLimitDate());
        if (now.before(calendar)) {
            //密码还在有效期内
            matchs = judgeLeisurePwd(user, sysSet, now, isCasual);
        } else {
            throw new AccountException(UserStatusEnum.OVERDUE.getMessage());
        }
        return matchs;
    }

    /**
     * 判断用户是否已经很久没登录，需要修改面或者已经被注销
     *
     * @param user   用户信息
     * @param sysSet 系统配置
     */
    private Boolean judgeLeisurePwd(User user, SysSet sysSet, Calendar now, boolean isCasual) {
        //闲置修改密码
        Calendar idel = Calendar.getInstance();
        //闲置注销用户
        Calendar loginOut = Calendar.getInstance();
        if (isCasual) {
            //临时用户根据配置文件
            idel.setTime(user.getRegisterTime());
            idel.add(Calendar.MONTH, sysSet.getCasualUserTime());
            loginOut.setTime(user.getLoginTime());
            loginOut.add(Calendar.MONTH, sysSet.getLogOutTime() + sysSet.getCasualUserTime());
        } else {
            //正式用户根据系统配置
            idel.setTime(user.getLoginTime());
            idel.add(Calendar.MONTH, sysSet.getLimitIdle());
            loginOut.setTime(user.getLoginTime());
            loginOut.add(Calendar.MONTH, sysSet.getLimitLogout() + sysSet.getLimitIdle());
        }
        if (now.after(idel) && now.before(loginOut)) {
            //将用户状态置为休眠
            user.setState(4);
            userMapper.updateByPrimaryKeyBySql(user);
            throw new AccountException(UserStatusEnum.SLEEP.getMessage());
        } else if (now.after(loginOut)) {
            //将用户状态置为注销
            user.setState(0);
            userMapper.updateByPrimaryKeyBySql(user);
            throw new AccountException(UserStatusEnum.DESTROY.getMessage());
        }

        return Boolean.TRUE;
    }

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        User user = TokenManager.getToken();

        //判断是否为认证中心的授权
        if (user.getItem().equalsIgnoreCase(ProjectEnum.SSO.getItem())) {
            RoleGroup roleGroup = userUtil.getRoleGroupByUserIndex(TokenManager.getUserId());
            if (null != roleGroup) {
                info.addRole(roleGroup.getRoleGpName());
            }
        } else {
            List<Role> roles = userUtil.getRoles(TokenManager.getUserId());
            if (!CollectionUtils.isEmpty(roles)) {
                for (Role role : roles) {
                    info.addRole(role.getRoleName());

                }
            }
        }

        List<String> roleId = new ArrayList<>();
        String sysType = null;
        List<Role> roles = userUtil.getRoles(TokenManager.getUserId());
        if (!CollectionUtils.isEmpty(roles)) {
            for (Role role : roles) {
                roleId.add(role.getRoleIndex());
            }
        }
        sysType = appConfig.getSysType();
        if (sysType.equals(ProjectEnum.getKeyByItem("pqs9900"))) {
            sysType = TokenManager.getSysType();
            if (sysType == null) {
                List<SubSystem> subSystems = systemService.getActiveSystem();
                if (subSystems.size() == 1) {
                    String item = subSystems.get(0).getSystemEName();
                    sysType = ProjectEnum.getKeyByItem(item);
                } else {
                    for (SubSystem subSystem : subSystems) {
                        if (subSystem.getDefaultSystem() == 1) {
                            String item = subSystem.getSystemEName();
                            sysType = ProjectEnum.getKeyByItem(item);
                            break;
                        }
                    }
                }
            }
        }

        if ((roleId != null && roleId.size() > 0 && !roleId.isEmpty()) && sysType != null) {
            List<MenuEntity> listMenu = sysService.getListMenu(roleId, sysType); //获取资源路径

            //授权权限
            if (!CollectionUtils.isEmpty(listMenu)) {
                for (MenuEntity menuEntity : listMenu) {
                    //如果是#则获取上级菜单一起形成一个url
                    if (menuEntity.getPath().equals("#")) {
                        for (int i = 0; i < listMenu.size(); i++) {
                            if (menuEntity.getParentNode().equals(listMenu.get(i).getGuid())) {
                                menuEntity.setPath(listMenu.get(i).getPath() + "/#");
                            }
                        }
                    }

                    info.addStringPermission(menuEntity.getPath());
                }
            }
        }

        return info;
    }

    /**
     * 清空当前用户权限信息
     */
    public void clearCachedAuthorizationInfo() {
        PrincipalCollection principalCollection = SecurityUtils.getSubject().getPrincipals();
        SimplePrincipalCollection principals = new SimplePrincipalCollection(
                principalCollection, getName());
        super.clearCachedAuthorizationInfo(principals);
    }

    /**
     * 指定principalCollection 清除
     */
    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principalCollection) {
        SimplePrincipalCollection principals = new SimplePrincipalCollection(
                principalCollection, getName());
        super.clearCachedAuthorizationInfo(principals);
    }

//    public static void main(String[] args) {
//        System.out.println(UUID.randomUUID().toString());
//    }

}
