package com.lideeyunji.service.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.annotations.VisibleForTesting;
import com.lideeyunji.service.system.api.IApiSmsCodeApi;
import com.lideeyunji.service.system.config.convert.auth.AuthConvert;
import com.lideeyunji.service.system.constant.OAuth2ClientConstants;
import com.lideeyunji.service.system.controller.vo.auth.*;
import com.lideeyunji.service.system.controller.vo.auth.*;
import com.lideeyunji.service.system.dto.LoginLogCreateReqDTO;
import com.lideeyunji.service.system.dto.SocialUserBindReqDTO;
import com.lideeyunji.service.system.dto.SocialUserRespDTO;
import com.lideeyunji.service.system.entity.AdminUserDO;
import com.lideeyunji.service.system.entity.OAuth2AccessTokenDO;
import com.lideeyunji.service.system.entity.RoleDO;
import com.lideeyunji.service.system.entity.UserRoleDO;
import com.lideeyunji.service.system.enums.LoginLogTypeEnum;
import com.lideeyunji.service.system.enums.LoginResultEnum;
import com.lideeyunji.service.system.enums.SmsSceneEnum;
import com.lideeyunji.service.system.service.*;
import com.lideeyunji.tool.framework.common.enums.CommonStatusEnum;
import com.lideeyunji.tool.framework.common.enums.UserTypeEnum;
import com.lideeyunji.tool.framework.common.util.collection.CollectionUtils;
import com.lideeyunji.tool.framework.common.util.monitor.TracerUtils;
import com.lideeyunji.tool.framework.common.util.servlet.ServletUtils;
import com.lideeyunji.tool.framework.common.util.string.StrUtils;
import com.lideeyunji.tool.framework.common.util.validation.ValidationUtils;
import com.lideeyunji.tool.framework.tenant.core.aop.TenantIgnore;
import com.lideeyunji.service.system.service.*;
import com.xingyuv.captcha.model.common.ResponseModel;
import com.xingyuv.captcha.model.vo.CaptchaVO;
import com.xingyuv.captcha.service.CaptchaService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

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

import static com.lideeyunji.service.system.constant.ErrorCodeConstants.*;
import static com.lideeyunji.tool.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.lideeyunji.tool.framework.common.util.servlet.ServletUtils.getClientIP;
import static java.util.Collections.singleton;

/**
 * Auth Service 实现类
 *
 * @author 金灯剑客
 */
@Service
@Slf4j
public class AdminAuthServiceImpl implements IAdminAuthService {

    @Resource
    private IAdminUserService userService;
    @Resource
    private ILoginLogService loginLogService;
    @Resource
    private IOAuth2TokenService oauth2TokenService;
    @Resource
    private ISocialUserService socialUserService;

    @Resource
    private Validator validator;
    @Resource
    private CaptchaService captchaService;
    @Resource
    private IApiSmsCodeApi apiSmsCodeApi;

    @Resource
    private IPermissionService permissionService;

    @Resource
    private IRoleService roleService;
    /**
     * 验证码的开关，默认为 true
     */
    @Value("${lideeyunji.captcha.enable:true}")
    private Boolean captchaEnable;

    //处理角色
    private void handleDeptIdAndRoleId(AdminUserDO user) {
        boolean supAdminFlag = userService.isSupAdmin(user.getId());//是否是超级管理员
        boolean tenantAdminFlag = userService.isTenantAdmin(user.getId());//是否是租户管理员

        //处理上一次登录部门和角色
        Long loginDeptId = user.getLoginDeptId();
        Long loginRoleId = user.getLoginRoleId();
        if (supAdminFlag) {//超级管理员
            user.setLoginDeptId(loginDeptId);
            user.setLoginRoleId(loginRoleId);
            return;
        }

        if (tenantAdminFlag) {//租户管理员
            user.setLoginDeptId(loginDeptId);
            user.setLoginRoleId(loginRoleId);
            return;
        }

        String deptIdsStr = user.getDeptId();
        if (StrUtil.isEmpty(deptIdsStr)) {
            throw exception(AUTH_LOGIN_USER_DEPT_NULL);
        }
        Set<Long> deptIds = StrUtils.str2setLong(deptIdsStr);
        if (loginDeptId == null || !deptIds.contains(loginDeptId)) {//说明上一次没有选择部门，则默认选择一个，或者已经不在这个部门了
            loginDeptId = deptIds.iterator().next();
            List<UserRoleDO> userRoleList = userService.getUserRoleList(user.getId(), loginDeptId);
            if (CollectionUtil.isNotEmpty(userRoleList)) {//有权限
                loginRoleId = userRoleList.get(0).getRoleId();
            }
        } else {//有登录过
            List<UserRoleDO> userRoleList = userService.getUserRoleList(user.getId(), loginDeptId);
            if(CollectionUtil.isNotEmpty(userRoleList)){
                //判断这个角色是否还在角色列表里面
                Map<Long, UserRoleDO> userRoleMap = CollectionUtils.convertMap(userRoleList, UserRoleDO::getRoleId);

                if(loginRoleId==null){
                    loginRoleId = userRoleList.get(0).getRoleId();
                }else if (loginRoleId != -1 && !userRoleMap.containsKey(loginRoleId)) {//不存在，则重新赋值 -1=该部门下的所有角色
                    loginRoleId = userRoleList.get(0).getRoleId();
                }
            }


        }


        user.setLoginDeptId(loginDeptId);
        user.setLoginRoleId(loginRoleId);
    }

