package com.usky.sas.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.jna.Memory;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.usky.common.core.bean.CommonPage;
import com.usky.common.core.exception.BusinessException;
import com.usky.common.mybatis.core.AbstractCrudService;
import com.usky.sas.common.dahua.EM_SEND_SEARCH_TYPE;
import com.usky.sas.common.dahua.InitNetSDKLib;
import com.usky.sas.common.dahua.NET_IN_STARTSERACH_DEVICE;
import com.usky.sas.common.dahua.NET_OUT_STARTSERACH_DEVICE;
import com.usky.sas.common.dahua.NetSDKLib;
import com.usky.sas.common.dahua.RemoteDeviceInfo;
import com.usky.sas.common.entity.SearchDeviceInfo;
import com.usky.sas.common.enums.DeviceTypeEnum;
import com.usky.sas.common.enums.ProtocolEnum;
import com.usky.sas.common.hik.InitSadp;
import com.usky.sas.common.hik.PDEVICE_FIND_CALLBACK_V40;
import com.usky.sas.common.hik.SADP_DEVICE_INFO_V40;
import com.usky.sas.common.hik.Sadp;
import com.usky.sas.common.onvif.StandardOnvifService;
import com.usky.sas.common.onvif.OnvifService;
import com.usky.sas.common.util.GetIpUtils;
import com.usky.sas.domain.SasAlarsasGroupType;
import com.usky.sas.domain.SasConfig;
import com.usky.sas.domain.SasDevice;
import com.usky.sas.domain.SasGis;
import com.usky.sas.domain.SasVideoMonitorGroupType;
import com.usky.sas.enums.SystemTypeCodeEnum;
import com.usky.sas.mapper.SasAlarsasGroupTypeMapper;
import com.usky.sas.mapper.SasDeviceMapper;
import com.usky.sas.mapper.SasGisMapper;
import com.usky.sas.mapper.SasVideoMonitorGroupTypeMapper;
import com.usky.sas.service.SasConfigService;
import com.usky.sas.service.SasDeviceService;
import com.usky.sas.service.dto.agbox.AgboxChannelListVo;
import com.usky.sas.service.dto.agbox.AgboxDeviceListVo;
import com.usky.sas.service.dto.agbox.JsonRpcRequest;
import com.usky.sas.service.vo.*;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 设备信息 服务实现
 */
