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

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import cn.meowrain.aioj.backend.auth.common.constants.RedisKeyConstants;
import cn.meowrain.aioj.backend.auth.oauth2.dto.OAuth2AuthorizeRequest;
import cn.meowrain.aioj.backend.auth.oauth2.exception.InvalidGrantException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

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

/**
 * OAuth2 授权码管理服务 负责授权码的生成、存储、验证和消费
 *
 * @author meowrain
 * @since 2025-12-14
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OAuth2AuthorizationService {

	private final StringRedisTemplate redisTemplate;

	/**
	 * 授权码有效期（秒）
	 */
	private static final int AUTH_CODE_TTL = 600; // 10 分钟

	/**
	 * 授权码长度
	 */
	private static final int AUTH_CODE_LENGTH = 32;

	/**
	 * 生成授权码
	 * @param request 授权请求
	 * @return 授权码
	 */
	public String generateAuthorizationCode(OAuth2AuthorizeRequest request) {
		// 1. 生成 32 字符随机授权码
		String code = RandomUtil.randomString(AUTH_CODE_LENGTH);

		// 2. 构造授权码数据
		Map<String, Object> codeData = new HashMap<>();
		codeData.put("code", code);
		codeData.put("clientId", request.getClientId());
		codeData.put("userId", request.getUserId());
		codeData.put("redirectUri", request.getRedirectUri());
		codeData.put("scope", request.getScope());
		codeData.put("codeChallenge", request.getCodeChallenge());
		codeData.put("codeChallengeMethod", request.getCodeChallengeMethod());
		codeData.put("nonce", request.getNonce());
		codeData.put("expiresAt", System.currentTimeMillis() + AUTH_CODE_TTL * 1000);

		// 3. 存储到 Redis
		String key = String.format(RedisKeyConstants.OAUTH2_AUTH_CODE_PREFIX, code);
		redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(codeData), AUTH_CODE_TTL, TimeUnit.SECONDS);

		log.info("生成授权码: code={}, clientId={}, userId={}", code, request.getClientId(), request.getUserId());

		return code;
	}

	/**
	 * 验证并消费授权码（一次性使用）
	 * @param code 授权码
	 * @return 授权码数据
	 * @throws InvalidGrantException 授权码无效或已过期
	 */
	public Map<String, Object> validateAndConsumeCode(String code) {
		String key = String.format(RedisKeyConstants.OAUTH2_AUTH_CODE_PREFIX, code);

		// 1. 从 Redis 获取授权码数据
		String data = redisTemplate.opsForValue().get(key);

		if (data == null) {
			log.warn("授权码无效或已过期: {}", code);
			throw new InvalidGrantException("授权码无效或已过期");
		}

		// 2. 立即删除授权码（一次性使用）
		redisTemplate.delete(key);

		// 3. 解析数据
		Map<String, Object> codeData = JSONUtil.toBean(data, Map.class);

		// 4. 检查是否过期
		long expiresAt = ((Number) codeData.get("expiresAt")).longValue();
		if (System.currentTimeMillis() > expiresAt) {
			log.warn("授权码已过期: {}", code);
			throw new InvalidGrantException("授权码已过期");
		}

		log.info("授权码验证成功并已消费: code={}, clientId={}, userId={}", code, codeData.get("clientId"),
				codeData.get("userId"));

		return codeData;
	}

	/**
	 * 验证授权码绑定的参数
	 * @param codeData 授权码数据
	 * @param clientId 客户端ID
	 * @param redirectUri 重定向URI
	 * @throws InvalidGrantException 验证失败
	 */
	public void validateCodeBinding(Map<String, Object> codeData, String clientId, String redirectUri) {
		// 验证 client_id
		if (!clientId.equals(codeData.get("clientId"))) {
			log.warn("授权码绑定的 client_id 不匹配: expected={}, actual={}", codeData.get("clientId"), clientId);
			throw new InvalidGrantException("授权码与客户端不匹配");
		}

		// 验证 redirect_uri
		if (!redirectUri.equals(codeData.get("redirectUri"))) {
			log.warn("授权码绑定的 redirect_uri 不匹配: expected={}, actual={}", codeData.get("redirectUri"), redirectUri);
			throw new InvalidGrantException("授权码与重定向URI不匹配");
		}
	}

}
