package org.lingniu.sdk.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.lingniu.sdk.constant.CacheConstants;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
public class RedisOAuth2AuthorizedClientRepository implements OAuth2AuthorizedClientRepository {

    private final RedisTemplate<String, Object> redisTemplate;
    private final ClientRegistrationRepository clientRegistrationRepository;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public RedisOAuth2AuthorizedClientRepository(
            RedisTemplate<String, Object> redisTemplate,
            ClientRegistrationRepository clientRegistrationRepository) {
        this.redisTemplate = redisTemplate;
        this.clientRegistrationRepository = clientRegistrationRepository;
    }
    
    @Override
    public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(
            String clientRegistrationId, 
            Authentication principal,
            HttpServletRequest request) {
        
        if (principal == null || !principal.isAuthenticated()) {
            return null;
        }
        
        String principalName = principal.getName();

        String key = buildClientKey(principalName, clientRegistrationId);
        
        try {
            Object data = redisTemplate.opsForValue().get(key);
            if (data == null) {
                return null;
            }
            
            // 反序列化
            Map<String, Object> clientData = objectMapper.convertValue(data, new TypeReference<Map<String, Object>>() {});
            
            // 重建 ClientRegistration
            ClientRegistration clientRegistration = clientRegistrationRepository
                .findByRegistrationId(clientRegistrationId);
            
            if (clientRegistration == null) {
                return null;
            }
            
            // 重建 OAuth2AccessToken
            OAuth2AccessToken accessToken = rebuildAccessToken(
                (Map<String, Object>) clientData.get("accessToken")
            );
            
            // 重建 OAuth2RefreshToken（如果有）
            OAuth2RefreshToken refreshToken = null;
            if (clientData.containsKey("refreshToken")) {
                refreshToken = rebuildRefreshToken(
                    (Map<String, Object>) clientData.get("refreshToken")
                );
            }
            
            // 重建 OAuth2AuthorizedClient
            OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
                clientRegistration,
                principalName,
                accessToken,
                refreshToken
            );

            
            @SuppressWarnings("unchecked")
            T result = (T) authorizedClient;
            return result;
            
        } catch (Exception e) {
            // 如果反序列化失败，删除损坏的数据
            redisTemplate.delete(key);
            return null;
        }
    }
    
    @Override
    public void saveAuthorizedClient(
            OAuth2AuthorizedClient authorizedClient,
            Authentication principal,
            HttpServletRequest request,
            HttpServletResponse response) {
        
        if (principal == null || !principal.isAuthenticated()) {
            return;
        }
        
        String principalName = principal.getName();
        String clientRegistrationId = authorizedClient.getClientRegistration().getRegistrationId();
        String key = buildClientKey(principalName, clientRegistrationId);
        try {
            // 序列化 OAuth2AuthorizedClient
            Map<String, Object> clientData = new HashMap<>();
            
            // 存储 ClientRegistrationId
            clientData.put("clientRegistrationId", clientRegistrationId);
            clientData.put("principalName", principalName);
            
            // 序列化 AccessToken
            OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
            Map<String, Object> accessTokenData = new HashMap<>();
            accessTokenData.put("tokenValue", accessToken.getTokenValue());
            accessTokenData.put("tokenType", accessToken.getTokenType().getValue());
            accessTokenData.put("issuedAt", accessToken.getIssuedAt().toString());
            accessTokenData.put("expiresAt", accessToken.getExpiresAt().toString());
            accessTokenData.put("scopes", new ArrayList<>(accessToken.getScopes()));
            clientData.put("accessToken", accessTokenData);
            
            // 序列化 RefreshToken（如果有）
            OAuth2RefreshToken refreshToken = authorizedClient.getRefreshToken();
            if (refreshToken != null) {
                Map<String, Object> refreshTokenData = new HashMap<>();
                refreshTokenData.put("tokenValue", refreshToken.getTokenValue());
                if (refreshToken.getIssuedAt() != null) {
                    refreshTokenData.put("issuedAt", refreshToken.getIssuedAt().toString());
                }
                if (refreshToken.getExpiresAt() != null) {
                    refreshTokenData.put("expiresAt", refreshToken.getExpiresAt().toString());
                }
                clientData.put("refreshToken", refreshTokenData);
            }
            
            // 存储到 Redis
            redisTemplate.opsForValue().set(key, clientData);
            
            // 设置过期时间（根据 AccessToken 的过期时间）
            Duration expiresIn = Duration.between(Instant.now(), accessToken.getExpiresAt());
            if (!expiresIn.isNegative()) {
                redisTemplate.expire(key, 7, TimeUnit.DAYS);
            }
            
        } catch (Exception e) {
            throw new RuntimeException("Failed to save OAuth2AuthorizedClient to Redis", e);
        }
    }
    
    @Override
    public void removeAuthorizedClient(
            String clientRegistrationId,
            Authentication principal,
            HttpServletRequest request,
            HttpServletResponse response) {
        
        if (principal == null) {
            return;
        }
        
        String principalName = principal.getName();
        String key = buildClientKey(principalName, clientRegistrationId);
        
        // 删除客户端数据
        redisTemplate.delete(key);
    }
    
    private String buildClientKey(String principalName, String clientRegistrationId) {
        return CacheConstants.OAUTH2_CLIENT_KEY_PREFIX + principalName + ":" + clientRegistrationId;
    }

    
    private OAuth2AccessToken rebuildAccessToken(Map<String, Object> data) {
        String tokenValue = (String) data.get("tokenValue");
        OAuth2AccessToken.TokenType tokenType = OAuth2AccessToken.TokenType.BEARER;
        if (data.containsKey("tokenType")) {
            String typeStr = (String) data.get("tokenType");
            tokenType = new OAuth2AccessToken.TokenType(typeStr);
        }
        
        Instant issuedAt = Instant.parse((String) data.get("issuedAt"));
        Instant expiresAt = Instant.parse((String) data.get("expiresAt"));
        
        @SuppressWarnings("unchecked")
        Set<String> scopes = new HashSet<>((List<String>) data.get("scopes"));
        
        return new OAuth2AccessToken(tokenType, tokenValue, issuedAt, expiresAt, scopes);
    }
    
    private OAuth2RefreshToken rebuildRefreshToken(Map<String, Object> data) {
        String tokenValue = (String) data.get("tokenValue");
        Instant issuedAt = data.containsKey("issuedAt") ? 
            Instant.parse((String) data.get("issuedAt")) : null;
        Instant expiresAt = data.containsKey("expiresAt") ? 
            Instant.parse((String) data.get("expiresAt")) : null;
        
        return new OAuth2RefreshToken(tokenValue, issuedAt, expiresAt);
    }
    
    /**
     * 获取用户的所有客户端
     */
    public List<OAuth2AuthorizedClient> findByPrincipalName(String principalName) {
        String pattern = CacheConstants.OAUTH2_CLIENT_KEY_PREFIX + principalName + ":*";
        Set<String> keys = redisTemplate.keys(pattern);
        
        List<OAuth2AuthorizedClient> clients = new ArrayList<>();
        for (String key : keys) {
            String clientRegistrationId = extractClientRegistrationId(key);
            // 这里需要 principal，简化处理
            // 实际使用时可能需要调整
        }
        
        return clients;
    }
    
    /**
     * 清理过期的客户端
     */
    public void cleanupExpiredClients() {
        // 可以通过 Redis 的过期策略自动清理
        // 也可以手动扫描并删除过期的 token
        String pattern = CacheConstants.OAUTH2_CLIENT_KEY_PREFIX + "*";
        Set<String> keys = redisTemplate.keys(pattern);
        
        for (String key : keys) {
            Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
            if (ttl != null && ttl <= 0) {
                redisTemplate.delete(key);
            }
        }
    }
    
    private String extractClientRegistrationId(String key) {
        return key.substring(key.lastIndexOf(":") + 1);
    }
}