package com.ho.user.controller;

import cn.hutool.core.util.IdUtil;
import com.ho.common.tools.annotation.TokenIgnore;
import com.ho.common.tools.constant.CommonConstant;
import com.ho.common.tools.constant.ContextConstant;
import com.ho.common.tools.constant.RedisKeyConstant;
import com.ho.common.tools.entity.SimpleUser;
import com.ho.common.tools.entity.UserDetailRespVO;
import com.ho.common.tools.exception.BaseResponseCode;
import com.ho.common.tools.exception.BusinessException;
import com.ho.common.tools.exception.DataResult;
import com.ho.common.tools.service.RedisService;
import com.ho.user.api.entity.SysDept;
import com.ho.user.api.entity.SysUser;
import com.ho.user.api.vo.req.LoginFromLargeScreenVO;
import com.ho.user.api.vo.req.UserReq;
import com.ho.user.service.DeptService;
import com.ho.user.service.EncryptComponent;
import com.ho.user.service.UserService;
import com.ho.user.vo.req.LoginReqVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
 * @author fancl
 * @desc: token获取或重新登陆
 * @date 2023/1/11
 */
@RestController
@Api(tags = "大屏和云平台登陆")
@RequestMapping(ContextConstant.SYS + "token")
@Slf4j
public class TokenController {

    @Autowired
    UserService userService;

    @Autowired
    DeptService deptService;

    @Autowired
    RedisService redisService;

    @Autowired
    EncryptComponent encryptComponent;

    @Value("${spring.profiles.active}")
    String env;

    @GetMapping("checkPlatToken")
    @ApiOperation(value = "验证平台token是否有效")
    @TokenIgnore
    public DataResult<Boolean> checkPlatToken(HttpServletRequest request) {
        String token = request.getHeader(RedisKeyConstant.User.ACCESS_TOKEN);
        String tokenKey = RedisKeyConstant.User.TOKEN + token;
        if (redisService.hasKey(tokenKey)) {
            return DataResult.success(true);
        } else {
            return DataResult.success(false);
        }
    }

    @PostMapping("applyLargeScreenToken")
    @ApiOperation(value = "申请大屏token")
    public DataResult<UserDetailRespVO> getLargeScreenToken(HttpServletRequest request) {
        //能进到这个方法的肯定已经登陆了
        String token = request.getHeader(CommonConstant.ACCESS_TOKEN);
        SimpleUser simpleUser = redisService.getSimpleUserByToken(token);
        UserDetailRespVO userDetail = redisService.getUserDetailByToken(token);
        //如何判断是重复登陆?因为 largeScreenToken 不过期,所以如果这个largeScreenToken存在,就直接返回,而不再重新生成
        String bigUserNameKey = redisService.getUserNameKeyForBigToken(userDetail.getUsername());
        if (redisService.hasKey(bigUserNameKey)) {
            //
            log.info("已经存在 大屏token,将直接返回");
            SimpleUser simpleUserByBigScreenToken = (SimpleUser) redisService.get(bigUserNameKey);
            //SimpleUser simpleUserByBigScreenToken = redisService.getSimpleUserByBigScreenToken(bigUserNameKey);
            String bigToken = simpleUserByBigScreenToken.getToken();
            UserDetailRespVO userDetailByLargeScreenToken = redisService.getUserDetailByLargeScreenToken(bigToken);
            log.info("得到缓存中的 UserDetailRespVO 并返回 :" + userDetailByLargeScreenToken);
            return DataResult.success(userDetailByLargeScreenToken);
        }
        //不存在,重新生成大屏token
        String bigToken = IdUtil.simpleUUID();
        //大屏登陆返回对象,这个对象的元素直接从缓存中拿
        UserDetailRespVO userDetailBig = new UserDetailRespVO();
        userDetailBig.setUserId(userDetail.getUserId());
        userDetailBig.setUsername(userDetail.getUsername());
        userDetailBig.setAccessToken(bigToken);
        userDetailBig.setGroupId(userDetail.getGroupId());
        //因为部门信息有可能已经变更,所以不再保存部门
        //userDetailBig.setDeptId(userDetail.getDeptId());
        //userDetailBig.setDeptName(userDetail.getDeptName());
        //生成SimpleUser对象
        SimpleUser simpleUserBig = new SimpleUser();
        simpleUserBig.setGroupId(simpleUser.getGroupId());
        simpleUserBig.setUserId(simpleUser.getUserId());
        simpleUserBig.setToken(bigToken);
        //生成2对大屏token对象, 这两个缓存也设置为2小时过期,因为有接口刷新延续机制
        //1.token对应的 UserDetailRespVO 对象
        String tokenKey = RedisKeyConstant.User.TOKEN_LARGE_SCREEN + bigToken;
        redisService.set(tokenKey, userDetailBig, 365, TimeUnit.DAYS);
        //2.userName 对应的SimpleUser对象
        String userNameKey = RedisKeyConstant.User.USER_NAME_LARGE_SCREEN + "pc:" + userDetail.getUsername();
        redisService.set(userNameKey, simpleUserBig, 365, TimeUnit.DAYS);

        return DataResult.success(userDetailBig);
    }

