package com.usky.demo.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.usky.common.core.bean.ApiResult;
import com.usky.common.core.exception.BusinessException;
import com.usky.common.mybatis.core.AbstractCrudService;
import com.usky.demo.domain.*;
import com.usky.demo.mapper.QueryInfluxdbDataMapper;
import com.usky.demo.mapper.QueryTdengineDataMapper;
import com.usky.demo.service.DmpDeviceService;
import com.usky.demo.service.DmpProductService;
import com.usky.demo.service.QueryInfluxdbDataService;
import com.usky.demo.service.QueryTdengineDataService;
import com.usky.demo.service.utils.TsdbUtils;
import com.usky.demo.service.vo.DeviceMapVO;
import com.usky.demo.service.vo.ProductMapVO;
import com.usky.demo.service.vo.SuperTableVO;
import org.apache.commons.lang3.StringUtils;
import org.influxdb.dto.QueryResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.sql.DataSource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author ya
 * @since 2024-07-29
 */
@Service
@DS("taos")
public class QueryTdengineDataServiceImpl extends AbstractCrudService<QueryTdengineDataMapper, QueryTdengineData> implements QueryTdengineDataService {

    private static final Pattern SQL_IDENTIFIER_PATTERN = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");

    private static final Pattern DEVICE_UUID_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+$");

    private static final int IMPORT_BATCH_SIZE = 500;

    private static final DateTimeFormatter TS_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    private static final List<String> TS_HEADER_ALIASES = Arrays.asList("ts", "timestamp", "time");

    private static final List<String> P_HEADER_ALIASES = Arrays.asList("p", "power", "active_power", "value");

    @Autowired
    private TsdbUtils tsdbUtils;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private QueryTdengineDataMapper queryTdengineDataMapper;

