package com.ruoyi.framework.web.service;

import javax.annotation.Resource;

import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.dto.SchoolGradeClassesDto;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.enums.AccessFromType;
import com.ruoyi.common.enums.BoolValues;
import com.ruoyi.common.enums.CardStatus;
import com.ruoyi.common.enums.NewgaokaoType;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.exception.base.CustomException;
import com.ruoyi.common.utils.*;
import com.ruoyi.system.domain.ZuserToken;
import com.ruoyi.system.service.ISysRoleService;
import com.ruoyi.system.service.IZuserTokenService;
import com.ruoyi.voluntary.domain.*;
import com.ruoyi.voluntary.service.*;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 登录校验方法
 *
 * @author voluntary
 */
@Component
public class SysLoginService
{
    @Autowired
    private TokenService tokenService;

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ISysUserService userService;

    @Autowired
    private ISysRoleService roleService;

    @Autowired
    private IZuserTokenService zuserTokenService;

    @Autowired
    private ISysConfigService sysConfigService;
    @Autowired
    private IBusiCardsService cardsService;

    /**
     * 登录验证
     *
     * @param loginBody 登录参数
     * @return 结果
     */
    public String login(LoginBody loginBody)
    {
        String username=loginBody.getUsername();
        String password=loginBody.getPassword();
        String type=loginBody.getType();
        String code=loginBody.getCode();
        String uuid=loginBody.getUuid();
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        Integer from = 0;
        if(AccessFromType.isBack (type)){
            from = 2 ;
            if (captcha == null)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(null,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                throw new CaptchaExpireException();
            }
            if (!code.equalsIgnoreCase(captcha))
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(null,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                throw new CaptchaException();
            }
        }else if(AccessFromType.startsWithFrontApp(username)||AccessFromType.startsWithFront(username)||AccessFromType.startsWithH5(username)||AccessFromType.startsWithWechat(username)){
            //如果是front，则放行通过（无验证码）,app web端及h5均归类为前端
            from = 1;
        }

        // 用户验证
        LoginUser loginUser = getUser(username, password,from);
        loginUser.getUser().setUserType(loginBody.getType());

        //初始化登录时给sys_user表中的currentRoleId字段赋值
        loginUser = updateCurrentRoldId(loginUser);

        SysUser user= loginUser.getUser();
        recordLoginInfo(user);

        // TODO 踢出原来的登陆用户:20240914废弃
//        String multiUser = sysConfigService.selectConfigByKey("sys.user.login.multi");
//        if (StringUtils.isBlank(multiUser) || !multiUser.contains(loginUser.getUsername())) {
//            resetTokens(type + ":" + username);
//        }

        //单点登录
        resetTokensByUserIds(Arrays.asList(user.getUserId()),true);

        // 生成token
        String token = tokenService.createToken(loginUser);

        ZuserToken userToken = new ZuserToken();
        userToken.setUserName(StringUtils.isEmpty(loginBody.getType())?loginBody.getUsername():(loginBody.getType()+":"+loginBody.getUsername()));
        userToken.setUserId(user.getUserId());
        userToken.setToken(token);
        userToken.setUpdateTime(new Date());
        zuserTokenService.insertZuserToken(userToken);

        return token;
    }

    /**
     * 更新用户当前角色（默认角色）
     * 用于第一次登录时设置默认角色
     * @param loginUser
     * @return
     */
    private LoginUser updateCurrentRoldId(LoginUser loginUser){
        SysUser user= loginUser.getUser();
        if(null!= user&&(null==user.getCurrentRoleId()||0==user.getCurrentRoleId())){
            List<SysRole> roleList = roleService.selectRoleListByUserId2(user.getUserId());
            //多个角色时要取当前角色的数据
            if(CollectionUtils.isNotEmpty(roleList)){
                if(null==user.getCurrentRoleId()||0==user.getCurrentRoleId()){
                    user.setCurrentRoleId(roleList.get(0).getRoleId());
                    user.setUpdateTime(new Date());
                    userService.updateUserStatus(user);
                    loginUser.setUser(user);
                }
            }
        }
        return loginUser;
    }

    /**
     * 记录登录信息
     */
    public void recordLoginInfo(SysUser user)
    {
        user.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
        user.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(user);
    }

