package com.skr.mdm.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.skr.mdm.authentication.TokenExpireException;
import com.skr.mdm.dao.AuAdminDao;
import com.skr.mdm.dao.AuAdminRoleDao;
import com.skr.mdm.dao.AuPermissionDao;
import com.skr.mdm.dto.LoginAdminDTO;
import com.skr.mdm.dto.TokenDTO;
import com.skr.mdm.entity.AuAdminRoleDO;
import com.skr.mdm.entity.AuAdministratorDO;
import com.skr.mdm.entity.AuPermissionDO;
import com.skr.mdm.enums.ResultCodeEnum;
import com.skr.mdm.service.AdminTokenService;
import com.skr.mdm.util.JwtAdminUtil;
import com.skr.mdm.util.RedisGetKeyUtil;
import com.skr.mdm.util.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Primary
@Service
@Slf4j
public class AdminTokenServiceJWTImpl implements AdminTokenService {


    @Value("${token.expire.seconds}")
    private Long expireSeconds;
    @Value("${token.expire.refreshSeconds}")
    private Long expireRefreshSeconds;
    @Value("${admin.login.only-one}")
    private boolean loginOnlyOne;

    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private AuAdminDao auAdminDao;
    @Autowired
    private AuPermissionDao auPermissionDao;
    @Autowired
    private AuAdminRoleDao adminRoleDao;

    @Override
    public TokenDTO saveToken(LoginAdminDTO loginAdmin) {
        // 判断用户是否重复登录,删除旧的会话
        delLoginAdmin(loginAdmin.getId());

        loginAdmin.setLoginTime(System.currentTimeMillis());
        loginAdmin.setLogonTime(new Date());
        // 用户唯一标识
        String token = IdUtil.simpleUUID();
        loginAdmin.setToken(token);
        loginAdmin.setTokenExpireTime(loginAdmin.getLoginTime() + expireSeconds * 1000);
        // 刷新token
        String refreshToken = IdUtil.simpleUUID();
        loginAdmin.setRefreshToken(refreshToken);
        loginAdmin.setRefreshTokenExpireTime(loginAdmin.getLoginTime() + expireRefreshSeconds * 1000);

        // 存入数据库
        AuAdministratorDO auAdministratorDO = new AuAdministratorDO();
        auAdministratorDO.setId(loginAdmin.getId());
        auAdministratorDO.setToken(token);
        auAdministratorDO.setRefreshToken(refreshToken);
        auAdministratorDO.setLogonTime(new Date());
        auAdminDao.updateById(auAdministratorDO);
        // 存入redis中
        cacheLoginAdmin(loginAdmin);
        String accessToken = JwtAdminUtil.createJWTToken(loginAdmin);
        TokenDTO tokenDTO = new TokenDTO(accessToken, refreshToken, loginAdmin.getLoginTime(), loginAdmin.getTokenExpireTime());
        return tokenDTO;
    }


    /**
     * 刷新token
     */
    @Override
    public TokenDTO refreshToken(String jwt_token, String refresh_token) {

        LoginAdminDTO loginAdminDTO = (LoginAdminDTO) redisUtils.get(RedisGetKeyUtil.getAdminRefreshToken(refresh_token));
        if (loginAdminDTO == null) {
            // 数据库获取
            LoginAdminDTO loginAdminByRefreshToken = getLoginAdminByRefreshToken(refresh_token);
            if (loginAdminByRefreshToken == null) {
                return null;
            }
            delLoginAdmin(loginAdminByRefreshToken.getId());
            return createTokenHandler(loginAdminByRefreshToken, jwt_token, refresh_token);
        } else {
            delLoginAdmin(loginAdminDTO.getId());
            return createTokenHandler(loginAdminDTO, jwt_token, refresh_token);
        }
    }

