package com.evobms.project.vehicledata.service;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.evobms.project.battery.domain.ExtremeValues;
import com.evobms.project.battery.domain.SubsystemTemperature;
import com.evobms.project.battery.domain.SubsystemVoltage;
import com.evobms.project.battery.mapper.ExtremeValuesMapper;
import com.evobms.project.battery.service.ISubsystemTemperatureService;
import com.evobms.project.battery.service.ISubsystemVoltageService;
import com.evobms.project.bms.domain.BmsChargeDetail;
import com.evobms.project.bms.domain.BmsChargeTemperature;
import com.evobms.project.bms.domain.BmsRuntimeData;
import com.evobms.project.bms.domain.BmsSopData;
import com.evobms.project.bms.domain.BmsSystemStatus;
import com.evobms.project.bms.domain.BmsVoltageChannelData;
import com.evobms.project.bms.service.IBmsChargeDetailService;
import com.evobms.project.bms.service.IBmsChargeTemperatureService;
import com.evobms.project.bms.service.IBmsRuntimeDataService;
import com.evobms.project.bms.service.IBmsSopDataService;
import com.evobms.project.bms.service.IBmsSystemStatusService;
import com.evobms.project.bms.service.IBmsVoltageChannelDataService;
import com.evobms.project.vehicledata.domain.VehicleData;
import com.evobms.project.vehicledata.domain.VehicleLocation;
import com.evobms.project.vehicledata.dto.DeviceHistoryFlatRowDTO;
import com.evobms.project.vehicledata.mapper.VehicleLocationMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/** Merges multi-table BMS/vehicle data into flat history rows by vehicle_data timeline. */
@Component
public class DeviceRealtimeHistoryAssembler {

    private static final int PREFETCH_CAP_MAX = 20_000;
    private static final int PREFETCH_PER_VEHICLE = 40;
    private static final int PREFETCH_FLOOR = 128;
    /** 多探针/多通道表单页预取上限（pageSize=500 时约 8000） */
    private static final int MAP_PREFETCH_CAP_MAX = 20_000;
    /** 每页整车行对应多探针表预取系数（约 8 路温度 × 余量） */
    private static final int MAP_ROWS_PER_VEHICLE = 16;

    @Autowired
    private ExtremeValuesMapper extremeValuesMapper;
    @Autowired
    private IBmsSystemStatusService bmsSystemStatusService;
    @Autowired
    private IBmsChargeDetailService bmsChargeDetailService;
    @Autowired
    private IBmsRuntimeDataService bmsRuntimeDataService;
    @Autowired
    private IBmsSopDataService bmsSopDataService;
    @Autowired
    private IBmsChargeTemperatureService bmsChargeTemperatureService;
    @Autowired
    private IBmsVoltageChannelDataService bmsVoltageChannelDataService;
    @Autowired
    private ISubsystemVoltageService subsystemVoltageService;
    @Autowired
    private ISubsystemTemperatureService subsystemTemperatureService;
    @Autowired
    private VehicleLocationMapper vehicleLocationMapper;

