package com.zsElectric.boot.common.util.electric;

import cn.hutool.core.bean.BeanUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.zsElectric.boot.common.constant.ConnectivityConstants;
import com.zsElectric.boot.common.util.AESCryptoUtils;
import com.zsElectric.boot.common.util.HmacMD5Util;
import com.zsElectric.boot.common.util.OkHttpUtil;
import com.zsElectric.boot.common.util.SequenceGenUtil;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Token管理器 - 负责Token的获取、刷新、存储和有效性检查
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ElectricTokenManager {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Resource
    private OkHttpUtil okHttpUtil;

    private static final String TOKEN_KEY = "api:local:accessToken";
    private static final String TOKEN_REFRESH_LOCK_KEY = "api:token:refresh:lock";
    private static final int DEFAULT_TOKEN_EXPIRE_HOURS = 24;

    private final RedisTemplate<String, Object> redisTemplate;

    // 本地锁，防止单JVM内重复刷新Token[2](@ref)
    private final ReentrantLock localLock = new ReentrantLock();

    /**
     * 获取有效的访问令牌（主入口方法）
     */
    public String getValidAccessToken() {
        // 尝试从Redis获取已存储的Token[1](@ref)
        ApiToken apiToken = getStoredToken();

        // 如果Token不存在或已过期，则刷新Token[1,3](@ref)
        if (Objects.isNull(apiToken) || !apiToken.isValid()) {
            apiToken = refreshAccessToken();
        }

        return apiToken.getAccessToken();
    }

    /**
     * 从 Redis获取存储的Token
     */
    private ApiToken getStoredToken() {
        try {
            Object tokenObj = redisTemplate.opsForValue().get(TOKEN_KEY);
            if (Objects.isNull(tokenObj)) {
                log.debug("Redis中未找到Token，tokenKey: {}", TOKEN_KEY);
                return null;
            }
                
            // 如果是LinkedHashMap，需要转换为ApiToken
            if (tokenObj instanceof java.util.LinkedHashMap) {
                return BeanUtil.toBean(tokenObj, ApiToken.class);
            }
                
            // 如果已经是ApiToken类型，直接返回
            if (tokenObj instanceof ApiToken) {
                return (ApiToken) tokenObj;
            }
                
            log.warn("Redis中的Token类型不正确: {}", tokenObj.getClass().getName());
            return null;
        } catch (Exception e) {
            log.error("从Redis获取Token失败，tokenKey: {}", TOKEN_KEY, e);
            // 清除可能的错误数据
            redisTemplate.delete(TOKEN_KEY);
            return null;
        }
    }

    /**
     * Token刷新相关方法
     */
    private ApiToken refreshAccessToken() {
        // 先尝试获取本地锁，减少分布式锁的竞争[2](@ref)
        if (!localLock.tryLock()) {
            log.debug("本地锁已被占用，等待其他线程刷新Token");
            return waitAndRetrieve();
        }

        try {
            // 获取分布式锁，防止集群环境下多个实例同时刷新
            String lockKey = TOKEN_REFRESH_LOCK_KEY;
            Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(
                    lockKey, "LOCK", 30, TimeUnit.SECONDS);

            if (Boolean.FALSE.equals(lockAcquired)) {
                log.info("分布式锁已被占用，等待其他实例刷新Token");
                return waitAndRetrieve();
            }

            try {
                // 双重检查，防止重复刷新[2](@ref)
                ApiToken existingToken = getStoredToken();
                if (Objects.nonNull(existingToken) && existingToken.isValid()) {
                    log.info("其他线程已刷新Token，直接使用");
                    return existingToken;
                }

                log.info("开始刷新Token");
                ApiToken newToken = fetchNewTokenFromRemote();

                if (Objects.isNull(newToken) ||
                        Objects.isNull(newToken.getAccessToken()) ||
                        newToken.getAccessToken().trim().isEmpty()) {
                    throw new RuntimeException("刷新Token失败，获取到的Token为空");
                }

                // 设置Token时间信息并存储
                setupAndStoreToken(TOKEN_KEY, newToken);
                log.info("Token刷新成功，tokenKey: {}，过期时间: {}", TOKEN_KEY, newToken.getExpireTime());
                return newToken;

            } finally {
                // 释放分布式锁
                redisTemplate.delete(lockKey);
            }
        } finally {
            localLock.unlock();
        }
    }

    /**
     * 等待并重新获取Token（用于锁竞争场景）
     */
    private ApiToken waitAndRetrieve() {
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("等待锁释放时被中断", e);
        }
        // 重试获取Token
        ApiToken existingToken = getStoredToken();
        if (Objects.nonNull(existingToken) && existingToken.isValid()) {
            return existingToken;
        }
        // 如果仍然无效，递归刷新（会有锁控制）
        return refreshAccessToken();
    }

    /**
     * 调用第三方接口获取新Token
     */
    private ApiToken fetchNewTokenFromRemote() {
        log.info("========== 开始调用第三方接口获取Token ==========");
        try {
            //调用第三方API获取Token
            QueryTokenParms queryTokenParms = new QueryTokenParms();
            queryTokenParms
                    .setOperatorID(ConnectivityConstants.OPERATOR_ID)
                    .setOperatorSecret(ConnectivityConstants.PLATFORM_OPERATOR_SECRET);
            log.info("构造请求参数: OperatorID={}, OperatorSecret={}", 
                ConnectivityConstants.OPERATOR_ID, ConnectivityConstants.PLATFORM_OPERATOR_SECRET);

            RequestParmsEntity requestParms = new RequestParmsEntity();
            SequenceGenUtil.SequenceResult result = SequenceGenUtil.generate();
            log.info("生成序列号: TimeStamp={}, Seq={}", result.getTimestamp(), result.getSequence());

            requestParms
                    .setOperatorID(ConnectivityConstants.OPERATOR_ID);
            
            // 加密Data字段
            String dataToEncrypt = objectMapper.writeValueAsString(queryTokenParms);
            log.info("待加密的Data: {}", dataToEncrypt);
            String encryptedData = AESCryptoUtils.encrypt(dataToEncrypt, ConnectivityConstants.PLATFORM_DATA_SECRET,
                    ConnectivityConstants.PLATFORM_DATA_SECRET_IV);
            log.info("加密后的Data: {}", encryptedData);
            requestParms.setData(encryptedData);
            
            requestParms.setTimeStamp(result.getTimestamp());
            requestParms.setSeq(result.getSequence());
            
            // 生成签名
            String sig = HmacMD5Util.genSign(requestParms.getOperatorID(), requestParms.getData(), requestParms.getTimeStamp(),
                    requestParms.getSeq(), ConnectivityConstants.PLATFORM_SIG_SECRET);
            log.info("生成的签名: {}", sig);
            requestParms.setSig(sig);

            String requestUrl = ConnectivityConstants.TEST_DOMAIN + ConnectivityConstants.QUERY_TOKEN;
            log.info("调用第三方接口获取Token，URL: {}", requestUrl);
            
            // 直接将请求对象序列化为JSON字符串，确保大驼峰命名
            String requestJson = objectMapper.writeValueAsString(requestParms);
            log.info("请求参数JSON: {}", requestJson);
            
            JsonNode response = okHttpUtil.doPostForm(requestUrl, requestJson, null);

            if (Objects.isNull(response)) {
                log.error("调用第三方接口获取Token失败，响应为空");
                return null;
            }
            log.info("第三方接口响应: {}", response);

            ResponseParmsEntity responseParms = objectMapper.readValue(response.toString(), ResponseParmsEntity.class);
            
            if (Objects.isNull(responseParms)) {
                log.error("解析响应失败，responseParms为null，原始响应: {}", response);
                return null;
            }
            
            log.info("解析响应成功，ret: {}, msg: {}, data: {}", responseParms.getRet(), responseParms.getMsg(), responseParms.getData());
            
            String data = responseParms.getRet() + responseParms.getMsg() + responseParms.getData();
            boolean verify = HmacMD5Util.verify(data, ConnectivityConstants.PLATFORM_SIG_SECRET, responseParms.getSig());
            if (!verify) {
                log.error("第三方接口响应数据签名验证失败");
                return null;
            }
            if (responseParms.getRet() != 0) {
                switch (responseParms.getRet()) {
                    case -1:
                        log.error("系统繁忙,此时请求方稍后重试");
                        break;
                    case 4001:
                        log.error("签名错误");
                        break;
                    case 4002:
                        log.error("Token错误");
                        break;
                    case 4003:
                        log.error("参数不合法,缺少必需的示例:OperatorID、Sig、TimeStamp、Data、Seq五个参数");
                        break;
                    case 4004:
                        log.error("请求的业务参数不合法,各接口定义自己的必须参数");
                        break;
                    case 500:
                        log.error("系统错误");
                        break;
                    default:
                        log.error("未知错误码: {}", responseParms.getRet());
                        break;
                }
                return null;
            }
            
            String decodeData = AESCryptoUtils.decrypt(responseParms.getData(), ConnectivityConstants.PLATFORM_DATA_SECRET,
                    ConnectivityConstants.PLATFORM_DATA_SECRET_IV);
            log.info("解密后的数据: {}", decodeData);
            
            QueryTokenResponseData responseData = objectMapper.readValue(decodeData, QueryTokenResponseData.class);
            
            if (Objects.isNull(responseData)) {
                log.error("解析解密数据失败，responseData为null，解密数据: {}", decodeData);
                return null;
            }

            if (responseData.getSuccStat() == 1) {
                //0-无,1-OperatorID无效
                String failReason = "";
                if (responseData.getFailReason() == 0) {
                    failReason = "无";
                }
                if (responseData.getFailReason() == 1) {
                    failReason = "OperatorID无效";
                }
                log.error("调用第三方接口获取Token失败，失败原因: {}", failReason);
                return null;
            }

            ApiToken apiToken = new ApiToken();
            apiToken.setAccessToken(responseData.getAccessToken());
            apiToken.setTokenAvailableTime(responseData.getTokenAvailableTime());
            apiToken.setObtainTime(LocalDateTime.now());
            apiToken.setExpireTime(apiToken.getObtainTime().plusSeconds(apiToken.getTokenAvailableTime()));
            
            log.info("成功获取Token: AccessToken={}, 有效期={}s, 过期时间={}",
                apiToken.getAccessToken(), apiToken.getTokenAvailableTime(), apiToken.getExpireTime());
            log.info("========== 第三方接口调用成功 ==========");

            return apiToken;
        } catch (Exception e) {
            log.error("获取新Token时发生异常，tokenKey: {}, 异常信息: {}", TOKEN_KEY, e.getMessage(), e);
            throw new RuntimeException("调用第三方接口失败: " + e.getMessage(), e);
        }
    }

    /**
     * 设置Token信息并存储到Redis
     */
    private void setupAndStoreToken(String tokenKey, ApiToken token) {
        LocalDateTime obtainTime = LocalDateTime.now();
        token.setObtainTime(obtainTime);
        token.setExpireTime(obtainTime.plusSeconds(token.getTokenAvailableTime()));
        storeToken(tokenKey, token);
    }

    /**
     * 存储Token到Redis
     */
    private void storeToken(String apiKey, ApiToken token) {
        long expireSeconds = Duration.between(LocalDateTime.now(), token.getExpireTime()).getSeconds();

        // 设置Redis过期时间，取实际过期时间和默认过期时间的较小值
        long redisExpireSeconds = Math.max(expireSeconds, 60); // 至少保留60秒
        redisExpireSeconds = Math.min(redisExpireSeconds, DEFAULT_TOKEN_EXPIRE_HOURS * 3600L);

        redisTemplate.opsForValue().set(TOKEN_KEY, token, redisExpireSeconds, TimeUnit.SECONDS);
        log.debug("Token已存储到Redis，apiKey: {}, 过期时间: {}秒", apiKey, redisExpireSeconds);
    }
}