package com.ruoyi.web.modules.screen.mallstats;

import java.net.URI;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.web.modules.screen.domain.vo.MallStatsOverviewVo;

/**
 * 商城 Open Stats HTTP 客户端：单次 overview + 本地 Redis 缓存。
 * <p>
 * 配置 {@code bigscreen.mall-stats.*}；未启用或失败返回 {@code null}，由调用方降级为空态。
 */
@Service
public class MallStatsOpenApiClientImpl implements MallStatsOpenApiClient
{
    private static final Logger log = LoggerFactory.getLogger(MallStatsOpenApiClientImpl.class);

    /** Redis 缓存键前缀，后缀为 statYear */
    private static final String CACHE_KEY_PREFIX = "bigscreen:mallstats:overview:v2:";

    private final ObjectMapper objectMapper = new ObjectMapper();

    private final MallStatsOpenProperties properties;

    private final RestTemplate restTemplate;

    private final MallStatsOpenTokenSupport tokenSupport;

    private final RedisCache redisCache;

    public MallStatsOpenApiClientImpl(
            MallStatsOpenProperties properties,
            @Qualifier("mallStatsRestTemplate") RestTemplate restTemplate,
            MallStatsOpenTokenSupport tokenSupport,
            RedisCache redisCache)
    {
        this.properties = properties;
        this.restTemplate = restTemplate;
        this.tokenSupport = tokenSupport;
        this.redisCache = redisCache;
    }

    @Override
    public MallStatsOverviewVo fetchOverview(int statYear)
    {
        if (!properties.isEnabled() || StringUtils.isEmpty(properties.getBaseUrl()))
        {
            return null;
        }
        String cacheKey = CACHE_KEY_PREFIX + statYear;
        MallStatsOverviewVo cached = readCachedOverview(cacheKey);
        if (cached != null)
        {
            return cached;
        }
        try
        {
            MallStatsOverviewVo overview = requestOverview(statYear);
            if (overview != null)
            {
                int ttl = Math.max(1, properties.getCacheTtlMinutes());
                writeCachedOverview(cacheKey, overview, ttl);
            }
            return overview;
        }
        catch (Exception ex)
        {
            log.warn("拉取商城统计 overview 失败 statYear={}: {}", statYear, ex.getMessage());
            return null;
        }
    }

    private MallStatsOverviewVo readCachedOverview(String cacheKey)
    {
        try
        {
            String json = redisCache.getCacheObject(cacheKey);
            if (StringUtils.isEmpty(json))
            {
                return null;
            }
            return objectMapper.readValue(json, MallStatsOverviewVo.class);
        }
        catch (Exception ex)
        {
            log.warn("商城统计缓存反序列化失败，将重新拉取 {}: {}", cacheKey, ex.getMessage());
            redisCache.deleteObject(cacheKey);
            return null;
        }
    }

    private void writeCachedOverview(String cacheKey, MallStatsOverviewVo overview, int ttlMinutes)
    {
        try
        {
            String json = objectMapper.writeValueAsString(overview);
            redisCache.setCacheObject(cacheKey, json, ttlMinutes, TimeUnit.MINUTES);
        }
        catch (Exception ex)
        {
            log.warn("商城统计缓存写入失败 {}: {}", cacheKey, ex.getMessage());
        }
    }

    /** GET /api/open/stats/overview?statYear=；Header X-Open-Token 为 AES 加密 UUID */
    private MallStatsOverviewVo requestOverview(int statYear) throws Exception
    {
        String base = trimTrailingSlash(properties.getBaseUrl());
        URI uri = UriComponentsBuilder.fromHttpUrl(base + "/api/open/stats/overview")
                .queryParam("statYear", statYear)
                .build(true)
                .toUri();

        HttpHeaders headers = new HttpHeaders();
        headers.set(MallStatsOpenTokenSupport.TOKEN_HEADER, tokenSupport.createToken(properties));
        HttpEntity<Void> entity = new HttpEntity<>(headers);

        ResponseEntity<String> response;
        try
        {
            response = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
        }
        catch (RestClientException ex)
        {
            throw new IllegalStateException(ex.getMessage(), ex);
        }
        if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null)
        {
            throw new IllegalStateException("HTTP " + response.getStatusCodeValue());
        }
        JsonNode root = objectMapper.readTree(response.getBody());
        if (!isSuccessCode(root))
        {
            String msg = root.has("msg") ? root.get("msg").asText() : "unknown";
            throw new IllegalStateException("商城接口 code 异常: " + msg);
        }
        JsonNode data = root.get("data");
        if (data == null || data.isNull())
        {
            return emptyOverview();
        }
        return objectMapper.treeToValue(data, MallStatsOverviewVo.class);
    }

    private static boolean isSuccessCode(JsonNode root)
    {
        if (root == null || !root.has("code"))
        {
            return false;
        }
        JsonNode codeNode = root.get("code");
        if (codeNode.isInt())
        {
            return codeNode.intValue() == 200;
        }
        return "200".equals(codeNode.asText());
    }

    private static MallStatsOverviewVo emptyOverview()
    {
        return new MallStatsOverviewVo();
    }

    private static String trimTrailingSlash(String url)
    {
        String trimmed = url.trim();
        while (trimmed.endsWith("/"))
        {
            trimmed = trimmed.substring(0, trimmed.length() - 1);
        }
        return trimmed;
    }
}
