package com.jeesharp.business.modules.setting.client;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jeesharp.business.modules.setting.config.QWeatherProperties;
import com.jeesharp.business.modules.setting.vo.MiniHomeWeatherVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Locale;
import java.util.Optional;
import java.util.zip.GZIPInputStream;

/**
 * 调用和风天气「实时天气」接口（{@code /v7/weather/now}），将结果映射为小程序壳层 {@link MiniHomeWeatherVO}。
 *
 * @author jeesharp
 * @since 2026-05-18
 * @see <a href="https://dev.qweather.com/docs/api/weather/weather-now/">QWeather 实时天气</a>
 */
@Slf4j
@Component
@AllArgsConstructor
public class QWeatherNowClient {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    private final QWeatherProperties properties;

    /**
     * 拉取实况并映射为两行文案；未启用、参数不全、接口非 200 或解析失败时返回 {@code null}。
     */
    public MiniHomeWeatherVO fetchNowOrNull() {
        if (!properties.isEnabled() || StrUtil.isBlank(properties.getKey()) || StrUtil.isBlank(properties.getLocation())) {
            return null;
        }
        String host = properties.getApiHost().trim();
        if (host.endsWith("/")) {
            host = host.substring(0, host.length() - 1);
        }
        String locEnc = URLEncoder.encode(properties.getLocation().trim(), StandardCharsets.UTF_8);
        String lang = StrUtil.blankToDefault(properties.getLang(), "zh");
        String unit = StrUtil.blankToDefault(properties.getUnit(), "m");
        String url = host + "/v7/weather/now?location=" + locEnc + "&lang=" + URLEncoder.encode(lang, StandardCharsets.UTF_8)
            + "&unit=" + URLEncoder.encode(unit, StandardCharsets.UTF_8);
        int timeout = Math.max(1, properties.getHttpTimeoutSeconds());
        try {
            HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(timeout))
                .build();
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .timeout(Duration.ofSeconds(timeout))
                .header("Authorization", "Bearer " + properties.getKey().trim())
                .GET()
                .build();
            HttpResponse<byte[]> resp = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
            if (resp.statusCode() != 200) {
                String errBody = readBodyUtf8(resp);
                log.warn("和风天气 HTTP 非 200: status={}, bodySnippet={}", resp.statusCode(), snippet(errBody));
                return null;
            }
            String json = readBodyUtf8(resp);
            JsonNode root = MAPPER.readTree(json);
            String code = root.path("code").asText("");
            if (!"200".equals(code)) {
                log.warn("和风天气业务码非 200: code={}, bodySnippet={}", code, snippet(json));
                return null;
            }
            JsonNode now = root.path("now");
            if (now.isMissingNode() || now.isNull()) {
                return null;
            }
            String text = now.path("text").asText("").trim();
            String temp = now.path("temp").asText("").trim();
            String windDir = now.path("windDir").asText("").trim();
            String windScale = now.path("windScale").asText("").trim();
            if (StrUtil.isBlank(text) && StrUtil.isBlank(temp)) {
                return null;
            }
            MiniHomeWeatherVO vo = new MiniHomeWeatherVO();
            String line1 = StrUtil.isBlank(temp) ? text : (StrUtil.isBlank(text) ? (temp + "℃") : (text + " " + temp + "℃"));
            vo.setLine1(line1);
            String label = StrUtil.blankToDefault(properties.getLocationLabel(), "").trim();
            String windPart = StrUtil.isAllBlank(windDir, windScale) ? ""
                : (windDir + (StrUtil.isBlank(windScale) ? "" : (" " + windScale + "级")));
            if (StrUtil.isNotBlank(windPart) && StrUtil.isNotBlank(label)) {
                vo.setLine2(windPart + " · " + label);
            } else if (StrUtil.isNotBlank(windPart)) {
                vo.setLine2(windPart);
            } else if (StrUtil.isNotBlank(label)) {
                vo.setLine2(label);
            }
            return vo;
        } catch (Exception ex) {
            log.warn("和风天气请求或解析异常: {}", ex.getMessage());
            return null;
        }
    }

    private static String snippet(String body) {
        if (body == null) {
            return "";
        }
        return body.length() > 200 ? body.substring(0, 200) + "…" : body;
    }

    /**
     * 和风返回体多为 Gzip；{@link HttpResponse.BodyHandlers#ofString()} 不会解压，会得到乱码。
     * 此处按 {@code Content-Encoding} 或 gzip 魔数 {@code 1f 8b} 解压后再按 UTF-8 解码。
     */
    private static String readBodyUtf8(HttpResponse<byte[]> resp) throws IOException {
        byte[] raw = resp.body();
        if (raw == null || raw.length == 0) {
            return "";
        }
        byte[] decoded = isGzipPayload(resp.headers(), raw) ? gunzip(raw) : raw;
        return new String(decoded, StandardCharsets.UTF_8);
    }

    private static boolean isGzipPayload(HttpHeaders headers, byte[] raw) {
        Optional<String> enc = headers.firstValue("Content-Encoding");
        if (enc.isPresent()) {
            String v = enc.get().toLowerCase(Locale.ROOT);
            if (v.contains("gzip")) {
                return true;
            }
        }
        return raw.length >= 2 && (raw[0] & 0xFF) == 0x1f && (raw[1] & 0xFF) == 0x8b;
    }

    private static byte[] gunzip(byte[] raw) throws IOException {
        try (GZIPInputStream gzin = new GZIPInputStream(new ByteArrayInputStream(raw))) {
            return gzin.readAllBytes();
        }
    }
}