    @Override
    public List<LastInnerResultVO> tdengineLast(LastInnerQueryVO requestVO){
        List<LastInnerResultVO> list = new ArrayList<>();
        List<String> deviceUUIds = requestVO.getDeviceuuid();
        List<String> metrics = requestVO.getMetrics();


        // 遍历设备表集合，自动忽略表中不存在的字段，用有效字段构建查询，避免错误
        for (int i = 0; i < deviceUUIds.size(); i++) {

            String tablename = "_"+deviceUUIds.get(i);

            try (Connection connection = dataSource.getConnection()) {
                String sql = "describe " + tablename;

                try (PreparedStatement itemStmt = connection.prepareStatement(sql)) {
                    try (ResultSet resultSet = itemStmt.executeQuery()) {
                        String tagStr = "";
                        List<String> taglist = new ArrayList<>();
                        List<String> fieldKeys = new ArrayList<>();
                        while (resultSet.next()) {
                            String note = resultSet.getString("note");
                            if("TAG".equals(note)){
                                tagStr = tagStr.concat(resultSet.getString("field")+",");
                                taglist.add(resultSet.getString("field"));
                            }else{
                                fieldKeys.add(resultSet.getString("field"));
                            }
                        }
                        resultSet.close();

                        // 过滤有效字段
                        List<String> filterValidFields = new ArrayList<>();
                        if(CollectionUtils.isNotEmpty(metrics)){
                            filterValidFields = metrics.stream().filter(item -> fieldKeys.contains(item)).collect(Collectors.toList());
                        }else{
                            filterValidFields = fieldKeys;
                        }
                        String filterValidFieldStr = filterValidFields.stream().collect(Collectors.joining(","));
                        if(CollectionUtils.isEmpty(filterValidFields)){
                            throw new BusinessException(tablename+"没有可用属性，请添加");
                        }

                        String query = "SELECT ts,"+tagStr+filterValidFieldStr+" FROM "+tablename+" order by ts desc limit 1";
                        try (PreparedStatement itemStmt1 = connection.prepareStatement(query)) {
                            try (ResultSet metricList = itemStmt1.executeQuery()) {
                                while (metricList.next()) {
                                    LastInnerResultVO resultVO = new LastInnerResultVO();
                                    resultVO.setDeviceuuid(deviceUUIds.get(i));
                                    Map<String, String> tag = new HashMap<String,String>();
                                    for(int k=0;k<taglist.size();k++){
                                        tag.put(taglist.get(k),metricList.getString(taglist.get(k)));
                                    }
                                    resultVO.setTags(tag);

                                    Map<String, Object> field = new HashMap<String,Object>();
                                    field.put("realtime",metricList.getTimestamp("ts").getTime());
                                    for(int k =0;k<filterValidFields.size();k++){
                                        field.put(filterValidFields.get(k),metricList.getString(filterValidFields.get(k)));
                                    }
                                    resultVO.setMetrics(field);
                                    list.add(resultVO);
                                }
                            }
                        }

                    }
                }

            } catch (SQLException e) {
                // 判断TDengine专属错误码 0x2603 = 表不存在
                if (e.getMessage().contains("0x2603") || e.getMessage().contains("Table does not exist")) {
                    System.out.println("表" + tablename + "不存在，跳过");
                    log.error("表" + tablename + "不存在，跳过");
                } else {
                    // 其他异常正常打印堆栈
                    e.printStackTrace();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return list;
    }

    @Override
    public List<HistorysInnerResultVO> tdengineHistory(HistorysInnerRequestVO requestVO){
        List<HistorysInnerResultVO> list = new ArrayList<>();
        String startTime = requestVO.getStartTime();
        String endTime = requestVO.getEndTime();
        List<String> deviceUUIds = requestVO.getDeviceuuid();
        List<String> metrics = requestVO.getMetrics();

        // 遍历设备表集合，自动忽略表中不存在的字段，用有效字段构建查询，避免错误
        for (int i = 0; i < deviceUUIds.size(); i++) {

            String tablename = "_"+deviceUUIds.get(i);

            try (Connection connection = dataSource.getConnection()) {
                String sql = "describe " + tablename;

                try (PreparedStatement itemStmt = connection.prepareStatement(sql)) {
                    try (ResultSet resultSet = itemStmt.executeQuery()) {
                        String tagStr = "";
                        List<String> taglist = new ArrayList<>();
                        List<String> fieldKeys = new ArrayList<>();
                        while (resultSet.next()) {
                            String note = resultSet.getString("note");
                            if("TAG".equals(note)){
                                tagStr = tagStr.concat(resultSet.getString("field")+",");
                                taglist.add(resultSet.getString("field"));
                            }else{
                                fieldKeys.add(resultSet.getString("field"));
                            }
                        }
                        resultSet.close();

                        // 过滤有效字段
                        List<String> filterValidFields = new ArrayList<>();
                        if(CollectionUtils.isNotEmpty(metrics)){
                            filterValidFields = metrics.stream().filter(item -> fieldKeys.contains(item)).collect(Collectors.toList());
                        }else{
                            filterValidFields = fieldKeys;
                        }
                        String filterValidFieldStr = filterValidFields.stream().collect(Collectors.joining(","));
                        if(CollectionUtils.isEmpty(filterValidFields)){
                            throw new BusinessException(tablename+"没有可用属性，请添加");
                        }

                        HistorysInnerResultVO resultVO = new HistorysInnerResultVO();
                        resultVO.setDeviceuuid(deviceUUIds.get(i));

                        String query = "SELECT ts,"+tagStr+filterValidFieldStr+" FROM "+tablename+" where ts >= '"+startTime+"' and ts <= '"+endTime+"'";
                        try (PreparedStatement itemStmt1 = connection.prepareStatement(query)) {
                            try (ResultSet resultSet1 = itemStmt1.executeQuery()) {
                                int count = resultSet1.getMetaData().getColumnCount();
                                List<Map<String, Object>> resultList = new ArrayList<>();
                                while (resultSet1.next()) {
                                    Map<String, Object> map = new HashMap<>();
                                    for (int k = 1; k <= count; k++) {
                                        String key = resultSet1.getMetaData().getColumnName(k);
                                        Object value = resultSet1.getString(k);
                                        map.put(key, value);
                                    }
                                    resultList.add(map);
                                }
                                resultSet1.close();

                                if(CollectionUtils.isNotEmpty(resultList)){
                                    Map<String, String> tag = new HashMap<>();
                                    for (int k = 0; k < taglist.size(); k++) {
                                        tag.put(taglist.get(k), resultList.get(0).get(taglist.get(k)).toString());
                                    }
                                    resultVO.setTags(tag);

                                    List<MetricVO> metricList = new ArrayList<>();
                                    for (int h = 0; h < filterValidFields.size(); h++) {
                                        if(!"ts".equals(filterValidFields.get(h))){
                                            MetricVO metricVO = new MetricVO();
                                            metricVO.setMetric(filterValidFields.get(h));
                                            List<Map<String,Object>> metircItems = new ArrayList<>();
                                            for (int k = 0; k < resultList.size(); k++) {
                                                String field = filterValidFields.get(h);
                                                Map<String,Object> map = new HashMap<>();
                                                map.put("timestamp",resultList.get(k).get("ts"));
                                                map.put("value",resultList.get(k).get(field));
                                                metircItems.add(map);
                                            }
                                            metricVO.setMetricItems(metircItems);
                                            metricList.add(metricVO);
                                        }

                                    }
                                    resultVO.setMetrics(metricList);
                                    list.add(resultVO);
                                }
                            }
                        }

                    }
                }

            } catch (SQLException e) {
                // 判断TDengine专属错误码 0x2603 = 表不存在
                if (e.getMessage().contains("0x2603") || e.getMessage().contains("Table does not exist")) {
                    System.out.println("表" + tablename + "不存在，跳过");
                    log.error("表" + tablename + "不存在，跳过");
                } else {
                    // 其他异常正常打印堆栈
                    e.printStackTrace();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return list;

    }

    @Override
    public List<Map<String, Object>> getAllDeviceRealTime(){
//        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
//        List<DeviceRealDataVO> realData =  queryTdengineDataMapper.getAllDeviceRealTime();
//        return realData.stream().map(item -> {
//            Map<String, Object> map = new HashMap<>();
//            map.put("device_uuid", item.getDeviceUuid());
//            map.put("device_id", item.getDeviceId());
//            try {
//                map.put("realtime", sdf.parse(item.getRealtime().toString()).getTime());
//            } catch (ParseException e) {
//                throw new RuntimeException(e);
//            }
//            return map;
//        }).collect(Collectors.toList());
        List<Map<String, Object>> resultList = new ArrayList<>();

        try (Connection connection = dataSource.getConnection()) {

            String query = "select device_uuid,realtime,device_id from datarealtime";
            try (PreparedStatement itemStmt = connection.prepareStatement(query)) {
                try (ResultSet resultSet1 = itemStmt.executeQuery()){
                    while (resultSet1.next()) {
                        Map<String, Object> map = new HashMap<>();
                        map.put("device_uuid", resultSet1.getString("device_uuid"));
                        map.put("device_id", resultSet1.getString("device_id"));
                        map.put("realtime", resultSet1.getTimestamp("realtime").getTime());

                        resultList.add(map);
                    }
                }

            }


        } catch (Exception e) {
            e.printStackTrace();
        }

        return resultList;
    }

    @Override
    public void createSuperTableAndColumn(SuperTableDTO superTableDTO){
        queryTdengineDataMapper.createSuperTableAndColumn(superTableDTO);
    }

    @Override
    public void addSuperTableColumn(String superTableName, Fields fields) {
        queryTdengineDataMapper.addSuperTableColumn(superTableName, fields);
    }

    @Override
    public void dropSuperTableColumn(String superTableName, Fields fields) {
        queryTdengineDataMapper.dropSuperTableColumn(superTableName, fields);
    }

    @Override
    public List<SuperTableDescribeVO> describeSuperOrSubTable(String tableName) {
        try {
            return queryTdengineDataMapper.describeSuperOrSubTable(tableName);
        } catch (Exception e) {
            log.warn("Error describing super or sub table. Database: {}, Table: {}, Error: {}"+tableName+", "+e.getMessage());
            return Collections.emptyList();
        }
    }

    @Override
    public void addSuperTableTag(String superTableName, Fields fields) {
        queryTdengineDataMapper.addSuperTableTag(superTableName, fields);
    }

    @Override
    public void dropSuperTableTag(String superTableName, Fields fields) {
        queryTdengineDataMapper.dropSuperTableTag(superTableName, fields);
    }

    @Override
    public EnergyItemSumResultVO sumEnergyItemDiff(EnergyItemSumQueryVO requestVO) {
        if (requestVO == null) {
            throw new BusinessException("查询参数不能为空");
        }
        String superTable = requestVO.getSuperTable();
        String identifier = requestVO.getIdentifier();
        String startTime = requestVO.getStartTime();
        String endTime = requestVO.getEndTime();
        if (StringUtils.isAnyBlank(superTable, identifier, startTime, endTime)) {
            throw new BusinessException("super_table、identifier、startTime、endTime 均不能为空");
        }
        assertSqlIdentifier(superTable, "super_table");
        assertSqlIdentifier(identifier, "identifier");

        String deviceFilter = buildDeviceUuidFilter(requestVO.getDeviceUuid());
        String sql = "SELECT SUM(diff) AS sum_diff FROM ("
                + " SELECT device_id, LAST(" + identifier + ") - FIRST(" + identifier + ") AS diff"
                + " FROM " + superTable
                + " WHERE ts >= '" + startTime + "' AND ts < '" + endTime + "'"
                + deviceFilter
                + " GROUP BY device_id"
                + ") AS sub_table_diff";

        EnergyItemSumResultVO result = new EnergyItemSumResultVO();
        result.setSumDiff(BigDecimal.ZERO);

        try (Connection connection = dataSource.getConnection();
             PreparedStatement stmt = connection.prepareStatement(sql);
             ResultSet rs = stmt.executeQuery()) {
            if (rs.next()) {
                Object value = rs.getObject("sum_diff");
                if (value != null) {
                    result.setSumDiff(new BigDecimal(value.toString()));
                }
            }
        } catch (SQLException e) {
            log.error("能耗分项汇总查询失败, sql=" + sql + ", " + e.getMessage());
        }
        return result;
    }

    @Override
    public EnergyItemTrendResultVO sumEnergyItemTrend(EnergyItemTrendQueryVO requestVO) {
        if (requestVO == null) {
            throw new BusinessException("查询参数不能为空");
        }
        String superTable = requestVO.getSuperTable();
        String identifier = requestVO.getIdentifier();
        String startTime = requestVO.getStartTime();
        String endTime = requestVO.getEndTime();
        Integer dateType = requestVO.getDateType();
        if (StringUtils.isAnyBlank(superTable, identifier, startTime, endTime) || dateType == null) {
            throw new BusinessException("super_table、identifier、startTime、endTime、dateType 均不能为空");
        }
        assertSqlIdentifier(superTable, "super_table");
        assertSqlIdentifier(identifier, "identifier");
        String interval = resolveTrendInterval(dateType);

        String sql = "SELECT _wstart, SUM(diff) AS sum_diff FROM ("
                + " SELECT _wstart, device_id, LAST(" + identifier + ") - FIRST(" + identifier + ") AS diff"
                + " FROM " + superTable
                + " WHERE ts >= '" + startTime + "' AND ts < '" + endTime + "'"
                + " PARTITION BY device_id"
                + " INTERVAL(" + interval + ")"
                + ") AS trend_diff GROUP BY _wstart ORDER BY _wstart";

        EnergyItemTrendResultVO result = new EnergyItemTrendResultVO();
        List<EnergyItemTrendPointVO> points = new ArrayList<>();

        try (Connection connection = dataSource.getConnection();
             PreparedStatement stmt = connection.prepareStatement(sql);
             ResultSet rs = stmt.executeQuery()) {
            while (rs.next()) {
                Object wstart = rs.getObject("_wstart");
                if (wstart == null) {
                    continue;
                }
                Object sumDiff = rs.getObject("sum_diff");
                EnergyItemTrendPointVO point = new EnergyItemTrendPointVO();
                point.setTime(wstart.toString());
                if (sumDiff != null) {
                    point.setValue(new BigDecimal(sumDiff.toString()));
                } else {
                    point.setValue(BigDecimal.ZERO);
                }
                points.add(point);
            }
        } catch (SQLException e) {
            log.error("能耗分项趋势查询失败, sql=" + sql + ", " + e.getMessage());
        }

        result.setPoints(points);
        return result;
    }

    private static String resolveTrendInterval(Integer dateType) {
        if (dateType == 1) {
            return "1h";
        }
        if (dateType == 2) {
            return "1d";
        }
        if (dateType == 3) {
            return "1n";
        }
        throw new BusinessException("dateType 仅支持 1-小时、2-天、3-月");
    }

    private static String buildDeviceUuidFilter(List<String> deviceUuids) {
        if (deviceUuids == null || deviceUuids.isEmpty()) {
            return "";
        }
        String tbnames = deviceUuids.stream()
                .filter(StringUtils::isNotBlank)
                .map(uuid -> "'_" + uuid.trim().replace("'", "''") + "'")
                .collect(Collectors.joining(","));
        if (tbnames.isEmpty()) {
            return "";
        }
        return " AND tbname IN (" + tbnames + ")";
    }

    private static void assertSqlIdentifier(String name, String paramName) {
        if (!SQL_IDENTIFIER_PATTERN.matcher(name).matches()) {
            throw new BusinessException(paramName + " 格式非法，仅允许字母、数字与下划线，且不能以数字开头");
        }
    }

    @Override
    public ApiResult<DeviceCsvImportResultVO> importDeviceCsv(String deviceUuid, MultipartFile file) {
        if (StringUtils.isBlank(deviceUuid)) {
            log.error("设备UUID不能为空");
            return ApiResult.error("设备UUID不能为空");
        }
        if (!DEVICE_UUID_PATTERN.matcher(deviceUuid.trim()).matches()) {
            log.error("设备UUID格式无效, deviceUuid=" + deviceUuid);
            return ApiResult.error("设备UUID格式无效");
        }
        if (file == null || file.isEmpty()) {
            log.error("导入文件不能为空, deviceUuid=" + deviceUuid);
            return ApiResult.error("导入文件不能为空");
        }
        String originalFilename = file.getOriginalFilename();
        if (StringUtils.isBlank(originalFilename) || !originalFilename.toLowerCase(Locale.ROOT).endsWith(".csv")) {
            log.error("仅支持 CSV 文件, deviceUuid=" + deviceUuid + ", filename=" + originalFilename);
            return ApiResult.error("仅支持 CSV 文件");
        }

        String normalizedUuid = deviceUuid.trim();
        String tableName = buildDeviceSubTableName(normalizedUuid);
        try {
            List<TsdbCsvRow> rows = parseCsvRows(file);
            if (rows == null) {
                return ApiResult.error("CSV 解析失败");
            }
            if (rows.isEmpty()) {
                log.error("CSV 无有效数据行, deviceUuid=" + normalizedUuid + ", tableName=" + tableName);
                return ApiResult.error("CSV 无有效可导入数据行");
            }
            int importedRows = batchInsertRows(tableName, rows);
            if (importedRows == 0) {
                log.error("CSV 无有效可导入数据行, deviceUuid=" + normalizedUuid + ", tableName=" + tableName);
                return ApiResult.error("CSV 无有效可导入数据行");
            }

            DeviceCsvImportResultVO result = new DeviceCsvImportResultVO();
            result.setDeviceUuid(normalizedUuid);
            result.setTableName(tableName);
            result.setImportedRows(importedRows);
            return ApiResult.success(result);
        } catch (IOException ex) {
            log.error("解析 CSV 文件失败, deviceUuid=" + normalizedUuid + ", tableName=" + tableName + ", error=" + ex.getMessage());
            return ApiResult.error("解析 CSV 文件失败: " + ex.getMessage());
        } catch (SQLException ex) {
            log.error("时序数据导入失败, deviceUuid=" + normalizedUuid + ", tableName=" + tableName + ", error=" + ex.getMessage());
            return ApiResult.error("时序数据导入失败: " + ex.getMessage());
        }
    }

    private List<TsdbCsvRow> parseCsvRows(MultipartFile file) throws IOException {
        List<TsdbCsvRow> rows = new ArrayList<>();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
            String line;
            int tsIndex = 0;
            int pIndex = 1;
            boolean headerResolved = false;
            int lineNo = 0;
            while ((line = reader.readLine()) != null) {
                lineNo++;
                line = line.trim();
                if (line.isEmpty()) {
                    continue;
                }
                String[] parts = splitCsvLine(line);
                if (!headerResolved) {
                    if (isHeaderRow(parts)) {
                        tsIndex = findColumnIndex(parts, TS_HEADER_ALIASES, "ts");
                        if (tsIndex < 0) {
                            return null;
                        }
                        pIndex = findColumnIndex(parts, P_HEADER_ALIASES, "p");
                        if (pIndex < 0) {
                            return null;
                        }
                        headerResolved = true;
                        continue;
                    }
                    headerResolved = true;
                }
                if (parts.length <= Math.max(tsIndex, pIndex)) {
                    log.error("CSV 第 " + lineNo + " 行列数不足，已跳过");
                    continue;
                }
                String tsRaw = parts[tsIndex];
                String pRaw = parts[pIndex];
                if (StringUtils.isBlank(tsRaw) || StringUtils.isBlank(pRaw)) {
                    log.error("CSV 第 " + lineNo + " 行 ts 或 p 存在空值，已跳过");
                    continue;
                }
                Long tsMillis = parseTimestamp(tsRaw, lineNo);
                if (tsMillis == null) {
                    continue;
                }
                Double pValue = parseMetricValue(pRaw, lineNo);
                if (pValue == null) {
                    continue;
                }
                TsdbCsvRow row = new TsdbCsvRow(lineNo, tsMillis, pValue);
                if (!isImportableRow(row)) {
                    continue;
                }
                rows.add(row);
            }
        }
        return rows;
    }

    private int batchInsertRows(String tableName, List<TsdbCsvRow> rows) throws SQLException {
        int importedRows = 0;
        try (Connection connection = dataSource.getConnection();
             Statement statement = connection.createStatement()) {
            for (int start = 0; start < rows.size(); start += IMPORT_BATCH_SIZE) {
                int end = Math.min(start + IMPORT_BATCH_SIZE, rows.size());
                StringBuilder sql = new StringBuilder("INSERT INTO ")
                        .append(tableName)
                        .append(" (ts, p) VALUES ");
                int batchCount = 0;
                for (int i = start; i < end; i++) {
                    TsdbCsvRow row = rows.get(i);
                    if (!isImportableRow(row)) {
                        continue;
                    }
                    if (batchCount > 0) {
                        sql.append(' ');
                    }
                    sql.append('(').append(row.tsMillis).append(',').append(row.pValue).append(')');
                    batchCount++;
                }
                if (batchCount == 0) {
                    continue;
                }
                statement.execute(sql.toString());
                importedRows += batchCount;
            }
        }
        return importedRows;
    }

    private boolean isImportableRow(TsdbCsvRow row) {
        if (row.tsMillis <= 0) {
            log.error("CSV 第 " + row.lineNo + " 行 ts 无效，已跳过");
            return false;
        }
        if (!Double.isFinite(row.pValue)) {
            log.error("CSV 第 " + row.lineNo + " 行 p 不是有效数值，已跳过");
            return false;
        }
        return true;
    }

    private static String[] splitCsvLine(String line) {
        return Arrays.stream(line.split(",", -1))
                .map(String::trim)
                .toArray(String[]::new);
    }

    private static boolean isHeaderRow(String[] parts) {
        for (String part : parts) {
            String normalized = part.trim().toLowerCase(Locale.ROOT);
            if (TS_HEADER_ALIASES.contains(normalized) || P_HEADER_ALIASES.contains(normalized)) {
                return true;
            }
        }
        return false;
    }

    private int findColumnIndex(String[] headers, List<String> aliases, String columnLabel) {
        for (int i = 0; i < headers.length; i++) {
            if (aliases.contains(headers[i].trim().toLowerCase(Locale.ROOT))) {
                return i;
            }
        }
        log.error("CSV 缺少 " + columnLabel + " 列");
        return -1;
    }

    private Long parseTimestamp(String rawValue, int lineNo) {
        String value = rawValue == null ? "" : rawValue.trim();
        if (value.isEmpty()) {
            log.error("CSV 第 " + lineNo + " 行 ts 为空，已跳过");
            return null;
        }
        if (value.matches("\\d+")) {
            long epoch = Long.parseLong(value);
            return value.length() <= 10 ? epoch * 1000L : epoch;
        }
        try {
            return LocalDateTime.parse(value, TS_FORMAT)
                    .atZone(ZoneId.systemDefault())
                    .toInstant()
                    .toEpochMilli();
        } catch (DateTimeParseException ignored) {
            // fall through
        }
        try {
            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(value).getTime();
        } catch (ParseException ex) {
            log.error("CSV 第 " + lineNo + " 行 ts 格式无效: " + value + "，已跳过");
            return null;
        }
    }

    private Double parseMetricValue(String rawValue, int lineNo) {
        String value = rawValue == null ? "" : rawValue.trim();
        if (value.isEmpty()) {
            log.error("CSV 第 " + lineNo + " 行 p 为空，已跳过");
            return null;
        }
        try {
            return new BigDecimal(value).doubleValue();
        } catch (NumberFormatException ex) {
            log.error("CSV 第 " + lineNo + " 行 p 不是有效数值: " + value + "，已跳过");
            return null;
        }
    }

    private static String buildDeviceSubTableName(String deviceUuid) {
        return "_" + deviceUuid;
    }

    private static final class TsdbCsvRow {
        private final int lineNo;
        private final long tsMillis;
        private final double pValue;

        private TsdbCsvRow(int lineNo, long tsMillis, double pValue) {
            this.lineNo = lineNo;
            this.tsMillis = tsMillis;
            this.pValue = pValue;
        }
    }
}