    /**
     * @param configuredPageSize 本页请求的 pageSize（10/20/100/500），用于放大预取上限；实际行数可能更少（末页）
     */
    public List<DeviceHistoryFlatRowDTO> build(
            String deviceId,
            List<VehicleData> vehicleRows,
            SimpleDateFormat tf,
            int configuredPageSize) {
        List<DeviceHistoryFlatRowDTO> out = new ArrayList<>();
        if (vehicleRows == null || vehicleRows.isEmpty()) {
            return out;
        }
        Date tMin = null;
        Date tMax = null;
        for (VehicleData v : vehicleRows) {
            Date t = vehiclePointTime(v);
            if (t == null) {
                continue;
            }
            if (tMin == null || t.before(tMin)) {
                tMin = t;
            }
            if (tMax == null || t.after(tMax)) {
                tMax = t;
            }
        }
        if (tMax == null) {
            return out;
        }
        // 仅预取「当前页」时间窗内的辅助表数据，避免按整段 beginTime~endTime 拉数千行
        Date lo = tMin;
        Date hi = tMax;
        Date bootstrapBefore = tMin;
        int capRows = Math.max(vehicleRows.size(), Math.max(1, configuredPageSize));
        int cap = prefetchCap(capRows);
        int mapCap = mapPrefetchCap(capRows);
        PrefetchContext ctx = loadPrefetchParallel(deviceId, bootstrapBefore, lo, hi, cap, mapCap);

        ExtremeValues curEx = ctx.curEx;
        BmsSystemStatus curSs = ctx.curSs;
        BmsChargeDetail curCd = ctx.curCd;
        BmsRuntimeData curRt = ctx.curRt;
        BmsSopData curSop = ctx.curSop;
        VehicleLocation curLoc = ctx.curLoc;
        Map<Integer, BmsChargeTemperature> chargeByIdx = ctx.chargeByIdx;
        Map<String, BmsVoltageChannelData> voltByKey = ctx.voltByKey;
        Map<Integer, SubsystemVoltage> svByNo = ctx.svByNo;
        Map<Integer, SubsystemTemperature> stByNo = ctx.stByNo;

        List<ExtremeValues> extremeAsc = ctx.extremeAsc;
        List<BmsSystemStatus> ssAsc = ctx.ssAsc;
        List<BmsChargeDetail> cdAsc = ctx.cdAsc;
        List<BmsRuntimeData> rtAsc = ctx.rtAsc;
        List<BmsSopData> sopAsc = ctx.sopAsc;
        List<VehicleLocation> locAsc = ctx.locAsc;
        List<BmsChargeTemperature> chargeAsc = ctx.chargeAsc;
        List<BmsVoltageChannelData> voltAsc = ctx.voltAsc;
        List<SubsystemVoltage> svAsc = ctx.svAsc;
        List<SubsystemTemperature> stAsc = ctx.stAsc;

        List<Integer> orderAsc = sortedIndicesByPointTimeAsc(vehicleRows);

        int pEx = 0, pSs = 0, pCd = 0, pRt = 0, pSop = 0, pLoc = 0;
        int pCh = 0, pVc = 0, pSv = 0, pSt = 0;

        Map<Integer, DeviceHistoryFlatRowDTO> slot = new HashMap<>();

        for (int idx : orderAsc) {
            VehicleData vd = vehicleRows.get(idx);
            Date ti = vehiclePointTime(vd);
            if (ti == null) {
                continue;
            }

            while (pEx < extremeAsc.size() && extremeEffectiveTime(extremeAsc.get(pEx)).compareTo(ti) <= 0) {
                curEx = extremeAsc.get(pEx++);
            }
            while (pSs < ssAsc.size() && nullSafe(ssAsc.get(pSs).getCreateTime()).compareTo(ti) <= 0) {
                curSs = ssAsc.get(pSs++);
            }
            while (pCd < cdAsc.size() && nullSafe(cdAsc.get(pCd).getCreateTime()).compareTo(ti) <= 0) {
                curCd = cdAsc.get(pCd++);
            }
            while (pRt < rtAsc.size() && nullSafe(rtAsc.get(pRt).getCreateTime()).compareTo(ti) <= 0) {
                curRt = rtAsc.get(pRt++);
            }
            while (pSop < sopAsc.size() && nullSafe(sopAsc.get(pSop).getCreateTime()).compareTo(ti) <= 0) {
                curSop = sopAsc.get(pSop++);
            }
            while (pLoc < locAsc.size() && locationEffectiveTime(locAsc.get(pLoc)).compareTo(ti) <= 0) {
                curLoc = locAsc.get(pLoc++);
            }
            while (pCh < chargeAsc.size() && chargeTempEffectiveTime(chargeAsc.get(pCh)).compareTo(ti) <= 0) {
                BmsChargeTemperature row = chargeAsc.get(pCh++);
                if (row.getTempIndex() != null) {
                    chargeByIdx.put(row.getTempIndex(), row);
                }
            }
            while (pVc < voltAsc.size() && voltageChannelEffectiveTime(voltAsc.get(pVc)).compareTo(ti) <= 0) {
                BmsVoltageChannelData row = voltAsc.get(pVc++);
                voltByKey.put(voltChannelKey(row), row);
            }
            while (pSv < svAsc.size() && subsystemVoltageEffectiveTime(svAsc.get(pSv)).compareTo(ti) <= 0) {
                SubsystemVoltage row = svAsc.get(pSv++);
                if (row.getSubsystemNo() != null) {
                    svByNo.put(row.getSubsystemNo(), row);
                }
            }
            while (pSt < stAsc.size() && subsystemTemperatureEffectiveTime(stAsc.get(pSt)).compareTo(ti) <= 0) {
                SubsystemTemperature row = stAsc.get(pSt++);
                if (row.getSubsystemNo() != null) {
                    stByNo.put(row.getSubsystemNo(), row);
                }
            }

            DeviceHistoryFlatRowDTO dto = toFlat(deviceId, vd, tf, curEx, curSs, curCd, curRt, curSop, curLoc,
                    chargeByIdx, voltByKey, svByNo, stByNo);
            slot.put(idx, dto);
        }

        for (int i = 0; i < vehicleRows.size(); i++) {
            DeviceHistoryFlatRowDTO row = slot.get(i);
            if (row != null) {
                out.add(row);
            }
        }
        return out;
    }

