package com.cf.imes.module.system.service.oauth2;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.date.DateUtils;
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
import com.cf.imes.framework.security.core.service.SecurityFrameworkService;
import com.cf.imes.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
import com.cf.imes.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO;
import com.cf.imes.module.system.dal.mysql.oauth2.OAuth2AccessTokenMapper;
import com.cf.imes.module.system.dal.mysql.oauth2.OAuth2RefreshTokenMapper;
import com.cf.imes.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;

import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception0;
import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertSet;

/**
 * OAuth2.0 Token Service 实现类
 *
 * @author 晨丰科技
 */
@Service
public class OAuth2TokenServiceImpl implements OAuth2TokenService {

    @Resource
    private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
    @Resource
    private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;

    @Resource
    private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO;

    @Resource
    private OAuth2ClientService oauth2ClientService;

    @Resource
    private SecurityFrameworkService securityFrameworkService;

    @Override
    @Transactional
    public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List<String> scopes,Boolean large, Integer dbNo, Integer tableNo, String dataCode, Long organId, String nickname) {
        OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
        // 填入访问令牌包含的字段
        OAuth2AccessTokenDO refreshTokenDO = createOAuth2ReToken(userId, userType, clientDO, scopes, large, dbNo, tableNo, dataCode, organId, nickname);

        // 创建访问令牌
        return createOAuth2AccessToken(refreshTokenDO, clientDO);
    }

    @Override
    public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId) {
        // 查询访问令牌
        OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken);
        if (refreshTokenDO == null) {
            throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "无效的刷新令牌");
        }

        // 校验 Client 匹配
        OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);
        if (ObjectUtil.notEqual(clientId, refreshTokenDO.getClientId())) {
            throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "刷新令牌的客户端编号不正确");
        }

        // 移除相关的访问令牌
        List<OAuth2AccessTokenDO> accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshToken);
        if (CollUtil.isNotEmpty(accessTokenDOs)) {
            oauth2AccessTokenMapper.deleteBatchIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId));
            oauth2AccessTokenRedisDAO.deleteList(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getAccessToken));
        }

        // 已过期的情况下，删除刷新令牌
        if (DateUtils.isExpired(refreshTokenDO.getExpiresTime())) {
            oauth2RefreshTokenMapper.deleteById(refreshTokenDO.getId());
            throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "刷新令牌已过期");
        }

//        // 创建访问令牌
//        return createOAuth2AccessToken(refreshTokenDO, clientDO);
        return null;
    }

    @Override
    public OAuth2AccessTokenDO getAccessToken(String accessToken) {
//        // 优先从 Redis 中获取
//        OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken);
//        if (accessTokenDO != null) {
//            return accessTokenDO;
//        }
//
//        // 获取不到，从 MySQL 中获取
//        accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken);
//        // 如果在 MySQL 存在，则往 Redis 中写入
//        if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
//            oauth2AccessTokenRedisDAO.set(accessTokenDO);
//        }
        // 因数据库不在存入缓存数据，所以直接从缓存中获取

        // 数据库移除令牌存入，所以直接从缓存中获取
        OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken);

        if(accessTokenDO != null){
            // 有令牌的校验，代表有接口访问，延长令牌的过期时间（30分钟）
            oauth2AccessTokenRedisDAO.set(accessTokenDO.setExpiresTime(LocalDateTime.now().plusSeconds(1800)));
        }
        return accessTokenDO;
    }

    @Override
    public OAuth2AccessTokenDO checkAccessToken(String accessToken) {
        OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken);
        if (accessTokenDO == null) {
            throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "访问令牌不存在");
        }
//        if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) {
//            throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), "访问令牌已过期");
//        }
        return accessTokenDO;
    }

    @Override
    public OAuth2AccessTokenDO removeAccessToken(String accessToken) {
        // 删除访问令牌
        OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken);
        if (accessTokenDO == null) {
            return null;
        }
        oauth2AccessTokenMapper.deleteById(accessTokenDO.getId());
        oauth2AccessTokenRedisDAO.delete(accessToken);
        // 删除刷新令牌
        oauth2RefreshTokenMapper.deleteByRefreshToken(accessTokenDO.getRefreshToken());
        return accessTokenDO;
    }

    @Override
    public PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO) {
        return oauth2AccessTokenMapper.selectPage(reqVO);
    }


    // 填入访问令牌包含的字段
    private OAuth2AccessTokenDO createOAuth2ReToken(Long userId, Integer userType, OAuth2ClientDO clientDO, List<String> scopes, Boolean large, Integer dbNo, Integer tableNo, String dataCode, Long organId, String nickname) {
        OAuth2AccessTokenDO refreshToken = new OAuth2AccessTokenDO().setRefreshToken(generateRefreshToken())
                .setUserId(userId).setUserType(userType)
                .setClientId(clientDO.getClientId()).setScopes(scopes)
                .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getRefreshTokenValiditySeconds()))
                .setLarge(large).setDbNo(dbNo).setTableNo(tableNo).setDataCode(dataCode)
                .setOrganId(organId).setNickname(nickname);
        if (organId != null) {
            OrganContextHolder.setOrganId(organId);
        }
        boolean b = securityFrameworkService.hasAnyRoles(userId,"super_admin");
        refreshToken.setIsSupAdmin(b);
//        oauth2RefreshTokenMapper.insert(refreshToken);
        return refreshToken;
    }

    // 在Redis中新增Token，除去刷新Token字段
    private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2AccessTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
        OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())
                .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType())
                .setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes())
                .setRefreshToken(refreshTokenDO.getRefreshToken())
                .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds()))
                .setLarge(refreshTokenDO.getLarge()).setDbNo(refreshTokenDO.getDbNo()).setTableNo(refreshTokenDO.getTableNo())
                .setDataCode(refreshTokenDO.getDataCode()).setOrganId(refreshTokenDO.getOrganId())
                .setNickname(refreshTokenDO.getNickname()).setIsSupAdmin(refreshTokenDO.getIsSupAdmin())
                ;
        //accessTokenDO.setOrganId(OrganContextHolder.getOrganId()); // 手动设置组织编号，避免缓存到 Redis 的时候，无对应的组织编号
//        oauth2AccessTokenMapper.insert(accessTokenDO);
        // 记录到 Redis 中
        oauth2AccessTokenRedisDAO.set(accessTokenDO);
        return accessTokenDO;
    }



    private static String generateAccessToken() {
        return IdUtil.fastSimpleUUID();
    }

    private static String generateRefreshToken() {
        return IdUtil.fastSimpleUUID();
    }

}