    @Override
    public LoginAdminDTO getLoginAdmin(String jwtToken) {
        String token = JwtAdminUtil.getUUIDFromJWT(jwtToken);
        if (StringUtils.isBlank(token)) {
            throw new TokenExpireException(ResultCodeEnum.TOKEN_EXPIRE.getCode(), ResultCodeEnum.TOKEN_EXPIRE.getMsg());
        }
        LoginAdminDTO loginAdmin = (LoginAdminDTO) redisUtils.get(RedisGetKeyUtil.getAdminTokenKey(token));
        if (loginAdmin == null) {
            // 数据库获取
            LambdaQueryChainWrapper<AuAdministratorDO> queryChainWrapper = new LambdaQueryChainWrapper<>(auAdminDao);
            AuAdministratorDO administratorDO = queryChainWrapper.eq(AuAdministratorDO::getToken, token).eq(AuAdministratorDO::getDeleted, 0)
                    .eq(AuAdministratorDO::getUsable, 1).one();
            if (administratorDO == null) {
                throw new TokenExpireException(ResultCodeEnum.TOKEN_EXPIRE.getCode(), ResultCodeEnum.TOKEN_EXPIRE.getMsg());
            }
            loginAdmin = new LoginAdminDTO();
            BeanUtils.copyProperties(administratorDO, loginAdmin);
            loginAdmin.setLoginTime(administratorDO.getLogonTime().getTime());
            loginAdmin.setTokenExpireTime(administratorDO.getLogonTime().getTime() + expireSeconds * 1000);
            loginAdmin.setRefreshTokenExpireTime(administratorDO.getLogonTime().getTime() + expireRefreshSeconds * 1000);
            // 权限
            List<AuPermissionDO> listPermissionByAdminId = auPermissionDao.listPermissionByAdminId(loginAdmin.getId());
            if (CollectionUtils.isNotEmpty(listPermissionByAdminId)) {
                Set<String> permissionList = listPermissionByAdminId.stream().map(AuPermissionDO::getPercode).collect(Collectors.toSet());
                loginAdmin.setPermissionList(permissionList);
            }
            // 权限id
            LambdaQueryChainWrapper<AuAdminRoleDO> chainWrapper = new LambdaQueryChainWrapper<>(adminRoleDao);
            AuAdminRoleDO adminRoleDO = chainWrapper.eq(AuAdminRoleDO::getAdminId, loginAdmin.getId()).one();
            if (adminRoleDO != null) {
                loginAdmin.setRoleId(adminRoleDO.getRoleId());
            }
            redisUtils.set(RedisGetKeyUtil.getAdminTokenKey(token), loginAdmin, expireSeconds);
            return loginAdmin;
        }
        return loginAdmin;

    }

    @Override
    public boolean deleteToken(String jwtToken) {
        try {
            String uuid = JwtAdminUtil.getUUIDFromJWT(jwtToken);
            if (uuid != null) {
                String key = RedisGetKeyUtil.getAdminTokenKey(uuid);
                LoginAdminDTO loginAdminDTO = (LoginAdminDTO) redisUtils.get(key);
                if (loginAdminDTO != null) {
                    redisUtils.del(key);
                    return true;
                }
            }
        } catch (TokenExpireException e) {
//            log.warn("管理员jwt已过期");
        }
        return false;
    }

    @Override
    public LoginAdminDTO getLoginAdminByRefreshToken(String refreshToken) {
        AuAdministratorDO auAdministratorDO = new LambdaQueryChainWrapper<>(auAdminDao)
                .eq(AuAdministratorDO::getRefreshToken, refreshToken)
                .eq(AuAdministratorDO::getDeleted, 0)
                .eq(AuAdministratorDO::getUsable, 1)
                .one();
        if (auAdministratorDO == null) {
            return null;
        }
        long expireTime = auAdministratorDO.getLogonTime().getTime() + expireRefreshSeconds * 1000;
        if (expireTime > System.currentTimeMillis()) {
            LoginAdminDTO loginAdminDTO = new LoginAdminDTO();
            BeanUtils.copyProperties(auAdministratorDO, loginAdminDTO);
            loginAdminDTO.setLoginTime(auAdministratorDO.getLogonTime().getTime());
            loginAdminDTO.setRefreshToken(refreshToken);
            loginAdminDTO.setRefreshTokenExpireTime(expireTime);
            return loginAdminDTO;
        }
        return null;
    }

