package com.qqchen.deploy.backend.framework.security.util;

import com.qqchen.deploy.backend.framework.security.CustomUserDetails;
import com.qqchen.deploy.backend.framework.utils.RedisUtil;
import com.qqchen.deploy.backend.framework.utils.UserAgentUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Slf4j
@Component
public class JwtTokenUtil {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;
    
    @Resource
    private RedisUtil redisUtil;
    
    @Resource
    private UserAgentUtil userAgentUtil;
    
    // Redis Key前缀
    private static final String TOKEN_PREFIX = "auth:token:";
    
    private SecretKey key;

    private SecretKey getKey() {
        if (key == null) {
            byte[] keyBytes = new byte[32];
            byte[] secretBytes = secret.getBytes(StandardCharsets.UTF_8);
            System.arraycopy(secretBytes, 0, keyBytes, 0, Math.min(secretBytes.length, keyBytes.length));
            key = Keys.hmacShaKeyFor(keyBytes);
        }
        return key;
    }

    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser()
                .verifyWith(getKey())
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    /**
     * 生成Token（集成Redis存储）
     * 
     * @param userDetails 用户信息
     * @return Token字符串
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        String token = doGenerateToken(claims, userDetails.getUsername());
        
        // 存储到Redis（如果userDetails是CustomUserDetails，可以获取userId）
        if (userDetails instanceof CustomUserDetails) {
            Long userId = ((CustomUserDetails) userDetails).getUserId();
            storeToken(userId, token);
        }
        
        return token;
    }
    
    /**
     * 生成Token（集成Redis存储，包含请求信息）
     * 
     * @param userDetails 用户信息
     * @param request HTTP请求
     * @return Token字符串
     */
    public String generateToken(UserDetails userDetails, HttpServletRequest request) {
        Map<String, Object> claims = new HashMap<>();
        String token = doGenerateToken(claims, userDetails.getUsername());
        
        // 存储到Redis（如果userDetails是CustomUserDetails，可以获取userId）
        if (userDetails instanceof CustomUserDetails) {
            Long userId = ((CustomUserDetails) userDetails).getUserId();
            storeToken(userId, token, request);
        }
        
        return token;
    }

    private String doGenerateToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .claims(claims)
                .subject(subject)
                .issuedAt(new Date(System.currentTimeMillis()))
                .expiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .signWith(getKey())
                .compact();
    }

    /**
     * 验证Token（集成Redis验证）
     * 
     * @param token Token字符串
     * @param userDetails 用户信息
     * @return true=有效, false=无效
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        try {
            final String username = getUsernameFromToken(token);
            
            // 1. 基础验证：用户名匹配 + Token未过期
            if (!username.equals(userDetails.getUsername()) || isTokenExpired(token)) {
                return false;
            }
            
            // 2. Redis验证：检查Token是否匹配（用于强制下线场景）
            if (userDetails instanceof CustomUserDetails) {
                Long userId = ((CustomUserDetails) userDetails).getUserId();
                
                // 检查Redis中的Token是否匹配（如果Redis中没有Token，说明用户已被强制下线）
                String storedToken = getStoredToken(userId);
                if (storedToken == null) {
                    log.warn("Token不存在（可能已被强制下线）: userId={}", userId);
                    return false;
                }
                
                if (!storedToken.equals(token)) {
                    log.warn("Token不匹配: userId={}", userId);
                    return false;
                }
            }
            
            return true;
        } catch (Exception e) {
            log.error("Error validating token", e);
            return false;
        }
    }
    
    // ============================== Redis Token 管理 ==============================
    
    /**
     * 存储Token到Redis（包含登录时间）
     */
    private void storeToken(Long userId, String token) {
        String key = TOKEN_PREFIX + userId;
        
        // 存储Token + 登录时间
        Map<String, Object> tokenInfo = new HashMap<>();
        tokenInfo.put("token", token);
        tokenInfo.put("loginTime", LocalDateTime.now().toString());
        
        redisUtil.set(key, tokenInfo, expiration);
        log.debug("Token已存储到Redis: userId={}, ttl={}秒", userId, expiration);
    }
    
    /**
     * 存储Token到Redis（包含登录时间和请求信息）
     */
    private void storeToken(Long userId, String token, HttpServletRequest request) {
        String key = TOKEN_PREFIX + userId;
        
        // 获取IP地址
        String ipAddress = userAgentUtil.getRealIpAddress(request);
        
        // 解析User-Agent
        String userAgentString = request.getHeader("User-Agent");
        UserAgentUtil.UserAgentInfo userAgentInfo = userAgentUtil.parseUserAgent(userAgentString);
        
        // 存储Token + 登录时间 + 请求信息
        Map<String, Object> tokenInfo = new HashMap<>();
        tokenInfo.put("token", token);
        tokenInfo.put("loginTime", LocalDateTime.now().toString());
        tokenInfo.put("ipAddress", ipAddress);
        tokenInfo.put("browser", userAgentInfo.getBrowser());
        tokenInfo.put("os", userAgentInfo.getOs());
        
        redisUtil.set(key, tokenInfo, expiration);
        log.debug("Token已存储到Redis: userId={}, ip={}, browser={}, os={}, ttl={}秒", 
                userId, ipAddress, userAgentInfo.getBrowser(), userAgentInfo.getOs(), expiration);
    }
    
    /**
     * 从Redis获取Token字符串
     */
    private String getStoredToken(Long userId) {
        String key = TOKEN_PREFIX + userId;
        Object value = redisUtil.get(key);
        
        if (value == null) return null;
        
        // 兼容旧格式（字符串）和新格式（Map）
        if (value instanceof String) {
            return (String) value;
        } else if (value instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> tokenInfo = (Map<String, Object>) value;
            return (String) tokenInfo.get("token");
        }
        
        return null;
    }
    
    /**
     * 获取完整的Token信息（包含在线状态）
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> getTokenInfo(Long userId) {
        String key = TOKEN_PREFIX + userId;
        Object value = redisUtil.get(key);
        
        if (value instanceof Map) {
            return (Map<String, Object>) value;
        }
        
        return null;
    }
    
    /**
     * 删除Token（用于登出或强制下线）
     * 
     * @param userId 用户ID
     */
    public void removeToken(Long userId) {
        String key = TOKEN_PREFIX + userId;
        redisUtil.del(key);
        log.info("Token已删除: userId={}", userId);
    }
    
    /**
     * 刷新Token过期时间
     * 
     * @param userId 用户ID
     */
    public void refreshToken(Long userId) {
        String key = TOKEN_PREFIX + userId;
        redisUtil.expire(key, expiration);
        log.debug("Token过期时间已刷新: userId={}, ttl={}秒", userId, expiration);
    }
}