    private PrefetchContext loadPrefetchParallel(
            String deviceId, Date bootstrapBefore, Date lo, Date hi, int cap, int mapCap) {
        CompletableFuture<ExtremeValues> curExF = CompletableFuture.supplyAsync(() -> bootstrapExtremeValues(deviceId, bootstrapBefore));
        CompletableFuture<BmsSystemStatus> curSsF = CompletableFuture.supplyAsync(() -> bootstrapSystemStatus(deviceId, bootstrapBefore));
        CompletableFuture<BmsChargeDetail> curCdF = CompletableFuture.supplyAsync(() -> bootstrapChargeDetail(deviceId, bootstrapBefore));
        CompletableFuture<BmsRuntimeData> curRtF = CompletableFuture.supplyAsync(() -> bootstrapRuntimeData(deviceId, bootstrapBefore));
        CompletableFuture<BmsSopData> curSopF = CompletableFuture.supplyAsync(() -> bootstrapSopData(deviceId, bootstrapBefore));
        CompletableFuture<VehicleLocation> curLocF = CompletableFuture.supplyAsync(() -> bootstrapVehicleLocation(deviceId, bootstrapBefore));
        CompletableFuture<Map<Integer, BmsChargeTemperature>> chargeMapF = CompletableFuture.supplyAsync(() -> bootstrapChargeTemps(deviceId, bootstrapBefore, mapCap));
        CompletableFuture<Map<String, BmsVoltageChannelData>> voltMapF = CompletableFuture.supplyAsync(() -> bootstrapVoltageChannels(deviceId, bootstrapBefore, mapCap));
        CompletableFuture<Map<Integer, SubsystemVoltage>> svMapF = CompletableFuture.supplyAsync(() -> bootstrapSubsystemVoltages(deviceId, bootstrapBefore, mapCap));
        CompletableFuture<Map<Integer, SubsystemTemperature>> stMapF = CompletableFuture.supplyAsync(() -> bootstrapSubsystemTemperatures(deviceId, bootstrapBefore, mapCap));

        CompletableFuture<List<ExtremeValues>> extremeAscF = CompletableFuture.supplyAsync(() -> loadExtremeValues(deviceId, lo, hi, cap));
        CompletableFuture<List<BmsSystemStatus>> ssAscF = CompletableFuture.supplyAsync(() -> loadSystemStatus(deviceId, lo, hi, cap));
        CompletableFuture<List<BmsChargeDetail>> cdAscF = CompletableFuture.supplyAsync(() -> loadChargeDetail(deviceId, lo, hi, cap));
        CompletableFuture<List<BmsRuntimeData>> rtAscF = CompletableFuture.supplyAsync(() -> loadRuntimeData(deviceId, lo, hi, cap));
        CompletableFuture<List<BmsSopData>> sopAscF = CompletableFuture.supplyAsync(() -> loadSopData(deviceId, lo, hi, cap));
        CompletableFuture<List<VehicleLocation>> locAscF = CompletableFuture.supplyAsync(() -> loadVehicleLocations(deviceId, lo, hi, cap));
        CompletableFuture<List<BmsChargeTemperature>> chargeAscF = CompletableFuture.supplyAsync(() -> loadChargeTemps(deviceId, lo, hi, mapCap));
        CompletableFuture<List<BmsVoltageChannelData>> voltAscF = CompletableFuture.supplyAsync(() -> loadVoltageChannels(deviceId, lo, hi, mapCap));
        CompletableFuture<List<SubsystemVoltage>> svAscF = CompletableFuture.supplyAsync(() -> loadSubsystemVoltages(deviceId, lo, hi, mapCap));
        CompletableFuture<List<SubsystemTemperature>> stAscF = CompletableFuture.supplyAsync(() -> loadSubsystemTemperatures(deviceId, lo, hi, mapCap));

        CompletableFuture.allOf(
                curExF, curSsF, curCdF, curRtF, curSopF, curLocF, chargeMapF, voltMapF, svMapF, stMapF,
                extremeAscF, ssAscF, cdAscF, rtAscF, sopAscF, locAscF, chargeAscF, voltAscF, svAscF, stAscF
        ).join();

        PrefetchContext ctx = new PrefetchContext();
        ctx.curEx = curExF.join();
        ctx.curSs = curSsF.join();
        ctx.curCd = curCdF.join();
        ctx.curRt = curRtF.join();
        ctx.curSop = curSopF.join();
        ctx.curLoc = curLocF.join();
        ctx.chargeByIdx = chargeMapF.join();
        ctx.voltByKey = voltMapF.join();
        ctx.svByNo = svMapF.join();
        ctx.stByNo = stMapF.join();
        ctx.extremeAsc = extremeAscF.join();
        ctx.ssAsc = ssAscF.join();
        ctx.cdAsc = cdAscF.join();
        ctx.rtAsc = rtAscF.join();
        ctx.sopAsc = sopAscF.join();
        ctx.locAsc = locAscF.join();
        ctx.chargeAsc = chargeAscF.join();
        ctx.voltAsc = voltAscF.join();
        ctx.svAsc = svAscF.join();
        ctx.stAsc = stAscF.join();
        return ctx;
    }

