package com.jeequan.jeepay.agent.service;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.agent.config.SystemYmlConfig;
import com.jeequan.jeepay.bizcommons.manage.auth.AuthManage;
import com.jeequan.jeepay.converter.BaseConverter;
import com.jeequan.jeepay.core.cache.ITokenService;
import com.jeequan.jeepay.core.cache.RedisUtil;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.SysUser;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.exception.JeepayAuthenticationException;
import com.jeequan.jeepay.core.jwt.JWTPayload;
import com.jeequan.jeepay.core.jwt.JWTUtils;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.core.model.security.JeeUserDetails;
import com.jeequan.jeepay.core.utils.JsonKit;
import com.jeequan.jeepay.db.entity.AgentConfig;
import com.jeequan.jeepay.db.entity.AgentInfo;
import com.jeequan.jeepay.db.entity.SysUserEntity;
import com.jeequan.jeepay.service.impl.*;
import com.jeequan.jeepay.service.mapper.SysEntitlementMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

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

/**
 * 认证Service
 *
 * @modify zhuxiao
 *
 * @date 2021-04-27 15:50
 */
@Slf4j
@Service
public class AuthService {

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private SysRoleService sysRoleService;
    @Autowired
    private SysRoleEntRelaService sysRoleEntRelaService;
    @Autowired
    private AgentInfoService agentInfoService;
    @Autowired
    private SysEntitlementMapper sysEntitlementMapper;
    @Autowired
    private SystemYmlConfig systemYmlConfig;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private AuthManage authManage;

    @Autowired
    private BaseConverter baseConverter;

    @Autowired private AgentConfigService agentConfigService;

    /**
     * 登录认证(密码登录， 包含MFA二次认证校验)
     **/
    public ApiRes authByAuthentication(String username, String password, String loginType, String vercodeToken, String mfaCode) {

        // 防穷举判断
        authManage.loginErrValidate(CS.SYS_ROLE_TYPE.AGENT, username);

        //1. 生成spring-security usernamePassword类型对象
        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);

        //spring-security 自动认证过程；
        // 1. 进入 JeeUserDetailsServiceImpl.loadUserByUsername 获取用户基本信息；
        //2. SS根据UserDetails接口验证是否用户可用；
        //3. 最后返回loadUserByUsername 封装的对象信息；
        Authentication authentication = null;
        try {
            authentication = authenticationManager.authenticate(upToken);
        } catch (JeepayAuthenticationException jex) {
            throw jex.getBizException() == null ? new BizException(jex.getMessage()) : jex.getBizException();
        } catch (BadCredentialsException e) {

            // 防穷举累加
            authManage.loginErrCount(CS.SYS_ROLE_TYPE.AGENT, username);

            // throw new BizException("用户名/密码错误！");
        } catch (AuthenticationException e) {
            log.error("AuthenticationException:", e);
            throw new BizException("认证服务出现异常， 请重试或联系系统管理员！");
        }
        JeeUserDetails jeeUserDetails = (JeeUserDetails) authentication.getPrincipal();
        SysUser sysUser = jeeUserDetails.getSysUser();

        //  第一次登录 && 该用户是否已经绑定MFA验证 :  返回前端，需要MFA的二次认证
        if (StringUtils.isEmpty(mfaCode) && sysUser.getMfaBindState() == CS.YES) {
            return ApiRes.ok4newJson("isMFA", true);
        }

        // mfaCode != null 说明： 二次输入的验证， 需要验证MFA验证码
        if (StringUtils.isNotEmpty(mfaCode)) {
            sysUserService.checkMFACode(sysUser.getSysUserId(), mfaCode);
        }

        // 删除图形验证码缓存数据  & 返回accessToken
        RedisUtil.del(CS.getCacheKeyImgCode(vercodeToken));
        String accessToken = userDetailsProcess(jeeUserDetails, loginType, null, JWTPayload.CREDENTIAL_AUTH_TYPE.PASSWD);

        // 更新上次登录时间
        sysUserService.updateLastLoginTime(sysUser.getSysUserId());

        JSONObject tokenRes = JsonKit.newJson(CS.ACCESS_TOKEN_NAME, accessToken);
        tokenRes.put("lastLoginTime", jeeUserDetails.getSysUser().getLastLoginTime());

        AgentInfo agentInfo = agentInfoService.getById(sysUser.getUserNo());
        List<String> agentNoList = new ArrayList<>();
        Optional.ofNullable(agentInfo.getPidArr()).ifPresent(item -> {
            agentNoList.addAll(Arrays.asList(item.split(",")));
        });

        agentNoList.add(agentInfo.getAgentNo());