    @Override
    public AdminUserDO authenticate(String username, String password) {
        final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;


        // 校验账号是否存在
        AdminUserDO user = userService.getUserByUsername(username);
        if (user == null) {
            createLoginLog(null, null, null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
        }
        if (!userService.isPasswordMatch(password, user.getPassword())) {
            createLoginLog(user.getId(), user.getLoginDeptId(), user.getLoginRoleId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
        }
        // 校验是否禁用
        if (CommonStatusEnum.isDisable(user.getStatus())) {
            createLoginLog(user.getId(), user.getLoginDeptId(), user.getLoginRoleId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED);
            throw exception(AUTH_LOGIN_USER_DISABLED);
        }
        return user;
    }

    @Override
    public AdminUserDO authenticateSwitch(Long userId, Long loginDeptId, Long loginRoleId) {
        final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_SWITCH;

        // 校验账号是否存在
        AdminUserDO user = userService.getUser(userId);
        if (user == null) {
            createLoginLog(null, null, null, null, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
        }
        // 校验是否禁用
        if (CommonStatusEnum.isDisable(user.getStatus())) {
            createLoginLog(user.getId(), user.getLoginDeptId(), user.getLoginRoleId(), user.getUsername(), logTypeEnum, LoginResultEnum.USER_DISABLED);
            throw exception(AUTH_LOGIN_USER_DISABLED);
        }
        user.setLoginDeptId(loginDeptId);
        user.setLoginRoleId(loginRoleId);
        return user;
    }


    @Override
    public AuthLoginRespVO login(AuthLoginReqVO reqVO) {
        // 校验验证码
        validateCaptcha(reqVO);

        // 使用账号密码，进行登录
        AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword());
        this.handleDeptIdAndRoleId(user);//处理部门和角色
        // 如果 socialType 非空，说明需要绑定社交用户
        if (reqVO.getSocialType() != null) {
            socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
                    reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
        }
        //清空缓存
        permissionService.clearCache(user.getId());

        // 创建 Token 令牌，记录登录日志
        return createTokenAfterLoginSuccess(user.getId(), user.getLoginDeptId(), user.getLoginRoleId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
    }

    //切换身份登录
    @Override
    public AuthLoginRespVO switchLogin(Long userId, Long loginDeptId, Long loginRoleId) {
        // 进行切换登录
        AdminUserDO user = authenticateSwitch(userId, loginDeptId, loginRoleId);

        //清空缓存
        permissionService.clearCache(user.getId());

        // 创建 Token 令牌，记录登录日志
        return createTokenAfterLoginSuccess(user.getId(), user.getLoginDeptId(), user.getLoginRoleId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SWITCH);
    }

    @TenantIgnore
    @Override
    public AuthLoginRespVO supAdminSwitchTenantLogin(Long tenantId, boolean supAdminFlag) {
        List<AdminUserDO> userList =new ArrayList<>();
        if(tenantId==1){
            AdminUserDO adminUserDO = userService.getUser(1L);
            userList.add(adminUserDO);
        }else{
            RoleDO role = roleService.getTenantAdminRole(tenantId);//获取该租户的租户管理员
            //获取该角色下的所有人员
            Set<Long> userSet = permissionService.getUserIdByRoleId(singleton(role.getId()));
            if (CollectionUtil.isEmpty(userSet)) {
                throw exception(AUTH_SWITCH_LOGIN_USER_ISEXIT_TENANTADMIN);
            }
            userList = userService.getUserList(userSet);
            if (CollectionUtil.isEmpty(userList)) {
                throw exception(AUTH_SWITCH_LOGIN_USER_ISEXIT_TENANTADMIN);
            }
        }


        AdminUserDO tenantAdmin = null;
        //获取是否有admin用户,有admin用户优先获取
        for (AdminUserDO adminUserDO : userList) {
            if (tenantAdmin == null) {
                tenantAdmin = adminUserDO;
            }
            String username = adminUserDO.getUsername();
            if (username.toUpperCase().equals("ADMIN")) {
                tenantAdmin = adminUserDO;
                break;
            }
        }

        //清空缓存
        permissionService.clearCache(tenantAdmin.getId());

        // 创建 Token 令牌，记录登录日志
        return createTokenAfterLoginSuccess(tenantAdmin.getId(), tenantAdmin.getLoginDeptId(), tenantAdmin.getLoginRoleId(), tenantAdmin.getUsername(), LoginLogTypeEnum.LOGIN_SWITCH,supAdminFlag);
    }


    @Override
    public void sendSmsCode(AuthSmsSendReqVO reqVO) {
        // 登录场景，验证是否存在
        if (userService.getUserByMobile(reqVO.getMobile()) == null) {
            throw exception(AUTH_MOBILE_NOT_EXISTS);
        }
        // 发送验证码
        apiSmsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
    }

    @Override
    public AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) {
        // 校验验证码
        apiSmsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), getClientIP()));