    private static final class PrefetchContext {
        ExtremeValues curEx;
        BmsSystemStatus curSs;
        BmsChargeDetail curCd;
        BmsRuntimeData curRt;
        BmsSopData curSop;
        VehicleLocation curLoc;
        Map<Integer, BmsChargeTemperature> chargeByIdx;
        Map<String, BmsVoltageChannelData> voltByKey;
        Map<Integer, SubsystemVoltage> svByNo;
        Map<Integer, SubsystemTemperature> stByNo;
        List<ExtremeValues> extremeAsc;
        List<BmsSystemStatus> ssAsc;
        List<BmsChargeDetail> cdAsc;
        List<BmsRuntimeData> rtAsc;
        List<BmsSopData> sopAsc;
        List<VehicleLocation> locAsc;
        List<BmsChargeTemperature> chargeAsc;
        List<BmsVoltageChannelData> voltAsc;
        List<SubsystemVoltage> svAsc;
        List<SubsystemTemperature> stAsc;
    }

    private static int prefetchCap(int vehicleCount) {
        long cap = (long) vehicleCount * PREFETCH_PER_VEHICLE;
        cap = Math.max(PREFETCH_FLOOR, cap);
        cap = Math.min(PREFETCH_CAP_MAX, cap);
        return (int) cap;
    }

    private static int mapPrefetchCap(int vehicleCount) {
        long cap = (long) vehicleCount * MAP_ROWS_PER_VEHICLE;
        cap = Math.max(64, cap);
        cap = Math.min(MAP_PREFETCH_CAP_MAX, cap);
        return (int) cap;
    }

    private static List<Integer> sortedIndicesByPointTimeAsc(List<VehicleData> vehicleRows) {
        List<Integer> ix = new ArrayList<>(vehicleRows.size());
        for (int i = 0; i < vehicleRows.size(); i++) {
            ix.add(i);
        }
        ix.sort(Comparator.comparing(i -> vehiclePointTime(vehicleRows.get(i)), Comparator.nullsLast(Date::compareTo)));
        return ix;
    }