    @Override
    public void signOut(String jwt) throws TokenExpireException {
        if (StringUtils.isBlank(jwt)) {
            return;
        }
        String token = JwtAdminUtil.getUUIDFromJWT(jwt);
        if (redisUtils.get(RedisGetKeyUtil.getAdminTokenKey(token)) == null) {
            return;
        }
        LoginAdminDTO loginAdminDTO = (LoginAdminDTO) redisUtils.get(RedisGetKeyUtil.getAdminTokenKey(token));
        redisUtils.del(RedisGetKeyUtil.getAdminTokenKey(token));
        redisUtils.del(RedisGetKeyUtil.getAdminRefreshToken(loginAdminDTO.getRefreshToken()));
        if (loginOnlyOne) {
            redisUtils.del(RedisGetKeyUtil.getAdminLoginIdKey(loginAdminDTO.getId().toString()));
        }
        // 清除数据库token和refresh_token
        LambdaUpdateChainWrapper<AuAdministratorDO> updateChainWrapper = new LambdaUpdateChainWrapper<>(auAdminDao);
        updateChainWrapper.set(AuAdministratorDO::getToken, null).set(AuAdministratorDO::getRefreshToken, null)
                .eq(AuAdministratorDO::getToken, token)
                .update();
    }


    private TokenDTO createTokenHandler(LoginAdminDTO loginAdminDTO, String jwt_token, String refresh_token) {
        // 生成新的token和refresh_token（刷新token过期时间不变）
        String token = IdUtil.simpleUUID();
        loginAdminDTO.setToken(token);
        loginAdminDTO.setTokenExpireTime(System.currentTimeMillis() + expireSeconds * 1000);
        String refreshToken = IdUtil.simpleUUID();
        loginAdminDTO.setRefreshToken(refreshToken);

        AuAdministratorDO auAdministratorDO = new AuAdministratorDO();
        auAdministratorDO.setId(loginAdminDTO.getId());
        auAdministratorDO.setToken(token);
        auAdministratorDO.setRefreshToken(refreshToken);
        auAdminDao.updateById(auAdministratorDO);


        redisUtils.set(RedisGetKeyUtil.getAdminTokenKey(token), loginAdminDTO, expireSeconds);
        long refreshTokenExp = (loginAdminDTO.getRefreshTokenExpireTime() - System.currentTimeMillis()) / 1000;
        redisUtils.set(RedisGetKeyUtil.getAdminRefreshToken(refreshToken), loginAdminDTO, refreshTokenExp);
        if (loginOnlyOne) {
            redisUtils.set(RedisGetKeyUtil.getAdminLoginIdKey(loginAdminDTO.getId().toString()), loginAdminDTO, refreshTokenExp);
        }
        // 清除缓存
        deleteToken(jwt_token);
        redisUtils.del(RedisGetKeyUtil.getAdminRefreshToken(refresh_token));

        String jwtToken = JwtAdminUtil.createJWTToken(loginAdminDTO);
        return TokenDTO.builder()
                .access_token(jwtToken)
                .refresh_token(refreshToken)
                .loginTime(loginAdminDTO.getLoginTime())
                .expirationTime(loginAdminDTO.getTokenExpireTime())
                .build();
    }


    private void cacheLoginAdmin(LoginAdminDTO loginAdminDTO) {
        // 访问token
        redisUtils.set(RedisGetKeyUtil.getAdminTokenKey(loginAdminDTO.getToken()), loginAdminDTO, expireSeconds);
        // 刷新token
        redisUtils.set(RedisGetKeyUtil.getAdminRefreshToken(loginAdminDTO.getRefreshToken()), loginAdminDTO, expireRefreshSeconds);
        if (loginOnlyOne) {
            // 登录会话
            redisUtils.set(RedisGetKeyUtil.getAdminLoginIdKey(loginAdminDTO.getId().toString()), loginAdminDTO, expireRefreshSeconds);
        }
    }

    /**
     * 清除管理员相关缓存
     *
     * @param adminId
     */
    private void delLoginAdmin(Integer adminId) {
        if (loginOnlyOne) {
            if (redisUtils.get(RedisGetKeyUtil.getAdminLoginIdKey(adminId.toString())) != null) {
                LoginAdminDTO loginAdminDTO = (LoginAdminDTO) redisUtils.get(RedisGetKeyUtil.getAdminLoginIdKey(adminId.toString()));
                if (loginAdminDTO != null) {
                    redisUtils.del(RedisGetKeyUtil.getAdminTokenKey(loginAdminDTO.getToken()));
                    redisUtils.del(RedisGetKeyUtil.getAdminRefreshToken(loginAdminDTO.getRefreshToken()));
                }
                redisUtils.del(RedisGetKeyUtil.getAdminLoginIdKey(adminId.toString()));
            }
        }
    }


}
