package com.evobms.project.vehicledata.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.evobms.framework.web.domain.R;
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.service.IExtremeValuesService;
import com.evobms.project.battery.service.ISubsystemTemperatureService;
import com.evobms.project.battery.service.ISubsystemVoltageService;
import com.evobms.project.system.domain.BmsDevices;
import com.evobms.project.system.mapper.BmsDevicesMapper;
import com.evobms.project.vehicledata.domain.HomeDashboardDTO;
import com.evobms.project.vehicledata.domain.VehicleData;
import com.evobms.project.vehicledata.domain.VehicleLocation;
import com.evobms.project.vehicledata.dto.*;
import com.evobms.project.vehicledata.service.IVehicleDataService;
import com.evobms.project.vehicledata.service.IVehicleLocationService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 首页数据Controller
 *
 * @author 田志阳
 * @date 2025-11-15
 */
@RestController
@Api(tags = "首页数据统计")
public class BatteryChartController {

    @Autowired
    private IVehicleDataService vehicleDataService;

    @Autowired
    private IVehicleLocationService vehicleLocationService;

    @Autowired
    private ISubsystemVoltageService subsystemVoltageService;

    @Autowired
    private ISubsystemTemperatureService subsystemTemperatureService;

    @Autowired
    private IExtremeValuesService extremeValuesService;

    @Autowired
    private BmsDevicesMapper bmsDevicesMapper;

    // 图表数据：按最近 hours 小时返回整车电压/电流/SOC 的时间序列
    @GetMapping("/battery-data/{deviceId}/chart")
    @ApiOperation(value = "-整车电压电流SOC时间序列", notes = "按最近hours小时返回时间序列")
    public List<ChartDataPoint> getChartData(@ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String deviceId, @ApiParam(value = "最近多少小时的数据", defaultValue = "24") @RequestParam(value = "hours", required = false, defaultValue = "24") int hours) {
        long millis = System.currentTimeMillis() - (long) hours * 3600_000L;
        Date startTime = new Date(millis);
        List<VehicleData> rows = vehicleDataService.selectByDeviceAndStartTime(deviceId, startTime);
        return rows.stream().map(v -> new ChartDataPoint(v.getTimestamp(), v.getTotalVoltage() == null ? 0 : v.getTotalVoltage().doubleValue(), v.getTotalCurrent() == null ? 0 : v.getTotalCurrent().doubleValue(), v.getSoc() == null ? 0 : v.getSoc())).collect(Collectors.toList());
    }

    // 整车最新：返回 deviceId 最新的一条整车数据
    @GetMapping("/vehicle-data/{deviceId}/latest")
    @ApiOperation(value = "-整车最新数据", notes = "返回指定设备最新整车数据")
    public VehicleData latestVehicleData(@ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String deviceId) {
        return vehicleDataService.selectLatestByDeviceId(deviceId);
    }

    // 位置最新：返回 deviceId 最新的一条位置数据
    @GetMapping("/vehicle-location/{deviceId}/latest")
    @ApiOperation(value = "-车辆最新位置", notes = "返回指定设备最新位置数据")
    public VehicleLocation latestVehicleLocation(@ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String deviceId) {
        return vehicleLocationService.selectLatestByDeviceId(deviceId);
    }

    // 位置轨迹：按时间范围返回位置列表，start/end 为毫秒时间戳，可选 limit 限制条数
    @GetMapping("/vehicle-location/{deviceId}")
    @ApiOperation(value = "-车辆位置轨迹", notes = "按时间范围返回位置列表")
    public List<VehicleLocation> locationRange(@ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String deviceId, @ApiParam(value = "开始时间戳(毫秒)", required = true) @RequestParam("start") long startMillis, @ApiParam(value = "结束时间戳(毫秒)", required = true) @RequestParam("end") long endMillis, @ApiParam(value = "限制返回条数") @RequestParam(value = "limit", required = false) Integer limit) {
        Date start = new Date(startMillis);
        Date end = new Date(endMillis);
        return vehicleLocationService.selectByDeviceAndRange(deviceId, start, end, limit);
    }