    @PostMapping("applyLargeScreenTokenUserId")
    @ApiOperation(value = "申请大屏token-使用用户id")
    @TokenIgnore
    public DataResult<UserDetailRespVO> applyLargeScreenTokenUserId(@RequestBody UserReq userReq) {
        //使用用户id查询用户信息

        log.info("userId:" + userReq.getUserId());
        SysUser sysUser = userService.selectById(userReq.getUserId());
        if (sysUser == null) {
            throw new BusinessException(BaseResponseCode.NOT_ACCOUNT);
        }
        log.info("userName:" + sysUser.getUsername());
        //如何判断是重复登陆?因为 largeScreenToken 不过期,所以如果这个largeScreenToken存在,就直接返回,而不再重新生成
        String bigUserNameKey = redisService.getUserNameKeyForBigToken(sysUser.getUsername());
        if (redisService.hasKey(bigUserNameKey)) {
            //
            log.info("已经存在 大屏token,将直接返回");
            SimpleUser simpleUserByBigScreenToken = (SimpleUser) redisService.get(bigUserNameKey);
            //SimpleUser simpleUserByBigScreenToken = redisService.getSimpleUserByBigScreenToken(bigUserNameKey);
            String bigToken = simpleUserByBigScreenToken.getToken();
            UserDetailRespVO userDetailByLargeScreenToken = redisService.getUserDetailByLargeScreenToken(bigToken);
            log.info("得到缓存中的 UserDetailRespVO 并返回 :" + userDetailByLargeScreenToken);
            return DataResult.success(userDetailByLargeScreenToken);
        }
        //不存在,重新生成大屏token
        String bigToken = IdUtil.simpleUUID();
        //大屏登陆返回对象,这个对象的元素直接从缓存中拿
        UserDetailRespVO userDetailBig = new UserDetailRespVO();
        userDetailBig.setUserId(sysUser.getId());
        userDetailBig.setUsername(sysUser.getUsername());
        userDetailBig.setAccessToken(bigToken);
        userDetailBig.setGroupId(sysUser.getGroupId());
        //因为部门信息有可能已经变更,所以不再保存部门
        //userDetailBig.setDeptId(userDetail.getDeptId());
        //userDetailBig.setDeptName(userDetail.getDeptName());
        //生成SimpleUser对象
        SimpleUser simpleUserBig = new SimpleUser();
        simpleUserBig.setGroupId(sysUser.getGroupId());
        simpleUserBig.setUserId(sysUser.getId());
        simpleUserBig.setToken(bigToken);
        //生成2对大屏token对象, 这两个缓存也设置为2小时过期,因为有接口刷新延续机制
        //1.token对应的 UserDetailRespVO 对象
        String tokenKey = RedisKeyConstant.User.TOKEN_LARGE_SCREEN + bigToken;
        redisService.set(tokenKey, userDetailBig, 365, TimeUnit.DAYS);
        //2.userName 对应的SimpleUser对象
        String userNameKey = RedisKeyConstant.User.USER_NAME_LARGE_SCREEN + "pc:" + sysUser.getUsername();
        redisService.set(userNameKey, simpleUserBig, 365, TimeUnit.DAYS);

        return DataResult.success(userDetailBig);
    }


