package com.njcn.gather.steady.checksquare.component;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.steady.datavie.config.SteadyInfluxDbProperties;
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.Set;

/**
 * 数据校验 InfluxDB 查询组件。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SteadyChecksquareInfluxQueryComponent {

    private static final DateTimeFormatter INFLUX_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private final SteadyInfluxDbProperties properties;

    public Set<LocalDateTime> queryExistingSlots(SteadyTrendResolvedFieldBO field, LocalDateTime startTime,
                                                 LocalDateTime endTime, int intervalMinutes) {
        validateConfig();
        String query = buildChecksquareQuery(field, startTime, endTime);
        long startMillis = System.currentTimeMillis();
        log.info("数据校验 InfluxDB 查询开始，measurement={}，field={}，lineId={}，phase={}，statType={}，query={}",
                field.getMeasurement(), field.getField(), field.getLineId(), field.getPhase(), field.getStatType(), query);
        try {
            String body = executeQuery(query);
            Set<LocalDateTime> slots = parseExistingSlots(body, intervalMinutes);
            log.info("数据校验 InfluxDB 查询结束，slotCount={}，costMs={}", slots.size(), System.currentTimeMillis() - startMillis);
            return slots;
        } catch (RuntimeException ex) {
            log.warn("数据校验 InfluxDB 查询异常，costMs={}，error={}", System.currentTimeMillis() - startMillis, ex.getMessage());
            throw ex;
        }
    }

    public String buildChecksquareQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime) {
        StringBuilder sql = new StringBuilder();
        sql.append("SELECT \"").append(field.getField()).append("\" AS \"value\"");
        sql.append(" FROM \"").append(field.getMeasurement()).append("\"");
        sql.append(" WHERE time >= '").append(INFLUX_TIME_FORMATTER.format(startTime)).append("'");
        sql.append(" AND time <= '").append(INFLUX_TIME_FORMATTER.format(endTime)).append("'");
        sql.append(" AND \"line_id\" = '").append(escapeTagValue(field.getLineId())).append("'");
        sql.append(" AND \"phasic_type\" = '").append(escapeTagValue(field.getPhase())).append("'");
        if (hasValueTypeTag(field.getMeasurement())) {
            sql.append(" AND \"value_type\" = '").append(resolveValueType(field.getStatType())).append("'");
        }
        sql.append(" ORDER BY time ASC");
        return sql.toString();
    }

    private Set<LocalDateTime> parseExistingSlots(String body, int intervalMinutes) {
        try {
            JsonNode root = OBJECT_MAPPER.readTree(body);
            JsonNode values = root.path("results").path(0).path("series").path(0).path("values");
            Set<LocalDateTime> result = new HashSet<LocalDateTime>();
            if (!values.isArray()) {
                return result;
            }
            for (JsonNode value : values) {
                if (value.size() < 2 || value.get(1).isNull()) {
                    continue;
                }
                LocalDateTime time = parseInfluxTime(value.get(0).asText());
                if (time != null) {
                    result.add(alignToPreviousSlot(time, intervalMinutes));
                }
            }
            return result;
        } catch (IOException ex) {
            throw fail("InfluxDB 返回结果解析失败：" + ex.getMessage());
        }
    }

    private LocalDateTime alignToPreviousSlot(LocalDateTime time, int intervalMinutes) {
        LocalDateTime minuteFloor = time.withSecond(0).withNano(0);
        int minuteOfDay = minuteFloor.getHour() * 60 + minuteFloor.getMinute();
        int remainder = minuteOfDay % intervalMinutes;
        return minuteFloor.minusMinutes(remainder);
    }

    private LocalDateTime parseInfluxTime(String value) {
        try {
            return OffsetDateTime.parse(value).withOffsetSameInstant(ZoneOffset.UTC).toLocalDateTime();
        } catch (RuntimeException ex) {
            return null;
        }
    }

    private String executeQuery(String query) {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(buildQueryUrl(query));
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(properties.getConnectTimeoutMs());
            connection.setReadTimeout(properties.getReadTimeoutMs());
            int status = connection.getResponseCode();
            InputStream stream = status >= 200 && status < 300 ? connection.getInputStream() : connection.getErrorStream();
            String body = readBody(stream);
            if (status < 200 || status >= 300) {
                throw fail("InfluxDB 查询失败：" + body);
            }
            return body;
        } catch (IOException ex) {
            throw fail("InfluxDB 查询异常：" + ex.getMessage());
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    private String buildQueryUrl(String query) throws IOException {
        StringBuilder url = new StringBuilder(trimRightSlash(properties.getUrl())).append("/query?");
        url.append("db=").append(encode(properties.getDatabase()));
        if (properties.getUsername() != null && !properties.getUsername().trim().isEmpty()) {
            url.append("&u=").append(encode(properties.getUsername().trim()));
        }
        if (properties.getPassword() != null && !properties.getPassword().trim().isEmpty()) {
            url.append("&p=").append(encode(properties.getPassword()));
        }
        url.append("&q=").append(encode(query));
        return url.toString();
    }

    private void validateConfig() {
        if (properties.getUrl() == null || properties.getUrl().trim().isEmpty()) {
            throw fail("InfluxDB 地址未配置");
        }
        if (properties.getDatabase() == null || properties.getDatabase().trim().isEmpty()) {
            throw fail("InfluxDB database 未配置");
        }
    }

    private String readBody(InputStream stream) throws IOException {
        if (stream == null) {
            return "";
        }
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
        StringBuilder body = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            body.append(line);
        }
        return body.toString();
    }

    private String escapeTagValue(String value) {
        return value == null ? "" : value.replace("\\", "\\\\").replace("'", "\\'");
    }

    private String resolveValueType(String statType) {
        if (statType == null || statType.trim().isEmpty()) {
            return "AVG";
        }
        return statType.trim().toUpperCase();
    }

    private boolean hasValueTypeTag(String measurement) {
        return !"data_flicker".equals(measurement) && !"data_fluc".equals(measurement) && !"data_plt".equals(measurement);
    }

    private String trimRightSlash(String value) {
        String text = value.trim();
        while (text.endsWith("/")) {
            text = text.substring(0, text.length() - 1);
        }
        return text;
    }

    private String encode(String value) throws IOException {
        return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
    }

    private BusinessException fail(String message) {
        return new BusinessException(CommonResponseEnum.FAIL, message);
    }
}
