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.dataobject.organ.OrganizationDO;
import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO;
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.mysql.organ.OrganMapper;
import com.cf.imes.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO;
import com.cf.imes.module.system.service.organ.OrganService;
import com.cf.imes.module.system.service.tokenconfig.TokenConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.Cursor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

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
@Slf4j
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;

    @Resource
    private OrganService organService;


    @Resource
    private TokenConfigService tokenConfigService;

    @Resource
    private OrganMapper organMapper;

    private static final String FIELD_IMES = "imes-";

    @Value("${chenfeng.organ.use-data-code:imes_prod}")
    private String useDataCode;



    @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);
    }

    @Override
    public OAuth2AccessTokenDO createAccessToken(AdminUserDO user, Integer userType, String clientId, String dataCode) {
        // 填入访问令牌包含的字段
        OAuth2AccessTokenDO refreshTokenDO = createOAuth2ReToken(user, userType, dataCode);
        // 创建访问令牌
        return createOAuth2AccessToken(refreshTokenDO);
    }

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

        // 校验 Client 匹配
        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 null;
    }



    @Override
    public OAuth2AccessTokenDO getAccessToken(String accessToken) {

        OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO();

        String substring = accessToken.substring(0, 5);

        // 外部应用 token 校验
        if(FIELD_IMES.equals(substring)){

            Long organId = tokenConfigService.checkTokenConfig(accessToken);

            OrganizationDO organizationDO = organMapper.selectById(organId);


            accessTokenDO.setOrganId(organId);
            accessTokenDO.setUserId(organizationDO.getContactUserId());
            accessTokenDO.setUserType(2);
            accessTokenDO.setClientId("default");
            accessTokenDO.setExpiresTime(organizationDO.getExpireTime());
            accessTokenDO.setDataCode(useDataCode);
            accessTokenDO.setNickname(organizationDO.getName());
            accessTokenDO.setIsSupAdmin(true);

        }else {

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

            if (accessTokenDO != null) {
                // 校验组织的有效性
                organService.validOrgan(accessTokenDO.getOrganId());
                // 有令牌的校验，代表有接口访问，延长令牌的过期时间（30分钟）
                oauth2AccessTokenRedisDAO.expire(accessToken, 1, TimeUnit.DAYS);
            }
        }

        return accessTokenDO;
    }

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

    @Override
    public OAuth2AccessTokenDO removeAccessToken(String accessToken) {
        // 删除访问令牌
        OAuth2AccessTokenDO oauth2AccessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken);
        if (ObjectUtil.isNotNull(oauth2AccessTokenDO)) {
            oauth2AccessTokenRedisDAO.delete(accessToken);
            return oauth2AccessTokenDO;
        } else {
            return null;
        }
    }

    @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).setDeptId(null);
        if (organId != null) {
            OrganContextHolder.setOrganId(organId);
        }
        boolean b = securityFrameworkService.hasAnyRoles(userId,"super_admin");
        refreshToken.setIsSupAdmin(b);
        return refreshToken;
    }

    // 填入访问令牌包含的字段
    private OAuth2AccessTokenDO createOAuth2ReToken(AdminUserDO user, Integer userType, String dataCode) {
        Long organId = user.getOrganId();
        Long userId = user.getId();
        OAuth2AccessTokenDO refreshToken = new OAuth2AccessTokenDO()
                .setUserId(userId)
                .setUserType(user.getUserType().getValue())
                .setDataCode(dataCode)
                .setOrganId(organId)
                .setNickname(user.getNickname())
                .setDeptId(user.getDeptId())
                .setGrayVersion(user.getGrayVersion())
                .setGrayDefaultVersion(user.getGrayDefaultVersion());
        if (organId != null) {
            OrganContextHolder.setOrganId(organId);
        }
        refreshToken.setIsSupAdmin(user.isSuperAdmin());
        return refreshToken;
    }

    // 在Redis中新增Token，除去刷新Token字段
    private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2AccessTokenDO accessTokenDO) {
        String currentToken = generateAccessToken();
        Long userId = accessTokenDO.getUserId();
        accessTokenDO.setAccessToken(currentToken);
        // 记录到 Redis 中
        oauth2AccessTokenRedisDAO.set(accessTokenDO);
        // 异步踢出其他token
        CompletableFuture.runAsync(() -> {
            kickOut(userId, currentToken);
        }).exceptionally(e -> {
            log.error("[OAuth2TokenServiceImpl] redis 清空token失败, 用户id:{}, 异常:{}", userId, e);
            return null;
        });
        return accessTokenDO;
    }

    /**
     * 踢出除本次userId登录产生token以外的所有token（不处理超管的超管除外）
     *
     * @param userId
     * @param currentToken
     */
    public void kickOut(Long userId, String currentToken) {
        List<String> matchKeys = new ArrayList<>();
        Cursor<String> cursor = oauth2AccessTokenRedisDAO.scan();
        while (cursor.hasNext()) {
            matchKeys.add(cursor.next());
        }
        cursor.close();
        if (CollUtil.isNotEmpty(matchKeys)) {
            for (String key : matchKeys) {
                // 获取key下的用户信息
                OAuth2AccessTokenDO oAuth2AccessTokenDO = oauth2AccessTokenRedisDAO.getWithKey(key);
                if (ObjectUtil.equal(userId, oAuth2AccessTokenDO.getUserId()) && !StringUtils.equals(currentToken, oAuth2AccessTokenDO.getAccessToken()) && Boolean.FALSE.equals(oAuth2AccessTokenDO.getIsSupAdmin())) {
                    // 用户id匹配、非当次登录token、非超管 -> 删除redis中的token缓存
                    oauth2AccessTokenRedisDAO.deleteWithKey(key);
                }
            }
        }
    }



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

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

}