        List<AgentConfig> list = agentConfigService.lambdaQuery()
                .in(AgentConfig::getAgentNo, agentNoList)
                .eq(AgentConfig::getGroupKey, "agentSiteConfig")
                .eq(AgentConfig::getConfigKey, "agentSiteUrl")
                .list();

        if (!list.isEmpty()) {
            tokenRes.put("oemSiteUrl", list.get(0).getConfigVal());
        }
        return ApiRes.ok(tokenRes);
    }

    /**
     * 认证（手机验证码方式）
     */
    public ApiRes authByPhoneCode(String phone, String loginType) {

        JeeUserDetails jeeUserDetails = (JeeUserDetails) userDetailsService.loadUserByUsername(phone);
        String accessToken = userDetailsProcess(jeeUserDetails, loginType, null, JWTPayload.CREDENTIAL_AUTH_TYPE.TEL_CODE);

        // 更新上次登录时间
        sysUserService.updateLastLoginTime(jeeUserDetails.getSysUserId());

        JSONObject tokenRes = JsonKit.newJson(CS.ACCESS_TOKEN_NAME, accessToken);
        tokenRes.put("lastLoginTime", jeeUserDetails.getSysUser().getLastLoginTime());

        AgentInfo agentInfo = agentInfoService.getById(jeeUserDetails.getSysUser().getUserNo());
        List<String> agentNoList = new ArrayList<>();
        Optional.ofNullable(agentInfo.getPidArr()).ifPresent(item -> {
            agentNoList.addAll(Arrays.asList(item.split(",")));
        });

        agentNoList.add(agentInfo.getAgentNo());

        List<AgentConfig> list = agentConfigService.lambdaQuery()
                .in(AgentConfig::getAgentNo, agentNoList)
                .eq(AgentConfig::getGroupKey, "agentSiteConfig")
                .eq(AgentConfig::getConfigKey, "agentSiteUrl")
                .list();

        if (!list.isEmpty()) {
            tokenRes.put("oemSiteUrl", list.get(0).getConfigVal());
        }

        return ApiRes.ok(tokenRes);
    }

    /**
     * 认证（app扫码登录方式）
     */
    public JSONObject authByAppQrcode(Long sysUserId, String loginType) {

        SysUserEntity sysUser = sysUserService.getById(sysUserId);
        if (sysUser == null || sysUser.getState() != CS.YES) {
            throw new BizException("用户不存在或状态不可用");
        }

        String accessToken = userDetailsProcess(userDetailsService.loadUserByUsername(sysUser.getLoginUsername()), loginType, null, JWTPayload.CREDENTIAL_AUTH_TYPE.QR_CODE);

        // 更新上次登录时间
        sysUserService.updateLastLoginTime(sysUserId);

        JSONObject tokenRes = JsonKit.newJson(CS.ACCESS_TOKEN_NAME, accessToken);
        tokenRes.put("lastLoginTime", sysUser.getLastLoginTime());

        AgentInfo agentInfo = agentInfoService.getById(sysUser.getUserNo());
        List<String> agentNoList = new ArrayList<>();
        Optional.ofNullable(agentInfo.getPidArr()).ifPresent(item -> {
            agentNoList.addAll(Arrays.asList(item.split(",")));
        });

        agentNoList.add(agentInfo.getAgentNo());

        List<AgentConfig> list = agentConfigService.lambdaQuery()
                .in(AgentConfig::getAgentNo, agentNoList)
                .eq(AgentConfig::getGroupKey, "agentSiteConfig")
                .eq(AgentConfig::getConfigKey, "agentSiteUrl")
                .list();

        if (!list.isEmpty()) {
            tokenRes.put("oemSiteUrl", list.get(0).getConfigVal());
        }

        return tokenRes;
    }

    // 通过 sysUserId 登录 (一般在app延期使用)
    public String authBySysUserId(Long sysUserId, String loginType, String existsCacheKey) {
        SysUserEntity sysUser = sysUserService.getById(sysUserId);

        if (sysUser == null || sysUser.getState() != CS.YES) {
            throw new BizException("用户不存在或状态不可用");
        }

        return userDetailsProcess(userDetailsService.loadUserByUsername(sysUser.getLoginUsername()), loginType, existsCacheKey, null);
    }


    /**
     * 根据用户ID 更新缓存中的权限集合， 使得分配实时生效
     **/
    public void refAuthentication(List<Long> sysUserIdList) {

        if (sysUserIdList == null || sysUserIdList.isEmpty()) {
            return;
        }

        Map<Long, SysUserEntity> sysUserMap = new HashMap<>();

        // 查询 sysUserId 和 state
        sysUserService.lambdaQuery()
                .in(SysUserEntity::getSysUserId, sysUserIdList)
                .list()
                .forEach(item -> sysUserMap.put(item.getSysUserId(), item));

        for (Long sysUserId : sysUserIdList) {

            Collection<String> cacheKeyList = RedisUtil.keys(CS.getCacheKeyToken(sysUserId, "*"));
            if (cacheKeyList == null || cacheKeyList.isEmpty()) {
                continue;
            }

            for (String cacheKey : cacheKeyList) {

                //用户不存在 || 已禁用 需要删除Redis
                SysUserEntity sysUser = sysUserMap.get(sysUserId);
                if (sysUser == null || sysUser.getState() == CS.PUB_DISABLE) {
                    RedisUtil.del(cacheKey);
                    continue;
                }

                JeeUserDetails jwtBaseUser = RedisUtil.getObject(cacheKey, JeeUserDetails.class);
                if (jwtBaseUser != null) {
                    // 重新放置sysUser对象
                    jwtBaseUser.setSysUser(baseConverter.toModel(sysUser));

                    //查询放置权限数据
                    jwtBaseUser.setAuthorities(getUserAuthority(jwtBaseUser.getSysUser()));

                    //保存token  失效时间不变
                    RedisUtil.set(cacheKey, jwtBaseUser);
                }
            }
        }

    }

    /**
     * 根据用户ID 删除用户缓存信息
     **/
    public void delAuthentication(List<Long> sysUserIdList) {
        if (sysUserIdList == null || sysUserIdList.isEmpty()) {
            return;
        }
        for (Long sysUserId : sysUserIdList) {
            Collection<String> cacheKeyList = RedisUtil.keys(CS.getCacheKeyToken(sysUserId, "*"));
            if (cacheKeyList == null || cacheKeyList.isEmpty()) {
                continue;
            }
            for (String cacheKey : cacheKeyList) {
                RedisUtil.del(cacheKey);
            }
        }
    }

    public List<SimpleGrantedAuthority> getUserAuthority(SysUser sysUser) {

        //用户拥有的角色集合  需要以ROLE_ 开头,  用户拥有的权限集合
        List<String> roleList = sysRoleService.findListByUser(sysUser.getSysUserId());
        List<String> entList = sysRoleEntRelaService.selectEntIdsByUserId(sysUser.getSysUserId(), sysUser.getUserType(), sysUser.getSysType());

        List<SimpleGrantedAuthority> grantedAuthorities = new LinkedList<>();
        roleList.forEach(role -> grantedAuthorities.add(new SimpleGrantedAuthority(role)));
        entList.forEach(ent -> grantedAuthorities.add(new SimpleGrantedAuthority(ent)));
        return grantedAuthorities;
    }

    /**
     * 获取到userDetail之后的操作
     **/
    public String userDetailsProcess(UserDetails userDetails, String loginType, String existsCacheKey, String authType) {

        JeeUserDetails jeeUserDetails = (JeeUserDetails) userDetails;

        // 登录认证类型
        if (StringUtils.isNotEmpty(authType)) {
            jeeUserDetails.setAuthType(authType);
        }

        // 登录方式
        jeeUserDetails.setLoginType(loginType);

        //验证通过后 再查询用户角色和权限信息集合

        SysUser sysUser = jeeUserDetails.getSysUser();

        // 普通操作员 && 不包含左侧菜单 进行错误提示 （超管 or 扩展员都是通过菜单定义进行查询全部菜单进行过滤，只有普通操作员通过角色功能进行划分）
        if (sysUser.getUserType() == SysUser.UEST_TYPE_NORMAL
                && sysEntitlementMapper.userHasLeftMenu(sysUser.getSysUserId(), CS.SYS_ROLE_TYPE.AGENT) <= 0) {
            throw new BizException("当前用户未分配任何菜单权限，请联系管理员进行分配后再登录！");
        }

        // 查询当前用户的服务商信息
        AgentInfo agentInfo = agentInfoService.getById(sysUser.getBelongInfoId());
        if (agentInfo != null && (agentInfo.getState() == CS.NO)) {
            throw new BizException("当前服务商状态不可用！");
        }
        // 放置权限集合
        jeeUserDetails.setAuthorities(getUserAuthority(sysUser));

        //生成token
        String cacheKey = StringUtils.isNotEmpty(existsCacheKey) ? existsCacheKey : CS.getCacheKeyToken(sysUser.getSysUserId(), IdUtil.fastUUID());

        //生成iToken 并放置到缓存
        ITokenService.processTokenCache(jeeUserDetails, cacheKey); //处理token 缓存信息

        //将信息放置到Spring-security context中
        UsernamePasswordAuthenticationToken authenticationRest = new UsernamePasswordAuthenticationToken(jeeUserDetails, null, jeeUserDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationRest);

        //返回JWTToken
        return JWTUtils.generateToken(new JWTPayload(jeeUserDetails), systemYmlConfig.getJwtSecret());
    }

}