    private DeviceHistoryFlatRowDTO toFlat(
            String deviceId,
            VehicleData vd,
            SimpleDateFormat tf,
            ExtremeValues ev,
            BmsSystemStatus ss,
            BmsChargeDetail cd,
            BmsRuntimeData rt,
            BmsSopData sop,
            VehicleLocation loc,
            Map<Integer, BmsChargeTemperature> chargeByIdx,
            Map<String, BmsVoltageChannelData> voltByKey,
            Map<Integer, SubsystemVoltage> svByNo,
            Map<Integer, SubsystemTemperature> stByNo) {

        DeviceHistoryFlatRowDTO dto = new DeviceHistoryFlatRowDTO();
        dto.setDeviceId(deviceId);
        dto.setVehicleDataId(vd.getId());
        Date pt = vehiclePointTime(vd);
        dto.setPointTime(pt == null ? null : tf.format(pt));
        dto.setVehicleCreateTime(vd.getCreateTime() == null ? null : tf.format(vd.getCreateTime()));

        dto.setSoc(vd.getSoc());
        dto.setTotalVoltage(vd.getTotalVoltage());
        dto.setTotalCurrent(vd.getTotalCurrent());
        dto.setVehicleSpeed(vd.getVehicleSpeed());
        dto.setTotalMileage(vd.getTotalMileage());
        dto.setChargingStatus(vd.getChargingStatus());
        dto.setVehicleStatus(vd.getVehicleStatus());
        dto.setOperationMode(vd.getOperationMode());
        dto.setDcdcStatus(vd.getDcdcStatus());
        dto.setGearPosition(vd.getGearPosition());
        dto.setInsulationResistance(vd.getInsulationResistance());

        if (ev != null) {
            dto.setMaxCellVoltage(ev.getMaxVoltageValue());
            dto.setMinCellVoltage(ev.getMinVoltageValue());
            dto.setMaxTempValue(ev.getMaxTempValue());
            dto.setMinTempValue(ev.getMinTempValue());
            dto.setMaxCellPos(joinPos(ev.getMaxVoltageSubsystemNo(), ev.getMaxVoltageBatteryNo()));
            dto.setMinCellPos(joinPos(ev.getMinVoltageSubsystemNo(), ev.getMinVoltageBatteryNo()));
            dto.setMaxTempPos(joinPos(ev.getMaxTempSubsystemNo(), ev.getMaxTempProbeNo()));
            dto.setMinTempPos(joinPos(ev.getMinTempSubsystemNo(), ev.getMinTempProbeNo()));
        }

        if (ss != null) {
            dto.setRelayStatus(ss.getRelayStatus());
            dto.setOnaaccStatus(ss.getOnaaccStatus());
            dto.setResetCount(ss.getResetCount());
            dto.setCycleCount(ss.getCycleCount());
            dto.setCellCount(ss.getCellCount());
            dto.setSample25v(ss.getSample25v());
            dto.setSample5v(ss.getSample5v());
            dto.setTempSensorCount(ss.getTempSensorCount());
        }

        if (cd != null) {
            dto.setAcChargeCurrent(cd.getAcChargeCurrent());
            dto.setAcChargeVoltage(cd.getAcChargeVoltage());
            dto.setDcChargeCurrent(cd.getDcChargeCurrent());
            dto.setDcChargeVoltage(cd.getDcChargeVoltage());
            dto.setChargeTime(cd.getChargeTime());
            dto.setAcStopCode(cd.getAcStopCode());
            dto.setDcStopCode(cd.getDcStopCode());
            dto.setAcCpValue(cd.getAcCpValue());
            dto.setAcGunResistance(cd.getAcGunResistance());
            dto.setChargingStage(cd.getChargingStage());
            dto.setAcStage(cd.getAcStage());
            dto.setDcStage(cd.getDcStage());
        }

        if (rt != null) {
            dto.setRuntimeVehicleSpeed(rt.getVehicleSpeed());
            dto.setRuntimeMileage(rt.getMileage());
            dto.setSystemVoltage(rt.getSystemVoltage());
            dto.setSystemCurrent(rt.getSystemCurrent());
            dto.setRuntimeInsulationResistance(rt.getInsulationResistance());
            dto.setSocMax(rt.getSocMax());
            dto.setSocMin(rt.getSocMin());
            dto.setGpvVoltage(rt.getGpvVoltage());
            dto.setRunStatus(rt.getRunStatus());
            dto.setPowerStatus(rt.getPowerStatus());
        }

        if (sop != null) {
            dto.setDischargeSop10s(sop.getDischargeSop10s());
            dto.setDischargeSop30s(sop.getDischargeSop30s());
            dto.setDischargeSop60s(sop.getDischargeSop60s());
            dto.setDischargeSopContinuous(sop.getDischargeSopContinuous());
            dto.setDischargeSopTotal(sop.getDischargeSopTotal());
            dto.setRegenSop10s(sop.getRegenSop10s());
            dto.setRegenSop30s(sop.getRegenSop30s());
            dto.setRegenSop60s(sop.getRegenSop60s());
            dto.setRegenSopContinuous(sop.getRegenSopContinuous());
            dto.setRegenSopTotal(sop.getRegenSopTotal());
            dto.setChargeSop(sop.getChargeSop());
            dto.setAcChargeSop(sop.getAcChargeSop());
        }

        if (loc != null) {
            dto.setLongitude(loc.getLongitude());
            dto.setLatitude(loc.getLatitude());
            dto.setPositioningStatus(loc.getPositioningStatus());
        }

        List<BmsChargeTemperature> temps = new ArrayList<>(chargeByIdx.values());
        temps.sort(Comparator.comparing(BmsChargeTemperature::getTempIndex, Comparator.nullsLast(Integer::compareTo)));
        dto.setChargeTemperaturesAtPoint(temps.isEmpty() ? null : temps);

        List<BmsVoltageChannelData> volts = new ArrayList<>(voltByKey.values());
        volts.sort(Comparator
                .comparing(BmsVoltageChannelData::getChannelType, Comparator.nullsFirst(String::compareTo))
                .thenComparing(BmsVoltageChannelData::getChannelIndex, Comparator.nullsLast(Integer::compareTo)));
        dto.setVoltageChannelsAtPoint(volts.isEmpty() ? null : volts);

        List<SubsystemVoltage> svs = new ArrayList<>(svByNo.values());
        svs.sort(Comparator.comparing(SubsystemVoltage::getSubsystemNo, Comparator.nullsLast(Integer::compareTo)));
        dto.setSubsystemVoltagesAtPoint(svs.isEmpty() ? null : svs);

        List<SubsystemTemperature> sts = new ArrayList<>(stByNo.values());
        sts.sort(Comparator.comparing(SubsystemTemperature::getSubsystemNo, Comparator.nullsLast(Integer::compareTo)));
        dto.setSubsystemTemperaturesAtPoint(sts.isEmpty() ? null : sts);

        BmsChargeTemperature t1 = chargeByIdx.get(1);
        dto.setChargeSeatTemp1(t1 == null ? null : t1.getTemperature());

        BmsVoltageChannelData rtp1 = voltByKey.get("RTP|1");
        dto.setRtp1Voltage(rtp1 == null ? null : rtp1.getVoltage());

        return dto;
    }

    private static String voltChannelKey(BmsVoltageChannelData row) {
        String t = row.getChannelType() == null ? "" : row.getChannelType();
        Integer ix = row.getChannelIndex();
        return t + "|" + (ix == null ? "" : ix);
    }

    private static String joinPos(Integer a, Integer b) {
        if (a == null || b == null) {
            return null;
        }
        return a + "-" + b;
    }

    private static Date nullSafe(Date d) {
        return d == null ? new Date(0L) : d;
    }

    private static Date vehiclePointTime(VehicleData v) {
        if (v == null) {
            return null;
        }
        return v.getTimestamp() != null ? v.getTimestamp() : v.getCreateTime();
    }

