package cn.meowrain.aioj.backend.auth.oauth2.service;

import cn.meowrain.aioj.backend.auth.clients.UserClient;
import cn.meowrain.aioj.backend.auth.common.constants.RedisKeyConstants;
import cn.meowrain.aioj.backend.auth.dto.resp.UserAuthRespDTO;
import cn.meowrain.aioj.backend.auth.oauth2.dto.OAuth2TokenResponse;
import cn.meowrain.aioj.backend.auth.oauth2.entity.OAuth2Client;
import cn.meowrain.aioj.backend.auth.utils.AuthServiceJwtUtil;

import cn.meowrain.aioj.backend.framework.core.web.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * OAuth2 Token 服务 负责 Token 的生成、刷新和验证
 *
 * @author meowrain
 * @since 2025-12-14
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OAuth2TokenService {

	private final AuthServiceJwtUtil jwtUtil;

	private final UserClient userClient;

	private final StringRedisTemplate redisTemplate;

	private final OAuth2SessionService sessionService;

	/**
	 * 生成 Token 响应（授权码流程）
	 * @param client 客户端信息
	 * @param userId 用户ID
	 * @param scope 授权范围
	 * @param nonce Nonce参数（用于ID Token）
	 * @return Token响应
	 */
	public OAuth2TokenResponse generateTokenResponse(OAuth2Client client, Long userId, String scope, String nonce) {
		// 1. 调用 user-service 获取用户信息
		Result<UserAuthRespDTO> userResult = userClient.getUserById(userId);
		if (userResult == null || userResult.getData() == null) {
			throw new RuntimeException("获取用户信息失败");
		}

		UserAuthRespDTO user = userResult.getData();

		// 2. 生成 Access Token
		String accessToken = jwtUtil.generateAccessToken(user);

		// 3. 生成 Refresh Token
		String refreshToken = jwtUtil.generateRefreshToken(userId);

		// 4. 生成 ID Token（OIDC）
		String idToken = null;
		if (scope != null && scope.contains("openid")) {
			idToken = jwtUtil.generateIdToken(user, client.getClientId(), nonce);
		}

		// 5. 存储 Refresh Token 到 Redis
		String refreshTokenKey = String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, userId);
		redisTemplate.opsForValue().set(refreshTokenKey, refreshToken, client.getRefreshTokenTtl(), TimeUnit.SECONDS);

		// 6. 创建会话（用于单点登出）
		sessionService.createSession(userId, client.getClientId(), accessToken, refreshToken);

		log.info("签发Token: clientId={}, userId={}, scope={}", client.getClientId(), userId, scope);

		// 7. 构造响应
		return OAuth2TokenResponse.builder()
			.accessToken(accessToken)
			.tokenType("Bearer")
			.expiresIn(client.getAccessTokenTtl())
			.refreshToken(refreshToken)
			.scope(scope)
			.idToken(idToken)
			.build();
	}

	/**
	 * 刷新 Token
	 * @param client 客户端信息
	 * @param refreshToken Refresh Token
	 * @param scope 授权范围（可选，不能超出原范围）
	 * @return Token响应
	 */
	public OAuth2TokenResponse refreshToken(OAuth2Client client, String refreshToken, String scope) {
		// 1. 验证 Refresh Token
		if (!jwtUtil.isTokenValid(refreshToken)) {
			throw new RuntimeException("Refresh Token 无效或已过期");
		}

		// 2. 解析 Refresh Token 获取用户ID
		Long userId = Long.parseLong(jwtUtil.parseClaims(refreshToken).getSubject());

		// 3. 从 Redis 验证 Refresh Token
		String refreshTokenKey = String.format(RedisKeyConstants.REFRESH_TOKEN_KEY_PREFIX, userId);
		String storedToken = redisTemplate.opsForValue().get(refreshTokenKey);

		if (!refreshToken.equals(storedToken)) {
			log.warn("Refresh Token 不匹配: userId={}", userId);
			throw new RuntimeException("Refresh Token 无效");
		}

		// 4. 调用 user-service 获取最新用户信息
		Result<UserAuthRespDTO> userResult = userClient.getUserById(Long.valueOf(userId));
		if (userResult == null || userResult.getData() == null) {
			throw new RuntimeException("获取用户信息失败");
		}

		UserAuthRespDTO user = userResult.getData();

		// 5. 生成新的 Access Token
		String newAccessToken = jwtUtil.generateAccessToken(user);

		// 6. 生成新的 Refresh Token（Refresh Token Rotation）
		String newRefreshToken = jwtUtil.generateRefreshToken(userId);

		// 7. 更新 Redis 中的 Refresh Token
		redisTemplate.opsForValue()
			.set(refreshTokenKey, newRefreshToken, client.getRefreshTokenTtl(), TimeUnit.SECONDS);

		log.info("刷新Token: clientId={}, userId={}", client.getClientId(), userId);

		// 8. 构造响应（刷新时不返回 ID Token）
		return OAuth2TokenResponse.builder()
			.accessToken(newAccessToken)
			.tokenType("Bearer")
			.expiresIn(client.getAccessTokenTtl())
			.refreshToken(newRefreshToken)
			.scope(scope)
			.build();
	}

	/**
	 * 从授权码数据中提取信息
	 * @param codeData 授权码数据
	 * @param key 键名
	 * @return 字符串值
	 */
	public String extractFromCodeData(Map<String, Object> codeData, String key) {
		Object value = codeData.get(key);
		return value != null ? value.toString() : null;
	}

	/**
	 * 从授权码数据中提取用户ID
	 * @param codeData 授权码数据
	 * @return 用户ID
	 */
	public Long extractUserIdFromCodeData(Map<String, Object> codeData) {
		Object userId = codeData.get("userId");
		if (userId instanceof Number) {
			return ((Number) userId).longValue();
		}
		return Long.parseLong(userId.toString());
	}

}