    /**
     *
     * @param username
     * @param password
     * @param from 1表示前端（web前端、app或h5） 2表示后端 3 表示 Batch
     * @return
     */
    private LoginUser getUser(String username, String password,Integer from) {
        String msgType = "user.password.error";
        switch (from){
            case 1:
                msgType = "user.password.error";
                break;
            case 2:
                msgType = "user.password.not.match";
                break;
        }
        // 用户验证
        Authentication authentication = null;
        LoginUser loginUser = null;
        try {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
            loginUser = (LoginUser) authentication.getPrincipal();
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(null,username, Constants.LOGIN_FAIL, MessageUtils.message(msgType)));
                throw new UserPasswordNotMatchException(msgType);
            } else {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(null,username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new CustomException(e.getMessage());
            }
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginUser.getUser(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        return loginUser;
    }

    /**
     * 此处不使用resetTokens是为了与志愿保持一致
     * 重置指定用户的登陆信息，强制重新登陆系统
     * @param userIds
     * @param isSso：适配志愿中锁分时也要清空用户token信息，此时设置为false，直接清空用户的token，设置为true就表示走单点登录的逻辑
     * @param isSetSso：后台配置的单点登录配置，设置为true时启用单点登录，为false时不启用单点登录
     * @return
     */
    public AjaxResult resetTokensByUserIds(List<Long> userIds,Boolean isSso) {
        if (isSso){
            String isSetSso = sysConfigService.selectConfigByKey( "is.set.sso");
            if (StringUtils.isBlank(isSetSso)||!Boolean.valueOf(isSetSso)){
                //参数配置的单点登录为false，此时不设置单点登录（将userids设置为空就不走清空流程），不需要清空用户已登录的token
                userIds = new ArrayList<>();
            }

            Set<String> excludeUserIdSet=new HashSet<>();
            String ssoWhiteListUserName = sysConfigService.selectConfigByKey("sso.white.list");
            if(StringUtils.isNotBlank(ssoWhiteListUserName)){
                Arrays.asList(ssoWhiteListUserName.split(",")).forEach(username->{
                    excludeUserIdSet.add(username);
                });
            }
            if (CollectionUtils.isNotEmpty(excludeUserIdSet)){
                List<Long> excludeUserIds = userService.selectUsers(null,null,excludeUserIdSet).stream().map(SysUser::getUserId).collect(Collectors.toList());
                //userIds减去excludeUserIds
                // 将排除的用户ID转化为Set，以便高效查找
                Set<Long> excludeSet = new HashSet<>(excludeUserIds);

                // 使用Stream API过滤掉排除的用户ID
                userIds = userIds.stream().filter(userId -> !excludeSet.contains(userId)).collect(Collectors.toList());
            }
        }
        return resetTokensByUserIds(userIds);
    }

    /**
     * 重置指定用户的登陆信息，强制重新登陆系统
     * @param startUserList
     * @return
     */
    public AjaxResult resetTokensByUserIds(List<Long> userIds) {
        if(CollectionUtils.isEmpty(userIds)){
            return AjaxResult.success();
        }
        ZuserToken zuserToken = new ZuserToken();
        zuserToken.setUserIds(userIds);
        List<ZuserToken> zuserTokenList = zuserTokenService.selectTopZuserTokenList(zuserToken);
        for (ZuserToken zt : zuserTokenList) {
            if(StringUtils.isNotNull(zt)){
                tokenService.removeUserToken(zt.getToken());
//                zuserTokenService.deleteZuserTokenById(zt.getId());
            }
        }
        List<Long> tokenIdList = zuserTokenList.stream().map(ZuserToken::getId).collect(Collectors.toList()).stream().distinct().collect(Collectors.toList());
        Long[] tokenIds = tokenIdList.toArray(new Long[tokenIdList.size()]);
        if(tokenIds.length>0){
            zuserTokenService.deleteZuserTokenByIds(tokenIds);
        }
        return AjaxResult.success();
    }

    public AjaxResult resetTokens(List<SysUser> startUserList) {
        List<Long> userIds = startUserList.stream().map(SysUser::getUserId).collect(Collectors.toList());
        return resetTokensByUserIds(userIds);
    }

    /**
     * 重置指定用户的登陆信息，强制重新登陆系统
     * @param userName
     * @return
     */
    public AjaxResult resetTokens(String userName) {
        ZuserToken zuserToken = new ZuserToken();
        zuserToken.setUserName(userName);
        List<ZuserToken> zuserTokenList = zuserTokenService.selectTopZuserTokenList(zuserToken);
        for (ZuserToken zt : zuserTokenList) {
            if(StringUtils.isNotNull(zt)){
                tokenService.removeUserToken(zt.getToken());
                zuserTokenService.deleteZuserTokenById(zt.getId());
            }
        }
        return AjaxResult.success();
    }

    public void isVip(){
        if(!judgeHasVipPermission()){
            throw new com.ruoyi.common.exception.CustomException("没有权限，请开通VIP", 405);
        }
    }

    public Boolean judgeHasVipPermission(){
        SysUser user = SecurityUtils.getLoginUser().getUser();
        if (!BoolValues.isTrue(user.getIsBind())) {
            return false;
        }
        if (null!=user.getCardId()){
            BusiCards card = cardsService.selectBusiCardsByCardId(user.getCardId());
            Date nowDate = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant());
            if (CardStatus.isExpired(card.getStatus()) || (null != card.getOutDate() && card.getOutDate().getTime() < nowDate.getTime())
                    ||null != card.getCloseId()||CardStatus.isClosed(card.getStatus())) {
                return false;
            }
        }else {
            return false;
        }
        return true;
    }
}