@Slf4j
@Service
public class SasDeviceServiceImpl extends AbstractCrudService<SasDeviceMapper, SasDevice>
        implements SasDeviceService {

    private static final String AGBOX_DEVICE_PATH = "/agbox/device";
    private static final int MAX_RETRIES = 3;
    private static final int HTTP_TIMEOUT_MS = 30000;

    @Autowired
    private SasAlarsasGroupTypeMapper alarsasGroupTypeMapper;

    @Autowired
    private SasVideoMonitorGroupTypeMapper videoMonitorGroupTypeMapper;

    @Autowired
    private SasConfigService sasConfigService;

    @Autowired
    private SasGisMapper sasGisMapper;

    @Autowired
    private StandardOnvifService standardOnvifService;

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public CommonPage<DeviceHeartbeatResponse> heartbeatPage(DeviceHeartbeatRequest request) {
        IPage<SasDevice> page = new Page<>(request.getCurrent(), request.getSize());
        QueryWrapper<SasDevice> wrapper = new QueryWrapper<>();
        if (request.getDeviceId() != null && !request.getDeviceId().isEmpty()) {
            wrapper.lambda().eq(SasDevice::getDeviceId, request.getDeviceId());
        }
        if (request.getDeviceType() != null) {
            wrapper.lambda().eq(SasDevice::getDeviceType, request.getDeviceType());
        }
        wrapper.orderByDesc("trigger_time");
        page = this.page(page, wrapper);

        List<DeviceHeartbeatResponse> list = page.getRecords().stream().map(d -> {
            DeviceHeartbeatResponse vo = new DeviceHeartbeatResponse();
            vo.setId(d.getId());
            vo.setDeviceId(d.getDeviceId());
            vo.setDeviceType(d.getDeviceType());
            vo.setIpAddr(d.getIpAddr());
            vo.setTriggerTime(d.getTriggerTime());
            vo.setLastHeartbeat(d.getTriggerTime());
            if (d.getTriggerTime() != null) {
                long seconds = ChronoUnit.SECONDS.between(d.getTriggerTime(), LocalDateTime.now());
                vo.setStatus(seconds <= 60 ? "online" : "offline");
            } else {
                vo.setStatus("offline");
            }
            return vo;
        }).collect(Collectors.toList());

        return new CommonPage<>(list, page.getTotal(), page.getCurrent(), page.getSize());
    }

    @Override
    public CommonPage<DeviceConfigVO> configPage(DeviceConfigPageRequest request) {
        IPage<SasDevice> page = new Page<>(request.getCurrent(), request.getSize());
        QueryWrapper<SasDevice> wrapper = new QueryWrapper<>();
        if (request.getAlarmGroupType() != null && !request.getAlarmGroupType().isEmpty()) {
            wrapper.lambda().eq(SasDevice::getAlarsasGroupType, request.getAlarmGroupType());
        }
        if (request.getVideoGroupType() != null && !request.getVideoGroupType().isEmpty()) {
            wrapper.lambda().eq(SasDevice::getVideoGroupType, request.getVideoGroupType());
        }
        if (request.getDeviceType() != null) {
            wrapper.lambda().eq(SasDevice::getDeviceType, request.getDeviceType());
        }
        if (request.getDeviceId() != null && !request.getDeviceId().isEmpty()) {
            wrapper.lambda().eq(SasDevice::getDeviceId, request.getDeviceId());
        }
        if (request.getIpAddr() != null && !request.getIpAddr().isEmpty()) {
            wrapper.lambda().eq(SasDevice::getIpAddr, request.getIpAddr());
        }
        wrapper.orderByDesc("create_time");
        page = this.page(page, wrapper);

        List<DeviceConfigVO> list = page.getRecords().stream()
                .map(this::toConfigVOEnriched)
                .collect(Collectors.toList());
        return new CommonPage<>(list, page.getTotal(), page.getCurrent(), page.getSize());
    }

    @Override
    public String createConfig(DeviceConfigSaveRequest request) {
        SasDevice device = new SasDevice();
        device.setId(UUID.randomUUID().toString());
        applyConfig(device, request);
        // 经纬度信息：有任一非空则创建 gis 记录并关联
        if (request.getLon() != null || request.getLat() != null || request.getAlt() != null) {
            String gisId = UUID.randomUUID().toString();
            SasGis gis = new SasGis();
            gis.setId(gisId);
            gis.setLon(request.getLon() != null ? request.getLon() : BigDecimal.ZERO);
            gis.setLat(request.getLat() != null ? request.getLat() : BigDecimal.ZERO);
            gis.setAlt(request.getAlt() != null ? request.getAlt() : BigDecimal.ZERO);
            sasGisMapper.insert(gis);
            device.setGisId(gisId);
        }
        LocalDateTime now = LocalDateTime.now();
        device.setCreateTime(now);
        device.setUpdateTime(now);
        this.save(device);
        return device.getId();
    }

    @Override
    public void updateConfig(String id, DeviceConfigSaveRequest request) {
        SasDevice device = this.getById(id);
        if (device == null) {
            return;
        }
        applyConfig(device, request);
        // 若传入了经纬度/高度，则更新或创建 gis 记录
        if (request.getLon() != null || request.getLat() != null || request.getAlt() != null) {
            String gisId = device.getGisId();
            SasGis gis = null;
            if (gisId != null) {
                gis = sasGisMapper.selectById(gisId);
            }
            if (gis == null) {
                gisId = UUID.randomUUID().toString();
                gis = new SasGis();
                gis.setId(gisId);
            }
            if (request.getLon() != null) {
                gis.setLon(request.getLon());
            }
            if (request.getLat() != null) {
                gis.setLat(request.getLat());
            }
            if (request.getAlt() != null) {
                gis.setAlt(request.getAlt());
            }
            if (device.getGisId() == null) {
                sasGisMapper.insert(gis);
                device.setGisId(gisId);
            } else {
                sasGisMapper.updateById(gis);
            }
        }
        device.setUpdateTime(LocalDateTime.now());
        this.updateById(device);
    }

    @Override
    public void deleteConfig(String id) {
        this.removeById(id);
    }

    @Override
    public void deleteConfigBatch(List<String> ids) {
        if (ids == null || ids.isEmpty()) {
            return;
        }
        this.removeByIds(ids);
    }

    @Override
    public DeviceDetailVO getDetail(String id) {
        SasDevice device = this.getById(id);
        if (device == null) {
            return null;
        }
        DeviceDetailVO vo = new DeviceDetailVO();
        vo.setId(device.getId());
        vo.setDeviceId(device.getDeviceId());
        vo.setChannel(device.getChannel());
        vo.setDeviceType(device.getDeviceType());
        vo.setDeviceTypeName(deviceTypeName(device.getDeviceType()));
        vo.setIpAddr(device.getIpAddr());
        vo.setPort(device.getPort());
        vo.setUsername(device.getUsername());
        vo.setShield(device.getShield());
        vo.setVillageCode(device.getVillageCode());
        vo.setHouseCode(device.getHouseCode());
        vo.setAddress(device.getAddress());
        vo.setVideoGroupType(device.getVideoGroupType());
        vo.setVideoGroupTypeName(groupName(device.getVideoGroupType(), true));
        vo.setAlarmGroupType(device.getAlarsasGroupType());
        vo.setAlarmGroupTypeName(groupName(device.getAlarsasGroupType(), false));
        vo.setNote(device.getNote());
        vo.setBindingIpc(device.getBindingIpc());
        vo.setIsBinding(device.getIsBinding());
        vo.setCreateTime(device.getCreateTime());
        vo.setUpdateTime(device.getUpdateTime());
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setAlarmGroup(String id, String alarmGroupType) {
        SasDevice device = this.getById(id);
        if (device == null) {
            return;
        }
        device.setAlarsasGroupType(alarmGroupType);
        device.setUpdateTime(LocalDateTime.now());
        this.updateById(device);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setAlarmGroupBatch(List<String> ids, String alarmGroupType) {
        if (ids == null || ids.isEmpty()) {
            return;
        }
        for (String id : ids) {
            SasDevice device = this.getById(id);
            if (device != null) {
                device.setAlarsasGroupType(alarmGroupType);
                device.setUpdateTime(LocalDateTime.now());
                this.updateById(device);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setVideoGroup(String id, String videoGroupType) {
        SasDevice device = this.getById(id);
        if (device == null) {
            return;
        }
        device.setVideoGroupType(videoGroupType);
        device.setUpdateTime(LocalDateTime.now());
        this.updateById(device);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setVideoGroupBatch(List<String> ids, String videoGroupType) {
        if (ids == null || ids.isEmpty()) {
            return;
        }
        for (String id : ids) {
            SasDevice device = this.getById(id);
            if (device != null) {
                device.setVideoGroupType(videoGroupType);
                device.setUpdateTime(LocalDateTime.now());
                this.updateById(device);
            }
        }
    }

    @Override
    public List<DeviceIPCItemVO> getNotBindIPCList() {
        LambdaQueryWrapper<SasDevice> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SasDevice::getDeviceType, SystemTypeCodeEnum.video.getCode())
                .and(w -> w.isNull(SasDevice::getIsBinding).or().eq(SasDevice::getIsBinding, false));
        List<SasDevice> list = this.list(wrapper);
        if (list == null) {
            return Collections.emptyList();
        }
        return list.stream().map(d -> {
            DeviceIPCItemVO vo = new DeviceIPCItemVO();
            vo.setId(d.getId());
            vo.setDeviceId(d.getDeviceId());
            vo.setChannel(d.getChannel());
            vo.setIpAddr(d.getIpAddr());
            vo.setNote(d.getNote());
            return vo;
        }).collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void bindingIPC(String id, String ipcId) {
        SasDevice device = this.getById(id);
        if (device == null) {
            return;
        }
        if (ipcId != null && ipcId.equals(device.getBindingIpc())) {
            return;
        }
        device.setBindingIpc(ipcId);
        device.setUpdateTime(LocalDateTime.now());
        this.updateById(device);
        SasDevice ipc = this.getById(ipcId);
        if (ipc != null) {
            ipc.setIsBinding(true);
            ipc.setUpdateTime(LocalDateTime.now());
            this.updateById(ipc);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unbindIPC(String id) {
        SasDevice device = this.getById(id);
        if (device == null || device.getBindingIpc() == null) {
            return;
        }
        String ipcId = device.getBindingIpc();
        // 显式更新绑定关系，避免依赖通用 updateById 未更新 binding_ipc 的问题
        this.lambdaUpdate()
                .set(SasDevice::getBindingIpc, null)
                .set(SasDevice::getUpdateTime, LocalDateTime.now())
                .eq(SasDevice::getId, id)
                .update();

        this.lambdaUpdate()
                .set(SasDevice::getIsBinding, false)
                .set(SasDevice::getUpdateTime, LocalDateTime.now())
                .eq(SasDevice::getId, ipcId)
                .update();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void syncAgboxDevice() {
        SasConfig config = sasConfigService.getConfig();
        if (config == null || config.getHost() == null || config.getHost().isEmpty()) {
            log.warn("同步 AG 设备：未配置 Agbox 或 host 为空，跳过");
            return;
        }
        String baseUrl = buildAgboxBaseUrl(config.getHost(), config.getPort());
        String key = config.getKeyds() != null ? config.getKeyds() : "";
        for (SystemTypeCodeEnum type : SystemTypeCodeEnum.values()) {
            String deviceUrl = baseUrl + AGBOX_DEVICE_PATH + "/" + type.getAgboxType();
            try {
                AgboxDeviceListVo listVo = postDeviceList(deviceUrl, key);
                if (listVo == null || listVo.getResult() == null || listVo.getResult().getDeviceList() == null
                        || listVo.getResult().getDeviceList().isEmpty()) {
                    continue;
                }
                for (AgboxDeviceListVo.AgboxDeviceInfo dev : listVo.getResult().getDeviceList()) {
                    if (dev.getDeviceId() == null || dev.getDeviceId().isEmpty()) {
                        continue;
                    }
                    List<AgboxChannelListVo.AgboxChannelInfo> channels = postChannelList(deviceUrl, key, dev.getDeviceId());
                    if (channels != null && !channels.isEmpty()) {
                        for (AgboxChannelListVo.AgboxChannelInfo ch : channels) {
                            saveDeviceFromAgbox(dev, ch, type.getCode());
                        }
                    } else {
                        saveDeviceFromAgbox(dev, null, type.getCode());
                    }
                }
            } catch (Exception e) {
                log.warn("同步 AG 设备 type={} 失败: {}", type.getAgboxType(), e.getMessage());
            }
        }
    }

    private AgboxDeviceListVo postDeviceList(String deviceUrl, String key) throws Exception {
        JsonRpcRequest rpc = new JsonRpcRequest("getDeviceList", null, null);
        Map<String, Object> body = new HashMap<>();
        body.put("key", key);
        body.put("json", JSONUtil.toJsonStr(rpc));
        String res = postWithRetry(deviceUrl, body);
        return objectMapper.readValue(res, AgboxDeviceListVo.class);
    }

    private List<AgboxChannelListVo.AgboxChannelInfo> postChannelList(String deviceUrl, String key, String deviceId) {
        try {
            Map<String, Object> params = new HashMap<>();
            params.put("deviceId", deviceId);
            JsonRpcRequest rpc = new JsonRpcRequest("getDeviceChannelList", params, null);
            Map<String, Object> body = new HashMap<>();
            body.put("key", key);
            body.put("json", JSONUtil.toJsonStr(rpc));
            String res = postWithRetry(deviceUrl, body);
            AgboxChannelListVo vo = objectMapper.readValue(res, AgboxChannelListVo.class);
            if (vo != null && vo.getResult() != null && vo.getResult().getChannelList() != null) {
                return vo.getResult().getChannelList();
            }
        } catch (Exception e) {
            log.debug("getDeviceChannelList deviceId={} 失败: {}", deviceId, e.getMessage());
        }
        return Collections.emptyList();
    }

    private void saveDeviceFromAgbox(AgboxDeviceListVo.AgboxDeviceInfo dev,
                                     AgboxChannelListVo.AgboxChannelInfo channel,
                                     int deviceTypeCode) {
        String deviceId = dev.getDeviceId();
        int channelNum = channel != null ? (channel.getChannel() != null ? channel.getChannel() : 0) : 0;
        LambdaQueryWrapper<SasDevice> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SasDevice::getDeviceId, deviceId)
                .eq(SasDevice::getDeviceType, deviceTypeCode)
                .eq(SasDevice::getChannel, channelNum);
        if (this.count(wrapper) > 0) {
            return;
        }
        String gisId = ensureGis(dev);
        SasDevice device = new SasDevice();
        device.setId(UUID.randomUUID().toString());
        device.setDeviceId(deviceId);
        device.setChannel(channelNum);
        device.setDeviceType(deviceTypeCode);
        String ipAddr = (channel != null && channel.getAddr() != null && !channel.getAddr().isEmpty())
                ? channel.getAddr() : dev.getIpAddr();
        device.setIpAddr(ipAddr);
        device.setVillageCode(dev.getVillageCode());
        device.setAddress(dev.getAddress());
        device.setNote(channel != null && channel.getPlaceName() != null ? channel.getPlaceName() : dev.getNote());
        device.setGisId(gisId);
        device.setTriggerTime(parseTriggerTime(dev.getTriggerTime()));
        LocalDateTime now = LocalDateTime.now();
        device.setCreateTime(now);
        device.setUpdateTime(now);
        this.save(device);
    }

    private String ensureGis(AgboxDeviceListVo.AgboxDeviceInfo dev) {
        if (dev.getGis() == null) {
            return null;
        }
        AgboxDeviceListVo.AgboxGisInfo g = dev.getGis();
        if (g.getLon() == null && g.getLat() == null) {
            return null;
        }
        String gisId = UUID.randomUUID().toString();
        SasGis gis = new SasGis();
        gis.setId(gisId);
        gis.setLon(g.getLon() != null ? BigDecimal.valueOf(g.getLon()) : BigDecimal.ZERO);
        gis.setLat(g.getLat() != null ? BigDecimal.valueOf(g.getLat()) : BigDecimal.ZERO);
        gis.setAlt(g.getAlt() != null ? BigDecimal.valueOf(g.getAlt()) : BigDecimal.ZERO);
        sasGisMapper.insert(gis);
        return gisId;
    }

    private LocalDateTime parseTriggerTime(String triggerTime) {
        if (triggerTime == null || triggerTime.isEmpty()) {
            return null;
        }
        String s = triggerTime.trim().replace(' ', 'T');
        if (s.length() > 19) {
            s = s.substring(0, 19);
        }
        try {
            return LocalDateTime.parse(s, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        } catch (Exception e1) {
            try {
                return LocalDateTime.parse(triggerTime.trim(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            } catch (Exception e2) {
                return null;
            }
        }
    }

    private String buildAgboxBaseUrl(String host, String port) {
        if (host == null || host.isEmpty()) {
            return "";
        }
        if (host.startsWith("http://") || host.startsWith("https://")) {
            return host;
        }
        return "http://" + host;
    }

    private String postWithRetry(String url, Map<String, Object> requestBody) {
        Exception lastEx = null;
        for (int i = 0; i < MAX_RETRIES; i++) {
            try {
                return HttpRequest.post(url)
                        .form(requestBody)
                        .timeout(HTTP_TIMEOUT_MS)
                        .execute()
                        .body();
            } catch (Exception e) {
                lastEx = e;
                if (i < MAX_RETRIES - 1) {
                    log.warn("AG 设备接口请求失败，第 {} 次重试：{}", i + 1, e.getMessage());
                    try {
                        Thread.sleep(1000L * (i + 1));
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new RuntimeException("重试被中断", ie);
                    }
                }
            }
        }
        throw new RuntimeException("AG 接口请求失败，重试 " + MAX_RETRIES + " 次后仍失败", lastEx);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addDeviceBatch(DeviceBatchAddRequest request) {
        if (request == null
                || request.getDeviceId() == null || request.getDeviceId().isEmpty()
                || request.getDeviceType() == null
                || request.getPort() == null
                || request.getChannels() == null || request.getChannels() <= 0) {
            throw new BusinessException("批量新增设备参数不完整，设备编号、设备类型、端口、通道数量为必填");
        }
        int channels = request.getChannels();
        for (int ch = 0; ch < channels; ch++) {
            LambdaQueryWrapper<SasDevice> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(SasDevice::getDeviceId, request.getDeviceId())
                    .eq(SasDevice::getDeviceType, request.getDeviceType())
                    .eq(SasDevice::getChannel, ch);
            if (this.count(wrapper) > 0) {
                continue;
            }
            SasDevice device = new SasDevice();
            device.setId(UUID.randomUUID().toString());
            device.setDeviceId(request.getDeviceId());
            device.setDeviceType(request.getDeviceType());
            device.setChannel(ch);
            device.setIpAddr(request.getIpAddr());
            device.setPort(request.getPort());
            device.setUsername(request.getUsername());
            device.setPassword(request.getPassword());
            device.setVideoType(request.getVideoDeviceType());
            device.setVideoProtocol(request.getVideoProtocol());
            device.setAddress(request.getAddress());
            device.setHouseCode(request.getHouseCode());
            device.setNote(request.getNote());
            device.setIsBinding(false);
            device.setShield(false);
            LocalDateTime now = LocalDateTime.now();
            device.setCreateTime(now);
            device.setUpdateTime(now);
            this.save(device);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String importDeviceByExcel(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new BusinessException("请选择一个Excel文件上传");
        }
        ImportParams params = new ImportParams();
        params.setHeadRows(1);
        params.setTitleRows(1);
        String err = "文件导入失败";
        try {
            List<DeviceImportExcelVO> rows = ExcelImportUtil.importExcel(
                    file.getInputStream(), DeviceImportExcelVO.class, params);
            if (rows == null || rows.isEmpty()) {
                throw new BusinessException("文件不能为空");
            }
            int index = 0;
            for (DeviceImportExcelVO row : rows) {
                index++;
                // 跳过空行：设备编号、IP、端口、设备类型都为空时视为空行
                if ((row.getDeviceId() == null || row.getDeviceId().trim().isEmpty())
                        && (row.getIpAddr() == null || row.getIpAddr().trim().isEmpty())
                        && row.getPort() == null
                        && row.getDeviceType() == null) {
                    continue;
                }
                if (row.getDeviceType() == null
                        || row.getDeviceId() == null || row.getDeviceId().isEmpty()
                        || row.getIpAddr() == null || row.getIpAddr().isEmpty()
                        || row.getPort() == null) {
                    throw new BusinessException("第" + (index + 1) + "行数据不完整，设备类型、设备编号、设备IP、端口为必填");
                }
                int channel = row.getChannel() != null ? row.getChannel() : 0;
                LambdaQueryWrapper<SasDevice> wrapper = new LambdaQueryWrapper<>();
                wrapper.eq(SasDevice::getDeviceId, row.getDeviceId())
                        .eq(SasDevice::getDeviceType, row.getDeviceType())
                        .eq(SasDevice::getChannel, channel);
                if (this.count(wrapper) > 0) {
                    continue;
                }
                String gisId = null;
                if (row.getLon() != null || row.getLat() != null || row.getAlt() != null) {
                    SasGis gis = new SasGis();
                    gisId = UUID.randomUUID().toString();
                    gis.setId(gisId);
                    gis.setLon(row.getLon() != null ? row.getLon() : BigDecimal.ZERO);
                    gis.setLat(row.getLat() != null ? row.getLat() : BigDecimal.ZERO);
                    gis.setAlt(row.getAlt() != null ? row.getAlt() : BigDecimal.ZERO);
                    sasGisMapper.insert(gis);
                }
                SasDevice device = new SasDevice();
                device.setId(UUID.randomUUID().toString());
                device.setDeviceId(row.getDeviceId());
                device.setDeviceType(row.getDeviceType());
                device.setChannel(channel);
                device.setIpAddr(row.getIpAddr());
                device.setPort(row.getPort());
                device.setUsername(row.getUsername());
                device.setPassword(row.getPassword());
                device.setVideoType(row.getVideoDeviceType());
                device.setVideoProtocol(row.getVideoProtocol());
                device.setNote(row.getNote());
                device.setAddress(row.getAddress());
                device.setHouseCode(row.getHouseCode());
                device.setGisId(gisId);
                device.setIsBinding(false);
                device.setShield(false);
                LocalDateTime now = LocalDateTime.now();
                device.setCreateTime(now);
                device.setUpdateTime(now);
                this.save(device);
            }
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("批量导入设备失败", e);
            throw new BusinessException(err);
        }
        return "设备批量导入成功";
    }

    @Override
    public List<SearchDeviceInfoVO> unitySearchDevice(Integer second, Integer code) {
        if (second == null || second <= 0) {
            second = 3;
        }
        if (code == null) {
            throw new BusinessException("协议类型 code 不能为空");
        }

        // 海康 SADP 扫描
        if (code.equals(ProtocolEnum.HIK.getCode())) {
            Sadp sadp = InitSadp.sadp;
            log.info("SADP 释放资源开始");
            sadp.SADP_Clearup();
            log.info("SADP 正在关闭");
            sadp.SADP_Stop();

            final List<SearchDeviceInfo> deviceInfoList = new ArrayList<>();
            PDEVICE_FIND_CALLBACK_V40 callback = new PDEVICE_FIND_CALLBACK_V40() {
                @Override
                public void invoke(SADP_DEVICE_INFO_V40 lpDeviceInfo, Pointer pUserData) {
                    log.info("SADP 回调：发现设备");
                    SearchDeviceInfo info = new SearchDeviceInfo();
                    info.setIpAddr(bytesToString(lpDeviceInfo.struSadpDeviceInfo.szIPv4Address));
                    info.setPort(lpDeviceInfo.struSadpDeviceInfo.dwPort);
                    info.setDeviceId(String.valueOf(lpDeviceInfo.struSadpDeviceInfo.dwDeviceType));
                    info.setSerialNo(bytesToString(lpDeviceInfo.struSadpDeviceInfo.szSerialNO));
                    info.setNote(bytesToString(lpDeviceInfo.struSadpDeviceInfo.szDevDesc));
                    info.setOemInfo(bytesToString(lpDeviceInfo.struSadpDeviceInfo.szOEMinfo));
                    info.setMacAddr(bytesToMacString(lpDeviceInfo.struSadpDeviceInfo.szMAC));
                    info.setNetMask(bytesToString(lpDeviceInfo.struSadpDeviceInfo.szIPv4SubnetMask));
                    info.setGateway(bytesToString(lpDeviceInfo.struSadpDeviceInfo.szIPv4Gateway));

                    SasDevice device = baseMapper.selectOne(
                            new LambdaQueryWrapper<SasDevice>()
                                    .eq(SasDevice::getMacAddr, info.getMacAddr())
                    );
                    info.setIsAdded(device != null);
                    info.setIsActive(StrUtil.utf8Str(lpDeviceInfo.struSadpDeviceInfo.byActivated).equals("0"));
                    info.setProtocol(ProtocolEnum.HIK.getCode());
                    deviceInfoList.add(info);
                }
            };

            boolean result = sadp.SADP_Start_V40(callback);
            sadp.SADP_SetAutoRequestInterval(0);
            try {
                Thread.sleep(second * 1000L);
                log.info("SADP 正在停止");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            if (!result) {
                log.error("SADP 查询失败，错误码: {}", sadp.SADP_GetLastError());
            }

            List<SearchDeviceInfo> deviceList = sortByIpAddr(deviceInfoList);
            deviceList = distinctByMac(deviceList);
            log.info("海康搜索结果：{}", JSON.toJSONString(deviceList));
            return toVOList(deviceList);
        }

        // ONVIF 扫描
        if (code.equals(ProtocolEnum.ONVIF.getCode())) {
            List<SearchDeviceInfo> list = OnvifService.searchDeviceByOnvif(second, baseMapper);
            if (list == null) {
                list = Collections.emptyList();
            }
            list.forEach(d -> d.setProtocol(ProtocolEnum.ONVIF.getCode()));
            List<SearchDeviceInfo> deviceList = sortByIpAddr(list);
            deviceList = distinctByMac(deviceList);
            log.info("ONVIF 搜索结果：{}", JSON.toJSONString(deviceList));
            return toVOList(deviceList);
        }

        // 大华 NetSDK 扫描
        if (code.equals(ProtocolEnum.DA_HUA.getCode())) {
            NET_IN_STARTSERACH_DEVICE inParam = new NET_IN_STARTSERACH_DEVICE();
            final List<SearchDeviceInfo> deviceInfoList = new ArrayList<>();
            NetSDKLib.fSearchDevicesCBEx cb = new NetSDKLib.fSearchDevicesCBEx() {
                @Override
                public void invoke(Long lSearchHandle, Pointer pDevNetInfo, Pointer pUserData) {
                    NetSDKLib.DEVICE_NET_INFO_EX lpDeviceInfo = new NetSDKLib.DEVICE_NET_INFO_EX(pDevNetInfo);
                    lpDeviceInfo.read();

                    SearchDeviceInfo info = new SearchDeviceInfo();
                    info.setIpAddr(new String(lpDeviceInfo.szIP).trim());
                    info.setPort(lpDeviceInfo.nPort);
                    info.setDeviceId(new String(lpDeviceInfo.szDevName).trim());
                    info.setSerialNo(new String(lpDeviceInfo.szSerialNo).trim());
                    info.setOemInfo(new String(lpDeviceInfo.szVendor).trim());
                    info.setMacAddr(new String(lpDeviceInfo.szMac).trim());
                    info.setNetMask(new String(lpDeviceInfo.szSubmask).trim());
                    info.setGateway(new String(lpDeviceInfo.szGateway).trim());

                    SasDevice device = baseMapper.selectOne(
                            new LambdaQueryWrapper<SasDevice>()
                                    .eq(SasDevice::getMacAddr, info.getMacAddr())
                    );
                    info.setIsAdded(device != null);
                    info.setIsActive(StrUtil.utf8Str(lpDeviceInfo.byInitStatus).equals("2"));
                    info.setProtocol(ProtocolEnum.DA_HUA.getCode());
                    deviceInfoList.add(info);
                }
            };

            inParam.cbSearchDevices = cb;
            byte[] localIpBytes = GetIpUtils.getServerIP().getBytes();
            System.arraycopy(localIpBytes, 0, inParam.szLocalIp, 0, localIpBytes.length);
            inParam.emSendType = EM_SEND_SEARCH_TYPE.EM_SEND_SEARCH_TYPE_BROADCAST.ordinal();

            Pointer pInBuf = new Memory(inParam.size());
            SetStructDataToPointer(inParam, pInBuf, 0L);

            NET_OUT_STARTSERACH_DEVICE outParam = new NET_OUT_STARTSERACH_DEVICE();
            Pointer pOutBuf = new Memory(outParam.size());
            SetStructDataToPointer(outParam, pOutBuf, 0L);

            Long searchHandle = InitNetSDKLib.dhNetSDK.CLIENT_StartSearchDevicesEx(pInBuf, pOutBuf);
            if (searchHandle == 0L) {
                log.error("启动大华设备搜索失败，错误码：{}", InitNetSDKLib.dhNetSDK.CLIENT_GetLastError());
                return Collections.emptyList();
            }

            log.info("大华设备搜索已启动，本机IP: {}，等待设备响应...", GetIpUtils.getServerIP());
            try {
                Thread.sleep(second * 1000L);
                boolean stopped = InitNetSDKLib.dhNetSDK.CLIENT_StopSearchDevices(searchHandle);
                if (!stopped) {
                    log.error("关闭大华设备搜索失败，错误码：{}", InitNetSDKLib.dhNetSDK.CLIENT_GetLastError());
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            List<SearchDeviceInfo> deviceList = sortByIpAddr(deviceInfoList);
            deviceList = distinctByMac(deviceList);
            log.info("大华搜索结果：{}", JSON.toJSONString(deviceList));
            return toVOList(deviceList);
        }

        throw new BusinessException("不支持的协议类型 code=" + code);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<String> unityAddSearchDevice(UnityAddSearchDeviceRequest request) {
        if (request == null || request.getCode() == null || request.getDevices() == null || request.getDevices().isEmpty()) {
            throw new BusinessException("统一扫描添加设备参数不能为空");
        }
        log.info("统一扫描添加设备，请求参数：{}", JSON.toJSONString(request));

        List<SasDevice> toSave = new ArrayList<>();
        List<String> ids = new ArrayList<>();

        Integer code = request.getCode();

        // 海康
        if (code.equals(ProtocolEnum.HIK.getCode())) {
            for (SearchDeviceAddRequest bo : request.getDevices()) {
                if (bo == null) continue;
                if (Objects.equals(bo.getVideoType(), DeviceTypeEnum.IPC.getCode())) {
                    SasDevice device = BeanUtil.toBean(bo, SasDevice.class);
                    String id = IdUtil.randomUUID();
                    device.setId(id);
                    SasGis gis = new SasGis();
                    String gisId = IdUtil.randomUUID();
                    gis.setId(gisId);
                    sasGisMapper.insert(gis);
                    device.setGisId(gisId);
                    device.setChannel(1);
                    device.setDeviceType(SystemTypeCodeEnum.video.getCode());
                    device.setVideoProtocol(ProtocolEnum.HIK.getCode());
                    device.setIsBinding(false);
                    device.setShield(false);
                    toSave.add(device);
                    ids.add(id);
                }
            }
        }
        // ONVIF
        else if (code.equals(ProtocolEnum.ONVIF.getCode())) {
            for (SearchDeviceAddRequest bo : request.getDevices()) {
                if (bo == null) continue;
                if (Objects.equals(bo.getVideoType(), DeviceTypeEnum.IPC.getCode())) {
                    SasDevice device = BeanUtil.toBean(bo, SasDevice.class);
                    String id = IdUtil.randomUUID();
                    device.setId(id);
                    SasGis gis = new SasGis();
                    String gisId = IdUtil.randomUUID();
                    gis.setId(gisId);
                    sasGisMapper.insert(gis);
                    device.setGisId(gisId);
                    device.setDeviceType(SystemTypeCodeEnum.video.getCode());
                    device.setShield(false);
                    device.setChannel(1);
                    device.setVideoProtocol(ProtocolEnum.ONVIF.getCode());
                    toSave.add(device);
                    ids.add(id);
                }
            }
        }
        // 大华
        else if (code.equals(ProtocolEnum.DA_HUA.getCode())) {
            for (SearchDeviceAddRequest bo : request.getDevices()) {
                if (bo == null) continue;
                if (Objects.equals(bo.getVideoType(), DeviceTypeEnum.IPC.getCode())) {
                    SasDevice device = BeanUtil.toBean(bo, SasDevice.class);
                    String id = IdUtil.randomUUID();
                    device.setId(id);
                    SasGis gis = new SasGis();
                    String gisId = IdUtil.randomUUID();
                    gis.setId(gisId);
                    sasGisMapper.insert(gis);
                    device.setGisId(gisId);
                    device.setDeviceType(SystemTypeCodeEnum.video.getCode());
                    device.setVideoProtocol(ProtocolEnum.DA_HUA.getCode());
                    device.setIsBinding(false);
                    device.setShield(false);
                    device.setChannel(1);
                    toSave.add(device);
                    ids.add(id);
                }
            }
        } else {
            throw new BusinessException("不支持的协议类型 code=" + code);
        }

        // 过滤已存在的设备（按 macAddr + deviceType + channel）
        if (!toSave.isEmpty()) {
            Iterator<SasDevice> it = toSave.iterator();
            while (it.hasNext()) {
                SasDevice d = it.next();
                LambdaQueryWrapper<SasDevice> wrapper = new LambdaQueryWrapper<>();
                wrapper.eq(SasDevice::getMacAddr, d.getMacAddr())
                        .eq(SasDevice::getDeviceType, d.getDeviceType())
                        .eq(SasDevice::getChannel, d.getChannel());
                if (this.count(wrapper) > 0) {
                    it.remove();
                }
            }
            if (!toSave.isEmpty()) {
                LocalDateTime now = LocalDateTime.now();
                for (SasDevice d : toSave) {
                    d.setCreateTime(now);
                    d.setUpdateTime(now);
                }
                this.saveBatch(toSave);
            }
        }

        return ids;
    }

    @Override
    public void updateHikDeviceIp(UpdateDeviceIpAddrRequest request) {
        if (request == null
                || StrUtil.isBlank(request.getIpAddr())
                || request.getPort() == null
                || StrUtil.isBlank(request.getNetMask())
                || StrUtil.isBlank(request.getGateway())
                || StrUtil.isBlank(request.getMacAddr())) {
            throw new BusinessException("修改设备IP参数不完整");
        }

        String setIp = request.getIpAddr();
        String netmask = request.getNetMask();
        String gateway = request.getGateway();
        String macAddr = request.getMacAddr();
        String password = request.getPassword() != null ? request.getPassword() : "";

        Sadp.SADP_DEV_NET_PARAM netParam = new Sadp.SADP_DEV_NET_PARAM();

        byte[] ipBytes = setIp.getBytes(StandardCharsets.US_ASCII);
        System.arraycopy(ipBytes, 0, netParam.szIPv4Address, 0,
                Math.min(ipBytes.length, netParam.szIPv4Address.length));

        byte[] maskBytes = netmask.getBytes(StandardCharsets.US_ASCII);
        System.arraycopy(maskBytes, 0, netParam.szIPv4SubNetMask, 0,
                Math.min(maskBytes.length, netParam.szIPv4SubNetMask.length));

        byte[] gwBytes = gateway.getBytes(StandardCharsets.US_ASCII);
        System.arraycopy(gwBytes, 0, netParam.szIPv4Gateway, 0,
                Math.min(gwBytes.length, netParam.szIPv4Gateway.length));

        netParam.wPort = request.getPort().shortValue();
        netParam.wHttpPort = 80;
        netParam.byDhcpEnable = 0;
        netParam.byIPv6MaskLen = 64;

        Sadp.SADP_DEV_RET_NET_PARAM retNetParam = new Sadp.SADP_DEV_RET_NET_PARAM();

        long startTime = System.currentTimeMillis();
        boolean flag = InitSadp.sadp.SADP_ModifyDeviceNetParam_V40(macAddr, password, netParam, retNetParam, retNetParam.size());
        long endTime = System.currentTimeMillis();
        log.info("修改设备IP耗时: {}s", (endTime - startTime) / 1000L);

        if (!flag) {
            int error = InitSadp.sadp.SADP_GetLastError();
            log.error("修改设备IP失败 : errorCode {}", error);
            throw new BusinessException("修改设备IP失败，错误码: " + error);
        }

        log.info("修改设备IP成功");
    }

    @Override
    public List<DeviceProtocolOptionVO> getProtocolCodes() {
        List<DeviceProtocolOptionVO> list = new ArrayList<>();
        for (ProtocolEnum p : ProtocolEnum.values()) {
            DeviceProtocolOptionVO vo = new DeviceProtocolOptionVO();
            vo.setCode(p.getCode());
            vo.setName(p.getDesc());
            list.add(vo);
        }
        return list;
    }

    @Override
    public List<DeviceVideoTypeOptionVO> getVideoTypeCodes() {
        List<DeviceVideoTypeOptionVO> list = new ArrayList<>();
        for (DeviceTypeEnum t : DeviceTypeEnum.values()) {
            DeviceVideoTypeOptionVO vo = new DeviceVideoTypeOptionVO();
            vo.setCode(t.getCode());
            vo.setName(t.getDesc());
            list.add(vo);
        }
        return list;
    }

    private String deviceTypeName(Integer code) {
        SystemTypeCodeEnum e = SystemTypeCodeEnum.getByCode(code);
        return e != null ? e.getMessage() : null;
    }

    private String groupName(String groupId, boolean video) {
        if (groupId == null || groupId.isEmpty()) {
            return null;
        }
        if (video) {
            SasVideoMonitorGroupType g = videoMonitorGroupTypeMapper.selectById(groupId);
            return g != null ? g.getName() : null;
        } else {
            SasAlarsasGroupType g = alarsasGroupTypeMapper.selectById(groupId);
            return g != null ? g.getName() : null;
        }
    }

    private DeviceConfigVO toConfigVOEnriched(SasDevice d) {
        DeviceConfigVO vo = toConfigVO(d);
        vo.setDeviceTypeName(deviceTypeName(d.getDeviceType()));
        vo.setAlarmGroupTypeName(groupName(d.getAlarsasGroupType(), false));
        vo.setVideoGroupTypeName(groupName(d.getVideoGroupType(), true));
        // 完整报警联动组/视频监控组对象
        if (d.getAlarsasGroupType() != null && !d.getAlarsasGroupType().isEmpty()) {
            SasAlarsasGroupType alarmGroup = alarsasGroupTypeMapper.selectById(d.getAlarsasGroupType());
            vo.setAlarmGroupTypes(alarmGroup);
        }
        if (d.getVideoGroupType() != null && !d.getVideoGroupType().isEmpty()) {
            SasVideoMonitorGroupType videoGroup = videoMonitorGroupTypeMapper.selectById(d.getVideoGroupType());
            vo.setVideoGroupTypes(videoGroup);
        }
        // 若为 NVR，则展示远端 IPC IP 地址
        if (d.getVideoType() != null && DeviceTypeEnum.NVR.getCode().equals(d.getVideoType())
                && d.getRemoteIpcIpAddr() != null && !d.getRemoteIpcIpAddr().isEmpty()) {
            vo.setIpAddr(d.getRemoteIpcIpAddr());
        }
        // 绑定 IPC 信息
        if (d.getBindingIpc() != null && !d.getBindingIpc().isEmpty()) {
            SasDevice ipc = this.getById(d.getBindingIpc());
            if (ipc != null) {
                vo.setIpcId(ipc.getId());
                if (ipc.getVideoType() != null && DeviceTypeEnum.NVR.getCode().equals(ipc.getVideoType())) {
                    vo.setIpcName(ipc.getDeviceId() + "-" + ipc.getChannel());
                } else {
                    vo.setIpcName(ipc.getDeviceId());
                }
            }
        }
        return vo;
    }

    private void applyConfig(SasDevice device, DeviceConfigSaveRequest request) {
        device.setDeviceId(request.getDeviceId());
        device.setChannel(request.getChannel());
        device.setDeviceType(request.getDeviceType());
        device.setIpAddr(request.getIpAddr());
        device.setPort(request.getPort());
        device.setVideoProtocol(request.getVideoProtocol());
        device.setVideoType(request.getVideoType());
        device.setUsername(request.getUsername());
        device.setPassword(request.getPassword());
        device.setShield(request.getShield());
        device.setVillageCode(request.getVillageCode());
        device.setHouseCode(request.getHouseCode());
        device.setAddress(request.getAddress());
        device.setVideoGroupType(request.getVideoGroupType());
        device.setAlarsasGroupType(request.getAlarmGroupType());
        device.setNote(request.getNote());
    }

    private DeviceConfigVO toConfigVO(SasDevice d) {
        DeviceConfigVO vo = new DeviceConfigVO();
        vo.setId(d.getId());
        vo.setDeviceId(d.getDeviceId());
        vo.setChannel(d.getChannel());
        vo.setDeviceType(d.getDeviceType());
        vo.setIpAddr(d.getIpAddr());
        vo.setPort(d.getPort());
        vo.setUsername(d.getUsername());
        vo.setShield(d.getShield());
        vo.setVillageCode(d.getVillageCode());
        vo.setHouseCode(d.getHouseCode());
        vo.setAddress(d.getAddress());
        vo.setVideoGroupType(d.getVideoGroupType());
        vo.setAlarmGroupType(d.getAlarsasGroupType());
        vo.setNote(d.getNote());
        vo.setVideoProtocol(d.getVideoProtocol());
        vo.setVideoType(d.getVideoType());
        vo.setIsBinding(d.getIsBinding());
        vo.setCreateTime(d.getCreateTime());
        vo.setUpdateTime(d.getUpdateTime());
        return vo;
    }

    /**
     * 根据 MAC 地址去重，保留第一条记录，保持原有顺序
     */
    private List<SearchDeviceInfo> distinctByMac(List<SearchDeviceInfo> list) {
        if (list == null || list.isEmpty()) {
            return list;
        }
        Map<String, SearchDeviceInfo> map = new LinkedHashMap<>();
        for (SearchDeviceInfo info : list) {
            String mac = info.getMacAddr();
            // 以 MAC 为 key，已存在的不覆盖，保证顺序
            map.putIfAbsent(mac, info);
        }
        return new ArrayList<>(map.values());
    }

    private static String bytesToString(byte[] bytes) {
        if (bytes == null) {
            return null;
        }
        int len = 0;
        while (len < bytes.length && bytes[len] != 0) {
            len++;
        }
        return new String(bytes, 0, len).trim();
    }

    private static String bytesToMacString(byte[] bytes) {
        if (bytes == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            int b = bytes[i] & 0xFF;
            if (b == 0 && i >= 5) {
                break;
            }
            if (sb.length() > 0) {
                sb.append("-");
            }
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    private static long ipToLong(String ip) {
        if (ip == null || ip.isEmpty()) {
            return Long.MAX_VALUE;
        }
        String[] parts = ip.split("\\.");
        if (parts.length != 4) {
            return Long.MAX_VALUE;
        }
        long res = 0;
        for (String part : parts) {
            try {
                int v = Integer.parseInt(part);
                res = (res << 8) | (v & 0xFF);
            } catch (NumberFormatException e) {
                return Long.MAX_VALUE;
            }
        }
        return res;
    }

    private static List<SearchDeviceInfo> sortByIpAddr(List<SearchDeviceInfo> list) {
        if (list == null) {
            return Collections.emptyList();
        }
        return list.stream()
                .sorted(Comparator.comparingLong(d -> ipToLong(d.getIpAddr())))
                .collect(Collectors.toList());
    }

    private static List<SearchDeviceInfoVO> toVOList(List<SearchDeviceInfo> list) {
        if (list == null) {
            return Collections.emptyList();
        }
        List<SearchDeviceInfoVO> result = new ArrayList<>();
        for (SearchDeviceInfo d : list) {
            if (d == null) {
                continue;
            }
            SearchDeviceInfoVO vo = new SearchDeviceInfoVO();
            vo.setIpAddr(d.getIpAddr());
            vo.setPort(d.getPort());
            vo.setDeviceId(d.getDeviceId());
            vo.setSerialNo(d.getSerialNo());
            vo.setNote(d.getNote());
            vo.setOemInfo(d.getOemInfo());
            vo.setMacAddr(d.getMacAddr());
            vo.setNetMask(d.getNetMask());
            vo.setGateway(d.getGateway());
            vo.setIsAdded(d.getIsAdded());
            vo.setIsActive(d.getIsActive());
            vo.setProtocol(d.getProtocol());
            result.add(vo);
        }
        return result;
    }

    public static void SetStructDataToPointer(Structure pJavaStu, Pointer pNativeData, long OffsetOfpNativeData) {
        pJavaStu.write();
        Pointer pJavaMem = pJavaStu.getPointer();
        pNativeData.write(OffsetOfpNativeData, pJavaMem.getByteArray(0L, pJavaStu.size()), 0, pJavaStu.size());
    }
}