    private static Date extremeEffectiveTime(ExtremeValues e) {
        return e.getTimestamp() != null ? e.getTimestamp() : e.getCreateTime();
    }

    private static Date locationEffectiveTime(VehicleLocation l) {
        return l.getTimestamp() != null ? l.getTimestamp() : l.getCreateTime();
    }

    private static Date chargeTempEffectiveTime(BmsChargeTemperature r) {
        return r.getTimestamp() != null ? r.getTimestamp() : r.getCreateTime();
    }

    private static Date voltageChannelEffectiveTime(BmsVoltageChannelData r) {
        return r.getTimestamp() != null ? r.getTimestamp() : r.getCreateTime();
    }

    private static Date subsystemVoltageEffectiveTime(SubsystemVoltage r) {
        return r.getTimestamp() != null ? r.getTimestamp() : r.getCreateTime();
    }

    private static Date subsystemTemperatureEffectiveTime(SubsystemTemperature r) {
        return r.getTimestamp() != null ? r.getTimestamp() : r.getCreateTime();
    }

    private ExtremeValues bootstrapExtremeValues(String deviceId, Date before) {
        LambdaQueryWrapper<ExtremeValues> qw = new LambdaQueryWrapper<>();
        qw.eq(ExtremeValues::getDeviceId, deviceId);
        applyTimestampLt(qw, ExtremeValues::getTimestamp, before);
        qw.orderByDesc(ExtremeValues::getTimestamp);
        qw.last("LIMIT 1");
        List<ExtremeValues> rows = extremeValuesMapper.selectList(qw);
        return rows == null || rows.isEmpty() ? null : rows.get(0);
    }

    private VehicleLocation bootstrapVehicleLocation(String deviceId, Date before) {
        LambdaQueryWrapper<VehicleLocation> qw = new LambdaQueryWrapper<>();
        qw.eq(VehicleLocation::getDeviceId, deviceId);
        applyTimestampLt(qw, VehicleLocation::getTimestamp, before);
        qw.orderByDesc(VehicleLocation::getTimestamp);
        qw.last("LIMIT 1");
        List<VehicleLocation> rows = vehicleLocationMapper.selectList(qw);
        return rows == null || rows.isEmpty() ? null : rows.get(0);
    }

    private BmsSystemStatus bootstrapSystemStatus(String deviceId, Date before) {
        return bmsSystemStatusService.lambdaQuery()
                .eq(BmsSystemStatus::getDeviceId, deviceId)
                .isNotNull(BmsSystemStatus::getTimestamp)
                .lt(BmsSystemStatus::getTimestamp, before)
                .orderByDesc(BmsSystemStatus::getTimestamp)
                .last("limit 1")
                .one();
    }

    private BmsChargeDetail bootstrapChargeDetail(String deviceId, Date before) {
        return bmsChargeDetailService.lambdaQuery()
                .eq(BmsChargeDetail::getDeviceId, deviceId)
                .isNotNull(BmsChargeDetail::getTimestamp)
                .lt(BmsChargeDetail::getTimestamp, before)
                .orderByDesc(BmsChargeDetail::getTimestamp)
                .last("limit 1")
                .one();
    }

    private BmsRuntimeData bootstrapRuntimeData(String deviceId, Date before) {
        return bmsRuntimeDataService.lambdaQuery()
                .eq(BmsRuntimeData::getDeviceId, deviceId)
                .isNotNull(BmsRuntimeData::getTimestamp)
                .lt(BmsRuntimeData::getTimestamp, before)
                .orderByDesc(BmsRuntimeData::getTimestamp)
                .last("limit 1")
                .one();
    }

    private BmsSopData bootstrapSopData(String deviceId, Date before) {
        return bmsSopDataService.lambdaQuery()
                .eq(BmsSopData::getDeviceId, deviceId)
                .isNotNull(BmsSopData::getTimestamp)
                .lt(BmsSopData::getTimestamp, before)
                .orderByDesc(BmsSopData::getTimestamp)
                .last("limit 1")
                .one();
    }

    private Map<Integer, BmsChargeTemperature> bootstrapChargeTemps(String deviceId, Date before, int limit) {
        LambdaQueryWrapper<BmsChargeTemperature> qw = new LambdaQueryWrapper<>();
        qw.eq(BmsChargeTemperature::getDeviceId, deviceId);
        applyTimestampLt(qw, BmsChargeTemperature::getTimestamp, before);
        qw.orderByAsc(BmsChargeTemperature::getTimestamp);
        qw.last("LIMIT " + limit);
        return replayChargeTemps(bmsChargeTemperatureService.list(qw));
    }