    // 子系统最新帧（电压）：返回指定子系统号最新的一帧电压数据
    @GetMapping("/subsystem-voltage/{deviceId}/{subsystemNo}/latest")
    @ApiOperation(value = "-子系统最新电压帧", notes = "返回指定子系统最新电压数据")
    public SubsystemVoltage latestVoltage(@ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String deviceId, @ApiParam(value = "子系统号", required = true) @PathVariable("subsystemNo") Integer subsystemNo) {
        return subsystemVoltageService.selectLatestByDeviceAndSubsystem(deviceId, subsystemNo);
    }

    // 子系统最新帧（温度）：返回指定子系统号最新的一帧温度数据
    @GetMapping("/subsystem-temperature/{deviceId}/{subsystemNo}/latest")
    @ApiOperation(value = "-子系统最新温度帧", notes = "返回指定子系统最新温度数据")
    public SubsystemTemperature latestTemperature(@ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String deviceId, @ApiParam(value = "子系统号", required = true) @PathVariable("subsystemNo") Integer subsystemNo) {
        return subsystemTemperatureService.selectLatestByDeviceAndSubsystem(deviceId, subsystemNo);
    }

    @GetMapping("/extreme-values/{deviceId}/latest")
    @ApiOperation(value = "-极值最新数据", notes = "返回指定设备极值数据，支持before筛选")
    public ExtremeValues latestExtremeValues(@ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String deviceId, @ApiParam(value = "筛选指定时间之前的数据(yyyy-MM-dd HH:mm:ss 或 毫秒时间戳)") @RequestParam(value = "before", required = false) String before) {
        if (before == null || before.isEmpty()) {
            return extremeValuesService.selectLatestByDeviceId(deviceId);
        }
        Date beforeTime = null;
        if (before.matches("\\d+")) {
            beforeTime = new Date(Long.parseLong(before));
        } else {
            try {
                beforeTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(before);
            } catch (ParseException e) {
                return extremeValuesService.selectLatestByDeviceId(deviceId);
            }
        }
        return extremeValuesService.selectLatestByDeviceIdBefore(deviceId, beforeTime);
    }

    @GetMapping("/api/dashboard/home")
    @ApiOperation(value = "-首页所有分布数据", notes = "获取首页大屏的分布统计数据")
    public R<HomeDashboardDTO> home() {
        HomeDashboardDTO dto = vehicleDataService.getHomeDashboard();
        return R.ok(dto);
    }

 /*   @GetMapping("/monitor/dashboard/summary")
    @ApiOperation(value = "首页卡片统计", notes = "返回项目数、设备数、在线数、故障数")
    public R<DashboardSummaryDTO> summary() {
        long deviceCount = bmsDevicesMapper.selectCount(null);
        long onlineCount = bmsDevicesMapper.selectCount(new QueryWrapper<BmsDevices>().eq("status", "online"));
        long projectCount = 22L;
        long faultCount = 12L;
        DashboardSummaryDTO dto = new DashboardSummaryDTO();
        dto.setProjectCount(projectCount);
        dto.setDeviceCount(deviceCount);
        dto.setOnlineCount(onlineCount);
        dto.setFaultCount(faultCount);
        return R.ok(dto);
    }*/

