package com.erp.user.security;

import com.erp.user.entity.auth.AuthToken;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * JWT令牌提供者
 */
@Slf4j
@Component
public class JwtTokenProvider {
    
    @Value("${jwt.secret:erp-jwt-secret-key-change-in-production-12345678901234567890}")
    private String jwtSecret;
    
    @Value("${jwt.access-token-expiry:7200}")
    private Long accessTokenExpiry; // 2小时
    
    @Value("${jwt.refresh-token-expiry:604800}")
    private Long refreshTokenExpiry; // 7天
    
    private static final String ACCESS_TOKEN_PREFIX = "auth:access:";
    private static final String REFRESH_TOKEN_PREFIX = "auth:refresh:";
    private static final String USER_TOKEN_PREFIX = "auth:user:";
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    public JwtTokenProvider(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    private SecretKey getSigningKey() {
        byte[] keyBytes = jwtSecret.getBytes(StandardCharsets.UTF_8);
        return Keys.hmacShaKeyFor(keyBytes);
    }
    
    /**
     * 生成令牌对
     */
    public AuthToken generateToken(Long userId, Long tenantId, String username, Long deviceId, 
                                    String deviceName, String deviceType, String loginIp, String loginLocation) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime expiresAt = now.plusSeconds(accessTokenExpiry);
        LocalDateTime refreshExpiresAt = now.plusSeconds(refreshTokenExpiry);
        
        String accessToken = generateAccessToken(userId, tenantId, username, expiresAt);
        String refreshToken = generateRefreshToken(userId, tenantId, refreshExpiresAt);
        
        AuthToken authToken = AuthToken.builder()
                .accessToken(accessToken)
                .refreshToken(refreshToken)
                .userId(userId)
                .tenantId(tenantId)
                .username(username)
                .deviceId(deviceId)
                .deviceName(deviceName)
                .deviceType(deviceType)
                .loginIp(loginIp)
                .loginLocation(loginLocation)
                .createdAt(now)
                .expiresAt(expiresAt)
                .refreshExpiresAt(refreshExpiresAt)
                .build();
        
        // 存储到Redis
        saveTokenToRedis(authToken);
        
        return authToken;
    }
    
    private String generateAccessToken(Long userId, Long tenantId, String username, LocalDateTime expiresAt) {
        Date expiration = Date.from(expiresAt.atZone(java.time.ZoneId.systemDefault()).toInstant());
        
        return Jwts.builder()
                .id(UUID.randomUUID().toString())
                .subject(String.valueOf(userId))
                .claim("tenantId", tenantId)
                .claim("username", username)
                .issuedAt(new Date())
                .expiration(expiration)
                .signWith(getSigningKey())
                .compact();
    }
    
    private String generateRefreshToken(Long userId, Long tenantId, LocalDateTime expiresAt) {
        Date expiration = Date.from(expiresAt.atZone(java.time.ZoneId.systemDefault()).toInstant());
        
        return Jwts.builder()
                .id(UUID.randomUUID().toString())
                .subject(String.valueOf(userId))
                .claim("tenantId", tenantId)
                .claim("type", "refresh")
                .issuedAt(new Date())
                .expiration(expiration)
                .signWith(getSigningKey())
                .compact();
    }
    
    private void saveTokenToRedis(AuthToken authToken) {
        String accessKey = ACCESS_TOKEN_PREFIX + authToken.getAccessToken();
        String refreshKey = REFRESH_TOKEN_PREFIX + authToken.getRefreshToken();
        String userKey = USER_TOKEN_PREFIX + authToken.getUserId() + ":" + authToken.getDeviceId();
        
        redisTemplate.opsForValue().set(accessKey, authToken, Duration.ofSeconds(accessTokenExpiry));
        redisTemplate.opsForValue().set(refreshKey, authToken, Duration.ofSeconds(refreshTokenExpiry));
        redisTemplate.opsForValue().set(userKey, String.valueOf(authToken.getAccessToken()), Duration.ofSeconds(accessTokenExpiry));
    }
    
    /**
     * 验证并解析Token
     */
    public AuthToken validateToken(String token) {
        try {
            Claims claims = Jwts.parser()
                    .verifyWith(getSigningKey())
                    .build()
                    .parseSignedClaims(token)
                    .getPayload();
            
            String tokenType = claims.get("type", String.class);
            if ("refresh".equals(tokenType)) {
                return getRefreshToken(token);
            }
            return getAccessToken(token);
        } catch (ExpiredJwtException e) {
            log.warn("Token已过期: {}", e.getMessage());
            return null;
        } catch (JwtException e) {
            log.warn("Token验证失败: {}", e.getMessage());
            return null;
        }
    }
    
    /**
     * 从Redis获取访问令牌信息
     */
    public AuthToken getAccessToken(String token) {
        String key = ACCESS_TOKEN_PREFIX + token;
        Object value = redisTemplate.opsForValue().get(key);
        if (value instanceof AuthToken) {
            return (AuthToken) value;
        }
        return null;
    }
    
    /**
     * 从Redis获取刷新令牌信息
     */
    public AuthToken getRefreshToken(String token) {
        String key = REFRESH_TOKEN_PREFIX + token;
        Object value = redisTemplate.opsForValue().get(key);
        if (value instanceof AuthToken) {
            return (AuthToken) value;
        }
        return null;
    }
    
    /**
     * 刷新Token
     */
    public AuthToken refreshToken(String refreshToken) {
        AuthToken oldToken = getRefreshToken(refreshToken);
        if (oldToken == null) {
            return null;
        }
        
        // 删除旧令牌
        revokeToken(oldToken.getAccessToken());
        
        // 生成新令牌
        return generateToken(
                oldToken.getUserId(),
                oldToken.getTenantId(),
                oldToken.getUsername(),
                oldToken.getDeviceId(),
                oldToken.getDeviceName(),
                oldToken.getDeviceType(),
                oldToken.getLoginIp(),
                oldToken.getLoginLocation()
        );
    }
    
    /**
     * 撤销令牌
     */
    public void revokeToken(String token) {
        AuthToken authToken = getAccessToken(token);
        if (authToken != null) {
            redisTemplate.delete(ACCESS_TOKEN_PREFIX + token);
            redisTemplate.delete(REFRESH_TOKEN_PREFIX + authToken.getRefreshToken());
            redisTemplate.delete(USER_TOKEN_PREFIX + authToken.getUserId() + ":" + authToken.getDeviceId());
        }
    }
    
    /**
     * 撤销用户所有设备令牌
     */
    public void revokeAllUserTokens(Long userId) {
        String pattern = USER_TOKEN_PREFIX + userId + ":*";
        var keys = redisTemplate.keys(pattern);
        if (keys != null && !keys.isEmpty()) {
            for (String key : keys) {
                try {
                    Object tokenObj = redisTemplate.opsForValue().get(key);
                    if (tokenObj != null) {
                        String token = tokenObj.toString();
                        redisTemplate.delete(ACCESS_TOKEN_PREFIX + token);
                    }
                    redisTemplate.delete(key);
                } catch (Exception e) {
                    log.warn("撤销令牌失败: {}", e.getMessage());
                }
            }
        }
    }
    
    /**
     * 获取访问令牌过期时间
     */
    public Long getAccessTokenExpiry() {
        return accessTokenExpiry;
    }
}