        // 获得用户信息
        AdminUserDO user = userService.getUserByMobile(reqVO.getMobile());
        if (user == null) {
            throw exception(USER_NOT_EXISTS);
        }
        this.handleDeptIdAndRoleId(user);//处理部门和角色
        // 创建 Token 令牌，记录登录日志
        return createTokenAfterLoginSuccess(user.getId(), user.getLoginDeptId(), user.getLoginRoleId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
    }

    private void createLoginLog(Long userId, Long lastLoginDeptId, Long lastLoginRoleId, String username,
                                LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {
        // 插入登录日志
        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
        reqDTO.setLogType(logTypeEnum.getType());
        reqDTO.setTraceId(TracerUtils.getTraceId());
        reqDTO.setUserId(userId);
        reqDTO.setUserType(getUserType().getValue());
        reqDTO.setUsername(username);
        reqDTO.setUserAgent(ServletUtils.getUserAgent());
        reqDTO.setUserIp(ServletUtils.getClientIP());
        reqDTO.setResult(loginResult.getResult());
        reqDTO.setLoginDeptId(lastLoginDeptId);
        reqDTO.setLoginRoleId(lastLoginRoleId);
        loginLogService.createLoginLog(reqDTO);
        // 更新最后登录时间
        if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
            userService.updateUserLogin(userId, ServletUtils.getClientIP(), lastLoginDeptId, lastLoginRoleId);
        }
    }

    @Override
    public AuthLoginRespVO socialLogin(AuthSocialLoginReqVO reqVO) {
        // 使用 code 授权码，进行登录。然后，获得到绑定的用户编号
        SocialUserRespDTO socialUser = socialUserService.getSocialUserByCode(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
                reqVO.getCode(), reqVO.getState());
        if (socialUser == null || socialUser.getUserId() == null) {
            throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
        }

        // 获得用户
        AdminUserDO user = userService.getUser(socialUser.getUserId());
        if (user == null) {
            throw exception(USER_NOT_EXISTS);
        }
        this.handleDeptIdAndRoleId(user);//处理部门和角色
        // 创建 Token 令牌，记录登录日志
        return createTokenAfterLoginSuccess(user.getId(), user.getLoginDeptId(), user.getLoginRoleId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL);
    }

    @VisibleForTesting
    void validateCaptcha(AuthLoginReqVO reqVO) {
        if (true) {
            return;
        }
        // 如果验证码关闭，则不进行校验
        if (true) {//临时关闭
            return;
        }
        if (!captchaEnable) {
            return;
        }
        // 校验验证码
        ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class);
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification());
        ResponseModel response = captchaService.verification(captchaVO);
        // 验证不通过
        if (!response.isSuccess()) {
            // 创建登录失败日志（验证码不正确)
            createLoginLog(null, null, null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR);
            throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg());
        }
    }

    private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, Long lastLoginDeptId, Long lastLoginRoleId, String username, LoginLogTypeEnum logType) {
        return createTokenAfterLoginSuccess(userId, lastLoginDeptId, lastLoginRoleId, username, logType, false);
    }

    private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, Long lastLoginDeptId, Long lastLoginRoleId, String username, LoginLogTypeEnum logType, boolean supAdminFlag) {
        // 插入登陆日志
        createLoginLog(userId, lastLoginDeptId, lastLoginRoleId, username, logType, LoginResultEnum.SUCCESS);
        // 创建访问令牌
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, lastLoginDeptId, lastLoginRoleId, getUserType().getValue(),
                OAuth2ClientConstants.CLIENT_ID_DEFAULT, null, supAdminFlag);
        // 构建返回结果
        return AuthConvert.INSTANCE.convert(accessTokenDO);
    }


    @Override
    public AuthLoginRespVO refreshToken(String refreshToken) {
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);
        return AuthConvert.INSTANCE.convert(accessTokenDO);
    }

    @Override
    public void logout(String token, Integer logType) {
        // 删除访问令牌
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token);
        if (accessTokenDO == null) {
            return;
        }
        // 删除成功，则记录登出日志
        createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType);
    }

    private void createLogoutLog(Long userId, Integer userType, Integer logType) {
        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
        reqDTO.setLogType(logType);
        reqDTO.setTraceId(TracerUtils.getTraceId());
        reqDTO.setUserId(userId);
        reqDTO.setUserType(userType);

        reqDTO.setUsername(getUsername(userId));

        reqDTO.setUserAgent(ServletUtils.getUserAgent());
        reqDTO.setUserIp(ServletUtils.getClientIP());
        reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
        loginLogService.createLoginLog(reqDTO);
    }

    private String getUsername(Long userId) {
        if (userId == null) {
            return null;
        }
        AdminUserDO user = userService.getUser(userId);
        return user != null ? user.getUsername() : null;
    }

    private UserTypeEnum getUserType() {
        return UserTypeEnum.ADMIN;
    }

}