    @PostMapping("loginToCloud")
    //这个方法携带的是 大屏token,所以shiro和 Aspect都不要拦截
    @TokenIgnore
    @ApiOperation(value = "使用大屏token登陆到云平台")
    public DataResult<UserDetailRespVO> loginToCloud(@RequestBody LoginFromLargeScreenVO largeScreenVO) {
        /**
         * 登陆过程就是一个完整的登陆验证并颁发token的过程,只是这个登陆是使用大屏token,而不是用户名密码,需要如下两步
         * 登陆后用户原有token会被冲掉
         * 1.这个过程需要缓存中一定存在这个大屏token
         * 2.如果存在,使用这个里面的用户名判断用户状态后直接颁发 云平台token 套件
         */
        String bigToken = largeScreenVO.getBigToken();
        String username = "";
        //1.验证缓存中的大屏token
        String userDetailKey = redisService.getUserDetailKeyForBigToken(bigToken);
        if (redisService.hasKey(userDetailKey)) {
            UserDetailRespVO userDetailBigScreen = (UserDetailRespVO) redisService.get(userDetailKey);
            username = userDetailBigScreen.getUsername();
            //

        } else {
            //不存在就报错
            throw new BusinessException(BaseResponseCode.INVALID_BIG_SCREEN_TOKEN);
        }
        //2.验证 username 登陆 并颁发云平台token
        //查询用户
        SysUser user = userService.getUserByName(username);
        //用户不存在
        if (user == null) {
            throw new BusinessException(BaseResponseCode.NOT_ACCOUNT);
        }
        //状态0 (锁定状态)
        if (CommonConstant.USER.STATUS_LOCKING.equals(user.getStatus())) {
            throw new BusinessException(BaseResponseCode.USER_LOCK);
        }
        //判断部门是否被禁用
        SysDept dept = deptService.detailInfo(user.getDeptId());
        if (!CommonConstant.STATUS_FLAG.equals(dept.getStatus())) {
            throw new BusinessException(BaseResponseCode.TO_BELONGING_DEPT_DISABLE);
        }
        //把aesKey 和iv passwd 解密,此时还是加密的
        LoginReqVO loginReqTemp = new LoginReqVO();
        loginReqTemp.setAesKey(largeScreenVO.getAesKey());
        loginReqTemp.setIv(largeScreenVO.getIv());

        //执行解密,使用私钥对aesKey和iv分别进行解密,然后AES解密
        //本地环境不加密
        if (!CommonConstant.Env.DEV.equals(env)) {
            loginReqTemp = encryptComponent.decryptSecret(loginReqTemp);

        }
        //使用用户名查询用户并颁发token
        //获取用户信息
        UserDetailRespVO userDetail = userService.getUserDetail(user.getId(), RedisKeyConstant.User.LOGIN_CHANNEL_PC);
        //赋值 登陆渠道 登陆方式
        //因为是大屏过来的,所以这两个值 赋值为 pc 和 account
        userDetail.setLoginChannel(RedisKeyConstant.User.LOGIN_CHANNEL_PC);
        userDetail.setLoginType(RedisKeyConstant.User.LOGIN_TYPE_ACCOUNT);
        String token = IdUtil.simpleUUID();
        userDetail.setAccessToken(token);
        log.info("user:{} ,token:{}", user.getUsername(), token);
        //生成SimpleUser对象
        SimpleUser simpleUser = userService.generateSimpleUser(user, userDetail.getDeptId(), token, loginReqTemp);
        log.info("simpleUser:" + simpleUser);
        //清除用户用户原有缓存
        //清除用户登陆缓存
        userService.removeUserCache(user.getUsername(), RedisKeyConstant.User.LOGIN_CHANNEL_PC);
        //设置缓存键值对 这两个缓存时间2小时 这个登陆方式固定为pc
        redisService.set(RedisKeyConstant.User.TOKEN + token, userDetail, 2, TimeUnit.HOURS);
        redisService.set(RedisKeyConstant.User.USER_NAME + RedisKeyConstant.User.LOGIN_CHANNEL_PC + ":" + user.getUsername(), simpleUser, 2, TimeUnit.HOURS);


        return DataResult.success(userDetail);

    }


    @PostMapping("applyCommonLargeScreenToken")
    @ApiOperation(value = "申请通用大屏token")
    public DataResult<UserDetailRespVO> applyCommonLargeScreenToken(HttpServletRequest request) {
        //能进到这个方法的肯定已经登陆了
        String token = request.getHeader(CommonConstant.ACCESS_TOKEN);
        String tokenCommonLargeScreen = RedisKeyConstant.User.TOKEN_COMMON_LARGE_SCREEN+token;
        if (redisService.hasKey(tokenCommonLargeScreen)) {
            SimpleUser simpleUserByBigScreenToken = (SimpleUser) redisService.get(tokenCommonLargeScreen);
            simpleUserByBigScreenToken.setToken(token);
            redisService.set(tokenCommonLargeScreen, simpleUserByBigScreenToken, 7, TimeUnit.DAYS);
            log.info("已经存在 大屏token,将直接返回,simpleUserByBigScreenToken:{}", simpleUserByBigScreenToken);
            return DataResult.success(simpleUserByBigScreenToken);
        }
        SimpleUser simpleUser = redisService.getSimpleUserByToken(token);
        simpleUser.setToken(token);
        //不存在,重新生成大屏token
        redisService.set(tokenCommonLargeScreen, simpleUser, 7, TimeUnit.DAYS);
        return DataResult.success(simpleUser);
    }

}