    private Map<String, BmsVoltageChannelData> bootstrapVoltageChannels(String deviceId, Date before, int limit) {
        LambdaQueryWrapper<BmsVoltageChannelData> qw = new LambdaQueryWrapper<>();
        qw.eq(BmsVoltageChannelData::getDeviceId, deviceId);
        applyTimestampLt(qw, BmsVoltageChannelData::getTimestamp, before);
        qw.orderByAsc(BmsVoltageChannelData::getTimestamp);
        qw.last("LIMIT " + limit);
        return replayVoltageChannels(bmsVoltageChannelDataService.list(qw));
    }

    private Map<Integer, SubsystemVoltage> bootstrapSubsystemVoltages(String deviceId, Date before, int limit) {
        LambdaQueryWrapper<SubsystemVoltage> qw = new LambdaQueryWrapper<>();
        qw.eq(SubsystemVoltage::getDeviceId, deviceId);
        applyTimestampLt(qw, SubsystemVoltage::getTimestamp, before);
        qw.orderByAsc(SubsystemVoltage::getTimestamp);
        qw.last("LIMIT " + limit);
        return replaySubsystemVoltages(subsystemVoltageService.list(qw));
    }

    private Map<Integer, SubsystemTemperature> bootstrapSubsystemTemperatures(String deviceId, Date before, int limit) {
        LambdaQueryWrapper<SubsystemTemperature> qw = new LambdaQueryWrapper<>();
        qw.eq(SubsystemTemperature::getDeviceId, deviceId);
        applyTimestampLt(qw, SubsystemTemperature::getTimestamp, before);
        qw.orderByAsc(SubsystemTemperature::getTimestamp);
        qw.last("LIMIT " + limit);
        return replaySubsystemTemperatures(subsystemTemperatureService.list(qw));
    }

    private static Map<Integer, BmsChargeTemperature> replayChargeTemps(List<BmsChargeTemperature> rows) {
        Map<Integer, BmsChargeTemperature> map = new HashMap<>();
        if (rows == null) {
            return map;
        }
        for (BmsChargeTemperature row : rows) {
            if (row.getTempIndex() != null) {
                map.put(row.getTempIndex(), row);
            }
        }
        return map;
    }

    private static Map<String, BmsVoltageChannelData> replayVoltageChannels(List<BmsVoltageChannelData> rows) {
        Map<String, BmsVoltageChannelData> map = new HashMap<>();
        if (rows == null) {
            return map;
        }
        for (BmsVoltageChannelData row : rows) {
            map.put(voltChannelKey(row), row);
        }
        return map;
    }

    private static Map<Integer, SubsystemVoltage> replaySubsystemVoltages(List<SubsystemVoltage> rows) {
        Map<Integer, SubsystemVoltage> map = new HashMap<>();
        if (rows == null) {
            return map;
        }
        for (SubsystemVoltage row : rows) {
            if (row.getSubsystemNo() != null) {
                map.put(row.getSubsystemNo(), row);
            }
        }
        return map;
    }

    private static Map<Integer, SubsystemTemperature> replaySubsystemTemperatures(List<SubsystemTemperature> rows) {
        Map<Integer, SubsystemTemperature> map = new HashMap<>();
        if (rows == null) {
            return map;
        }
        for (SubsystemTemperature row : rows) {
            if (row.getSubsystemNo() != null) {
                map.put(row.getSubsystemNo(), row);
            }
        }
        return map;
    }

    private List<ExtremeValues> loadExtremeValues(String deviceId, Date lo, Date hi, int cap) {
        LambdaQueryWrapper<ExtremeValues> qw = new LambdaQueryWrapper<>();
        qw.eq(ExtremeValues::getDeviceId, deviceId);
        applyTimestampRange(qw, ExtremeValues::getTimestamp, lo, hi);
        qw.orderByAsc(ExtremeValues::getTimestamp);
        qw.last("LIMIT " + cap);
        List<ExtremeValues> rows = extremeValuesMapper.selectList(qw);
        return rows == null ? new ArrayList<>() : rows;
    }

    private List<VehicleLocation> loadVehicleLocations(String deviceId, Date lo, Date hi, int cap) {
        LambdaQueryWrapper<VehicleLocation> qw = new LambdaQueryWrapper<>();
        qw.eq(VehicleLocation::getDeviceId, deviceId);
        applyTimestampRange(qw, VehicleLocation::getTimestamp, lo, hi);
        qw.orderByAsc(VehicleLocation::getTimestamp);
        qw.last("LIMIT " + cap);
        List<VehicleLocation> rows = vehicleLocationMapper.selectList(qw);
        return rows == null ? new ArrayList<>() : rows;
    }

    private List<BmsChargeTemperature> loadChargeTemps(String deviceId, Date lo, Date hi, int cap) {
        LambdaQueryWrapper<BmsChargeTemperature> qw = new LambdaQueryWrapper<>();
        qw.eq(BmsChargeTemperature::getDeviceId, deviceId);
        applyTimestampRange(qw, BmsChargeTemperature::getTimestamp, lo, hi);
        qw.orderByAsc(BmsChargeTemperature::getTimestamp);
        qw.last("LIMIT " + cap);
        List<BmsChargeTemperature> rows = bmsChargeTemperatureService.list(qw);
        return rows == null ? new ArrayList<>() : rows;
    }