  /*  @GetMapping("/monitor/dashboard/soc-distribution")
    @ApiOperation(value = "设备SOC分布", notes = "按每台设备最新SOC值统计")
    public R<List<NameValueDTO>> socDistribution(@ApiParam(value = "分桶边界，逗号分隔，例：0,20,40,60,80,100") @RequestParam(value = "bins", required = false, defaultValue = "0,20,40,60,80,100") String bins, @ApiParam(value = "是否包含离线设备", defaultValue = "false") @RequestParam(value = "includeOffline", required = false, defaultValue = "false") boolean includeOffline) {
        List<Integer> edges = Stream.of(bins.split(",")).map(String::trim).filter(s -> !s.isEmpty()).map(Integer::parseInt).sorted().toList();
        if (edges.size() < 2) {
            return R.ok(new ArrayList<>());
        }
        long[] counts = new long[edges.size() - 1];
        QueryWrapper<BmsDevices> qw = new QueryWrapper<>();
        if (!includeOffline) {
            //查询在线设备
            qw.eq("status", "online");
        }
        List<BmsDevices> devices = bmsDevicesMapper.selectList(qw);

        for (BmsDevices d : devices) {
            String deviceId = d.getDeviceId();
            if (deviceId == null || deviceId.isEmpty()) {
                continue;
            }
            VehicleData latest = vehicleDataService.selectLatestByDeviceId(deviceId);
            Integer soc = latest == null ? null : latest.getSoc();
            //过滤
            if (soc == null || soc < 0 || soc > 100) {
                continue;
            }
            for (int i = 0; i < counts.length; i++) {
                int min = edges.get(i);
                int max = edges.get(i + 1);
                boolean isLast = (i == counts.length - 1);
                boolean inRange = isLast ? (soc >= min && soc <= max) : (soc >= min && soc < max);
                if (inRange) {
                    counts[i]++;
                    break;
                }
            }
        }

        List<NameValueDTO> items = new ArrayList<>(counts.length);
        String ts = Instant.now().toString();
        for (int i = 0; i < counts.length; i++) {
            int min = edges.get(i);
            int max = edges.get(i + 1);
            NameValueDTO nv = new NameValueDTO();
            nv.setName(min + "-" + max + "%");
            nv.setValue(counts[i]);
            nv.setCreateTime(ts);
            items.add(nv);
        }

        return R.ok(items);
    }
*/
    @GetMapping("/monitor/dashboard/overview")
    @ApiOperation(value = "首页概览聚合", notes = "返回基础统计、SOC分布、日充放电趋势")
    public R<DashboardOverviewDTO> overview(@ApiParam(value = "SOC分桶，默认 0,20,40,60,80,100") @RequestParam(value = "bins", required = false, defaultValue = "0,20,40,60,80,100") String bins, @ApiParam(value = "趋势统计天数", defaultValue = "7") @RequestParam(value = "days", required = false, defaultValue = "7") int days, @ApiParam(value = "运行时长档位(小时)", defaultValue = "10,50,100,150,200,250,300") @RequestParam(value = "runtimeBins", required = false, defaultValue = "10,50,100,150,200,250,300") String runtimeBins) {
        DashboardOverviewDTO out = new DashboardOverviewDTO();
        // summary
        long deviceCount = bmsDevicesMapper.selectCount(null);
        long onlineCount = bmsDevicesMapper.selectCount(new QueryWrapper<BmsDevices>().eq("status", "online"));
        long projectCount = 22L;
        long faultCount = 12L;
        DashboardSummaryDTO s = new DashboardSummaryDTO();
        s.setProjectCount(projectCount);
        s.setDeviceCount(deviceCount);
        s.setOnlineCount(onlineCount);
        s.setFaultCount(faultCount);
        out.setSummary(s);
        // soc distribution
        List<Integer> edges = Stream.of(bins.split(",")).map(String::trim).filter(x -> !x.isEmpty()).map(Integer::parseInt).sorted().toList();
        long[] counts = new long[Math.max(0, edges.size() - 1)];
        if (counts.length > 0) {
            List<BmsDevices> devices = bmsDevicesMapper.selectList(new QueryWrapper<BmsDevices>().eq("status", "online"));
            for (BmsDevices d : devices) {
                String did = d.getDeviceId();
                if (did == null || did.isEmpty()) continue;
                VehicleData latest = vehicleDataService.selectLatestByDeviceId(did);
                Integer soc = latest == null ? null : latest.getSoc();
                if (soc == null || soc < 0 || soc > 100) continue;
                for (int i = 0; i < counts.length; i++) {
                    int min = edges.get(i), max = edges.get(i + 1);
                    boolean last = i == counts.length - 1;
                    boolean in = last ? soc >= min && soc <= max : soc >= min && soc < max;
                    if (in) {
                        counts[i]++;
                        break;
                    }
                }
            }
        }
        String ts = Instant.now().toString();
        List<NameValueDTO> pie = new ArrayList<>();
        for (int i = 0; i < counts.length; i++) {
            int min = edges.get(i), max = edges.get(i + 1);
            NameValueDTO nv = new NameValueDTO();
            nv.setName(min + "-" + max + "%");
            nv.setValue(counts[i]);
            nv.setCreateTime(ts);
            pie.add(nv);
        }
        out.setSocDistribution(pie);
        // trend (simple heuristic by VehicleData records)
        int safeDays = Math.max(1, Math.min(31, days));
        Date endTime = new Date();
        Date startTime = Date.from(Instant.ofEpochMilli(endTime.getTime()).minusSeconds(safeDays * 86400L));
        // 复用 selectVehicleDataList 进行时间范围查询
        VehicleData q = new VehicleData();
        Map<String, Object> params = new HashMap<>();
        params.put("beginTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(startTime));
        params.put("endTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(endTime));
        q.setParams(params);
        List<VehicleData> all = vehicleDataService.selectVehicleDataList(q);
        all.sort((a, b) -> {
            if (a.getDeviceId() == null)
                return -1;
            if (b.getDeviceId() == null)
                return 1;
            int c = a.getDeviceId().compareTo(b.getDeviceId());
            if (c != 0) return c;
            return a.getTimestamp().compareTo(b.getTimestamp());
        });
        Map<String, DashboardTrendItemDTO> byDate = new HashMap<>();
        SimpleDateFormat dayFmt = new SimpleDateFormat("yyyy-MM-dd");
        // 初始化天数
        LocalDate today = LocalDate.now();
        for (int i = safeDays - 1; i >= 0; i--) {
            LocalDate d = today.minusDays(i);
            DashboardTrendItemDTO item = new DashboardTrendItemDTO();
            item.setDate(d.toString());
            item.setChargeDuration(0);
            item.setDischargeDuration(0);
            byDate.put(d.toString(), item);
        }
        // 累加相邻样本间隔（秒）
        for (int i = 0; i < all.size() - 1; i++) {
            VehicleData cur = all.get(i);
            VehicleData nex = all.get(i + 1);
            if (cur.getDeviceId() == null || !cur.getDeviceId().equals(nex.getDeviceId())) continue;
            if (cur.getTimestamp() == null || nex.getTimestamp() == null) continue;
            long delta = (nex.getTimestamp().getTime() - cur.getTimestamp().getTime()) / 1000L;
            if (delta <= 0 || delta > 6 * 3600) continue; // 限制单段不超过6小时
            String key = dayFmt.format(cur.getTimestamp());
            DashboardTrendItemDTO item = byDate.get(key);
            if (item == null) continue;
            Integer st = cur.getChargingStatus();
            if (st != null && (st == 1 || st == 2)) {
                item.setChargeDuration(item.getChargeDuration() + delta);
            } else if (st != null && st == 3) {
                item.setDischargeDuration(item.getDischargeDuration() + delta);
            }
        }
        List<DashboardTrendItemDTO> trend = byDate.values().stream().sorted(java.util.Comparator.comparing(DashboardTrendItemDTO::getDate)).collect(Collectors.toList());
        out.setTrend(trend);
        // runtime summary + distribution
        long sumChargeSec = trend.stream().mapToLong(DashboardTrendItemDTO::getChargeDuration).sum();
        long sumDischargeSec = trend.stream().mapToLong(DashboardTrendItemDTO::getDischargeDuration).sum();
        RuntimeSummaryDTO rsum = new RuntimeSummaryDTO();
        rsum.setTotalChargeHours(sumChargeSec / 3600);
        rsum.setTotalDischargeHours(sumDischargeSec / 3600);
        rsum.setTotalRunHours((sumChargeSec + sumDischargeSec) / 3600);
        rsum.setTotalChargeEnergyKWh(0);
        rsum.setTotalDischargeEnergyKWh(0);
        rsum.setTotalChargeCapacityAh(0);
        rsum.setTotalDischargeCapacityAh(0);
        out.setRuntimeSummary(rsum);
        // 每设备运行小时（近 days），按 chargingStatus in (1,2,3) 累加
        Map<String, Long> runSecByDevice = new HashMap<>();
        for (int i = 0; i < all.size() - 1; i++) {
            VehicleData cur = all.get(i);
            VehicleData nex = all.get(i + 1);
            if (cur.getDeviceId() == null || !cur.getDeviceId().equals(nex.getDeviceId())) continue;
            if (cur.getTimestamp() == null || nex.getTimestamp() == null) continue;
            long delta = (nex.getTimestamp().getTime() - cur.getTimestamp().getTime()) / 1000L;
            if (delta <= 0 || delta > 6 * 3600) continue;
            Integer st = cur.getChargingStatus();
            if (st != null && (st == 1 || st == 2 || st == 3)) {
                runSecByDevice.merge(cur.getDeviceId(), delta, Long::sum);
            }
        }
        List<Integer> hb = Stream.of(runtimeBins.split(",")).map(String::trim).filter(x -> !x.isEmpty()).map(Integer::parseInt).sorted().toList();
        long[] hCounts = new long[Math.max(0, hb.size() - 1)];
        for (Map.Entry<String, Long> e : runSecByDevice.entrySet()) {
            long hours = e.getValue() / 3600;
            for (int i = 0; i < hCounts.length; i++) {
                int min = hb.get(i), max = hb.get(i + 1);
                boolean last = i == hCounts.length - 1;
                boolean in = last ? hours >= min && hours <= max : hours >= min && hours < max;
                if (in) { hCounts[i]++; break; }
            }
        }
        List<NameValueDTO> runtimeDist = new ArrayList<>();
        String now = Instant.now().toString();
        for (int i = 0; i < hCounts.length; i++) {
            int min = hb.get(i), max = hb.get(i + 1);
            NameValueDTO nv = new NameValueDTO();
            nv.setName(min + "h");
            nv.setValue(hCounts[i]);
            nv.setCreateTime(now);
            runtimeDist.add(nv);
        }
        out.setRuntimeDuration(runtimeDist);
        return R.ok(out);
    }

    @GetMapping("/monitor/device/latest-locations")
    @ApiOperation(value = "所有设备最新位置", notes = "返回每个设备的设备对象与其最新位置")
    public R<List<DeviceLatestLocationDTO>> latestLocations(@ApiParam(value = "是否仅包含在线设备", defaultValue = "false") @RequestParam(value = "onlineOnly", required = false, defaultValue = "false") boolean onlineOnly) {
        QueryWrapper<BmsDevices> qw = new QueryWrapper<>();
        if (onlineOnly) {
            qw.eq("status", "online");
        }
        List<BmsDevices> devices = bmsDevicesMapper.selectList(qw);
        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        List<DeviceLatestLocationDTO> result = new ArrayList<>(devices.size());
        for (BmsDevices d : devices) {
            String did = d.getDeviceId();
            VehicleLocation loc = did == null ? null : vehicleLocationService.selectLatestByDeviceId(did);
            TrackPointDTO p = null;
            if (loc != null) {
                p = new TrackPointDTO();
                p.setTimestamp(loc.getTimestamp() == null ? null : fmt.format(loc.getTimestamp()));
                p.setLongitude(loc.getLongitude() == null ? null : loc.getLongitude().doubleValue());
                p.setLatitude(loc.getLatitude() == null ? null : loc.getLatitude().doubleValue());
                p.setPositioningStatus(loc.getPositioningStatus());
            }
            DeviceLatestLocationDTO dto = new DeviceLatestLocationDTO();
            dto.setDevice(d);
            dto.setLocation(p);
            result.add(dto);
        }
        return R.ok(result);
    }

    @GetMapping("/monitor/device/{deviceId}/track")
    @ApiOperation(value = "设备轨迹", notes = "返回指定时间范围内的轨迹")
    public R<DeviceTrackDTO> deviceTrack(@ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String deviceId, @ApiParam(value = "开始时间戳(毫秒)", required = true) @RequestParam("start") long startMillis, @ApiParam(value = "结束时间戳(毫秒)", required = true) @RequestParam("end") long endMillis, @ApiParam(value = "限制返回条数", defaultValue = "1000") @RequestParam(value = "limit", required = false, defaultValue = "1000") Integer limit, @ApiParam(value = "是否包含无效定位", defaultValue = "false") @RequestParam(value = "includeInvalid", required = false, defaultValue = "false") boolean includeInvalid) {
        Date start = new Date(startMillis);
        Date end = new Date(endMillis);
        //查询车辆位置信息
        List<VehicleLocation> rows = vehicleLocationService.selectByDeviceAndRange(deviceId, start, end, limit);

        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //位置列表按 timestamp 升序排序，空时间的记录排在最后。
        List<TrackPointDTO> list = rows.stream()
                .filter(v -> includeInvalid || (v.getPositioningStatus() != null && v.getPositioningStatus() == 1))
                .sorted((a, b) -> {
                    Date ta = a.getTimestamp(), tb = b.getTimestamp();
                    if (ta == null && tb == null) return 0;
                    if (ta == null) return 1;
                    if (tb == null) return -1;
                    return ta.compareTo(tb);
                })
                .map(v -> {
            TrackPointDTO p = new TrackPointDTO();
            p.setTimestamp(v.getTimestamp() == null ? null : fmt.format(v.getTimestamp()));
            p.setLongitude(v.getLongitude() == null ? null : v.getLongitude().doubleValue());
            p.setLatitude(v.getLatitude() == null ? null : v.getLatitude().doubleValue());
            p.setPositioningStatus(v.getPositioningStatus());
            return p;
        }).collect(Collectors.toList());
        DeviceTrackDTO dto = new DeviceTrackDTO();
        dto.setDeviceId(deviceId);
        // 基本设备信息
        BmsDevices dev = bmsDevicesMapper.selectOne(new QueryWrapper<BmsDevices>().eq("device_id", deviceId));
        if (dev != null) {
            dto.setDeviceName(dev.getDeviceName());
            dto.setDeviceSn(dev.getDeviceSn());
            dto.setDeviceType(dev.getDeviceType());
            dto.setStatus(dev.getStatus());
            dto.setFirmwareVersion(dev.getFirmwareVersion());
            dto.setLastOnline(dev.getLastOnline() == null ? null : fmt.format(dev.getLastOnline()));
        }
        // 运行时指标（取最新整车数据）
        VehicleData latest = vehicleDataService.selectLatestByDeviceId(deviceId);
        if (latest != null) {
            dto.setVehicleSpeed(latest.getVehicleSpeed());
            dto.setTotalMileage(latest.getTotalMileage());
            dto.setTotalVoltage(latest.getTotalVoltage());
            dto.setTotalCurrent(latest.getTotalCurrent());
            dto.setSoc(latest.getSoc());
        }
        dto.setTrackList(list);
        return R.ok(dto);
    }

    @GetMapping("/monitor/device/{deviceId}/detail")
    @ApiOperation(value = "设备详细信息", notes = "聚合设备、整车、位置等信息")
    public R<DeviceDetailDTO> deviceDetail(@ApiParam(value = "设备ID", required = true) @PathVariable("deviceId") String deviceId) {
        SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        DeviceDetailDTO out = new DeviceDetailDTO();
        out.setDeviceId(deviceId);
        BmsDevices dev = bmsDevicesMapper.selectOne(new QueryWrapper<BmsDevices>().eq("device_id", deviceId));
        if (dev != null) {
            out.setDeviceName(dev.getDeviceName());
            out.setDeviceSn(dev.getDeviceSn());
            out.setDeviceType(dev.getDeviceType());
            out.setStatus(dev.getStatus());
            out.setFirmwareVersion(dev.getFirmwareVersion());
            out.setLastOnline(dev.getLastOnline() == null ? null : fmt.format(dev.getLastOnline()));
        }
        VehicleData v = vehicleDataService.selectLatestByDeviceId(deviceId);
        if (v != null) {
            out.setTotalVoltage(v.getTotalVoltage());
            out.setTotalCurrent(v.getTotalCurrent());
            out.setSoc(v.getSoc());
            out.setVehicleStatus(v.getVehicleStatus());
            out.setChargingStatus(v.getChargingStatus());
            out.setOperationMode(v.getOperationMode());
            out.setDcdcStatus(v.getDcdcStatus());
            out.setGearPosition(v.getGearPosition());
            out.setInsulationResistance(v.getInsulationResistance());
        }
        VehicleLocation loc = vehicleLocationService.selectLatestByDeviceId(deviceId);
        if (loc != null) {
            out.setLongitude(loc.getLongitude() == null ? null : loc.getLongitude().doubleValue());
            out.setLatitude(loc.getLatitude() == null ? null : loc.getLatitude().doubleValue());
            out.setLocationTime(loc.getTimestamp() == null ? null : fmt.format(loc.getTimestamp()));
            out.setPositioningStatus(loc.getPositioningStatus());
        }
        out.setLastMaintenanceTime(null);
        out.setLastUpgradeTime(null);
        return R.ok(out);
    }

}
