package com.fs.erp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.fs.erp.dto.GetInitTokenRequestDTO;
import com.fs.erp.dto.GetInitTokenResponseDTO;
import com.fs.erp.dto.RefreshTokenRequestDTO;
import com.fs.erp.http.JstErpHttpService;
import com.fs.erp.utils.SignUtil;
import org.apache.http.protocol.HttpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;

@Service
public class JstTokenService {
    private static final Logger log = LoggerFactory.getLogger(JstTokenService.class);

    private static final String JST_TOKEN_KEY = "jst:access_token";
    private static final String JST_REFRESH_TOKEN_KEY = "jst:refresh_token";
    private static final String JST_TOKEN_EXPIRE_KEY = "jst:token_expire";

    @Value("${jst.app_key}")
    private String appKey;

    @Value("${jst.app_secret}")
    private String appSecret;

    @Value("${jst.authorization_code}")
    private String authorizationCode;

    @Autowired
    private JstErpHttpService httpService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 获取访问令牌，如果缓存中存在有效令牌则直接返回，否则重新获取
     * @return 有效的访问令牌
     */
    public String getAccessToken() {
        String accessToken = redisTemplate.opsForValue().get(JST_TOKEN_KEY);

        if (accessToken != null && !accessToken.isEmpty()) {
            log.debug("从缓存中获取到有效的access_token");
            return accessToken;
        }

        log.info("缓存中没有有效的access_token，重新获取");
        return refreshAccessToken();
    }

    /**
     * 强制刷新访问令牌
     * @return 新的访问令牌
     */
    public String refreshAccessToken() {
        // 检查是否有刷新令牌
        String refreshToken = redisTemplate.opsForValue().get(JST_REFRESH_TOKEN_KEY);

        if (refreshToken != null && !refreshToken.isEmpty()) {
            log.info("使用refresh_token刷新令牌");
            try {
                return refreshTokenWithApi(refreshToken);
            } catch (Exception e) {
                log.error("使用refresh_token刷新失败，尝试重新初始化", e);
            }
        }

        // 如果没有刷新令牌或刷新失败，则重新初始化
        return initToken();
    }

    /**
     * 初始化令牌
     * @return 新的访问令牌
     */
    private String initToken() {
        GetInitTokenRequestDTO requestDTO = new GetInitTokenRequestDTO();
        requestDTO.setApp_key(appKey);
        requestDTO.setGrant_type("authorization_code");
        requestDTO.setCharset("utf-8");
        requestDTO.setCode(authorizationCode);
        requestDTO.setTimestamp(String.valueOf(System.currentTimeMillis() / 1000));

        // 计算签名
        requestDTO.setSign(SignUtil.getSignNew(appSecret, requestDTO));

        log.info("初始化token请求参数: {}", JSON.toJSONString(requestDTO));

        GetInitTokenResponseDTO response = httpService.getInitToken(requestDTO);
        if (response == null || response.getAccess_token() == null) {
            throw new RuntimeException("初始化token失败: " + (response != null ? JSON.toJSONString(response) : "响应为空"));
        }

        // 缓存token信息
        storeTokenInfo(response);

        log.info("初始化token成功，token将在{}秒后过期", response.getExpires_in());
        return response.getAccess_token();
    }

    /**
     * 使用刷新令牌API刷新令牌
     * @param refreshToken 刷新令牌
     * @return 新的访问令牌
     */
    private String refreshTokenWithApi(String refreshToken) {
        RefreshTokenRequestDTO requestDTO = new RefreshTokenRequestDTO();
        requestDTO.setApp_key(appKey);
        requestDTO.setGrant_type("refresh_token");
        requestDTO.setCharset("utf-8");
        requestDTO.setRefresh_token(refreshToken);
        requestDTO.setTimestamp(String.valueOf(System.currentTimeMillis() / 1000));

        // 计算签名
        requestDTO.setSign(SignUtil.getSignNew(appSecret, requestDTO));

        log.info("刷新token请求参数: {}", JSON.toJSONString(requestDTO));

        GetInitTokenResponseDTO response = httpService.refreshToken(requestDTO);
        if (response == null || response.getAccess_token() == null) {
            throw new RuntimeException("刷新token失败: " + (response != null ? JSON.toJSONString(response) : "响应为空"));
        }

        // 缓存token信息
        storeTokenInfo(response);

        log.info("刷新token成功，token将在{}秒后过期", response.getExpires_in());
        return response.getAccess_token();
    }

    /**
     * 将令牌信息存储到Redis
     * @param tokenInfo 令牌信息
     */
    private void storeTokenInfo(GetInitTokenResponseDTO tokenInfo) {
        // 默认30天过期时间，转换为秒
        long expiresIn = tokenInfo.getExpires_in() != null ?
                          Long.parseLong(String.valueOf(tokenInfo.getExpires_in())) : 30 * 24 * 60 * 60;

        // 存储令牌，设置比官方少一天的过期时间，以确保安全
        redisTemplate.opsForValue().set(JST_TOKEN_KEY, tokenInfo.getAccess_token(), expiresIn - 86400, TimeUnit.SECONDS);

        // 存储刷新令牌，有效期与访问令牌相同
        if (tokenInfo.getRefresh_token() != null) {
            redisTemplate.opsForValue().set(JST_REFRESH_TOKEN_KEY, tokenInfo.getRefresh_token(), expiresIn, TimeUnit.SECONDS);
        }

        // 存储过期时间戳
        long expireAt = System.currentTimeMillis() + (expiresIn * 1000);
        redisTemplate.opsForValue().set(JST_TOKEN_EXPIRE_KEY, String.valueOf(expireAt), expiresIn, TimeUnit.SECONDS);
    }

    /**
     * 定时任务，每20天刷新一次令牌
     * 根据文档说明，刷新token必须在有效期小于15天时进行，所以这里设置20天刷新一次
     */
    @Scheduled(fixedRate = 20 * 24 * 60 * 60 * 1000)
    public void scheduleTokenRefresh() {
        log.info("定时任务：刷新聚水潭API令牌");
        try {
            refreshAccessToken();
        } catch (Exception e) {
            log.error("定时刷新令牌失败", e);
        }
    }

    /**
     * 启动时初始化令牌
     */
    @PostConstruct
    public void init() {
        try {
            // 检查Redis中是否已有有效token
            String accessToken = redisTemplate.opsForValue().get(JST_TOKEN_KEY);
            if (accessToken == null || accessToken.isEmpty()) {
                log.info("系统启动，初始化聚水潭API令牌");
                initToken();
            } else {
                log.info("系统启动，发现缓存中已有有效的聚水潭API令牌");
            }
        } catch (Exception e) {
            log.error("初始化聚水潭API令牌失败", e);
        }
    }
}