    private List<BmsVoltageChannelData> loadVoltageChannels(String deviceId, Date lo, Date hi, int cap) {
        LambdaQueryWrapper<BmsVoltageChannelData> qw = new LambdaQueryWrapper<>();
        qw.eq(BmsVoltageChannelData::getDeviceId, deviceId);
        applyTimestampRange(qw, BmsVoltageChannelData::getTimestamp, lo, hi);
        qw.orderByAsc(BmsVoltageChannelData::getTimestamp);
        qw.last("LIMIT " + cap);
        List<BmsVoltageChannelData> rows = bmsVoltageChannelDataService.list(qw);
        return rows == null ? new ArrayList<>() : rows;
    }

    private List<SubsystemVoltage> loadSubsystemVoltages(String deviceId, Date lo, Date hi, int cap) {
        LambdaQueryWrapper<SubsystemVoltage> qw = new LambdaQueryWrapper<>();
        qw.eq(SubsystemVoltage::getDeviceId, deviceId);
        applyTimestampRange(qw, SubsystemVoltage::getTimestamp, lo, hi);
        qw.orderByAsc(SubsystemVoltage::getTimestamp);
        qw.last("LIMIT " + cap);
        List<SubsystemVoltage> rows = subsystemVoltageService.list(qw);
        return rows == null ? new ArrayList<>() : rows;
    }

    private List<SubsystemTemperature> loadSubsystemTemperatures(String deviceId, Date lo, Date hi, int cap) {
        LambdaQueryWrapper<SubsystemTemperature> qw = new LambdaQueryWrapper<>();
        qw.eq(SubsystemTemperature::getDeviceId, deviceId);
        applyTimestampRange(qw, SubsystemTemperature::getTimestamp, lo, hi);
        qw.orderByAsc(SubsystemTemperature::getTimestamp);
        qw.last("LIMIT " + cap);
        List<SubsystemTemperature> rows = subsystemTemperatureService.list(qw);
        return rows == null ? new ArrayList<>() : rows;
    }

    private List<BmsSystemStatus> loadSystemStatus(String deviceId, Date lo, Date hi, int cap) {
        return bmsSystemStatusService.lambdaQuery()
                .eq(BmsSystemStatus::getDeviceId, deviceId)
                .isNotNull(BmsSystemStatus::getTimestamp)
                .ge(BmsSystemStatus::getTimestamp, lo)
                .le(BmsSystemStatus::getTimestamp, hi)
                .orderByAsc(BmsSystemStatus::getTimestamp)
                .last("limit " + cap)
                .list();
    }

    private List<BmsChargeDetail> loadChargeDetail(String deviceId, Date lo, Date hi, int cap) {
        return bmsChargeDetailService.lambdaQuery()
                .eq(BmsChargeDetail::getDeviceId, deviceId)
                .isNotNull(BmsChargeDetail::getTimestamp)
                .ge(BmsChargeDetail::getTimestamp, lo)
                .le(BmsChargeDetail::getTimestamp, hi)
                .orderByAsc(BmsChargeDetail::getTimestamp)
                .last("limit " + cap)
                .list();
    }

    private List<BmsRuntimeData> loadRuntimeData(String deviceId, Date lo, Date hi, int cap) {
        return bmsRuntimeDataService.lambdaQuery()
                .eq(BmsRuntimeData::getDeviceId, deviceId)
                .isNotNull(BmsRuntimeData::getTimestamp)
                .ge(BmsRuntimeData::getTimestamp, lo)
                .le(BmsRuntimeData::getTimestamp, hi)
                .orderByAsc(BmsRuntimeData::getTimestamp)
                .last("limit " + cap)
                .list();
    }

    private List<BmsSopData> loadSopData(String deviceId, Date lo, Date hi, int cap) {
        return bmsSopDataService.lambdaQuery()
                .eq(BmsSopData::getDeviceId, deviceId)
                .isNotNull(BmsSopData::getTimestamp)
                .ge(BmsSopData::getTimestamp, lo)
                .le(BmsSopData::getTimestamp, hi)
                .orderByAsc(BmsSopData::getTimestamp)
                .last("limit " + cap)
                .list();
    }

    private static <T> void applyTimestampLt(
            LambdaQueryWrapper<T> qw,
            SFunction<T, Date> timestampCol,
            Date before) {
        qw.isNotNull(timestampCol);
        if (before != null) {
            qw.lt(timestampCol, before);
        }
    }

    private static <T> void applyTimestampRange(
            LambdaQueryWrapper<T> qw,
            SFunction<T, Date> timestampCol,
            Date lo,
            Date hi) {
        qw.isNotNull(timestampCol);
        if (lo != null) {
            qw.ge(timestampCol, lo);
        }
        if (hi != null) {
            qw.le(timestampCol, hi);
        }
    }
}
