package cn.iocoder.yudao.module.product.service.deviceinfo;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.json.JSONObject;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.basic.api.businessinfo.BusinessInfoApi;
import cn.iocoder.yudao.module.basic.api.redis.dto.BusinessInfoDTO;
import cn.iocoder.yudao.module.product.controller.admin.deviceinfo.vo.*;
import cn.iocoder.yudao.module.product.controller.admin.slavedevice.vo.SlaveDeviceRespVO;
import cn.iocoder.yudao.module.product.controller.admin.slavedevice.vo.SlaveDeviceSaveReqVO;
import cn.iocoder.yudao.module.product.convert.deviceinfo.DeviceInfoConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.deviceinfo.DeviceInfoDO;
import cn.iocoder.yudao.module.product.dal.dataobject.envtype.EnvTypeDO;
import cn.iocoder.yudao.module.product.dal.dataobject.slavedevice.SlaveDeviceDO;
import cn.iocoder.yudao.module.product.dal.mysql.deviceinfo.DeviceInfoMapper;
import cn.iocoder.yudao.module.product.dal.mysql.envtype.EnvTypeMapper;
import cn.iocoder.yudao.module.product.dal.mysql.slavedevice.SlaveDeviceMapper;
import cn.iocoder.yudao.module.product.emqx.DeviceMqttService;
import cn.iocoder.yudao.module.product.emqx.MqttProperties;
import cn.iocoder.yudao.module.product.enums.DeviceTypeEnums;
import cn.iocoder.yudao.module.product.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.product.enums.GatewayTypeEnums;
import cn.iocoder.yudao.module.product.hik.HikHttpUtils;
import cn.iocoder.yudao.module.product.hik.dto.HikDeviceInfoDto;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;


/**
 * 设备信息 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
@Slf4j
public class DeviceInfoServiceImpl implements DeviceInfoService {

    @Resource
    private MqttProperties mqttProperties;
    @Resource
    private DeviceInfoMapper deviceInfoMapper;
    @Resource
    private SlaveDeviceMapper slaveDeviceMapper;
    @Resource
    private EnvTypeMapper envTypeMapper;
    @Resource
    private BusinessInfoApi businessInfoApi;
    @Resource
    private DeviceMqttService deviceMqttService;

    @Override
    @Transactional
    public Long createDeviceInfo(DeviceInfoCreateReqVO createReqVO) {
        // 校验商户
        BusinessInfoDTO businessInfo = businessInfoApi.getBusinessInfoById(createReqVO.getBelongBusinessId()).getData();
        if (businessInfo == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.BUSINESS_INFO_NOT_EXISTS);
        }
        // 校验监控点名称是否正确
        if (createReqVO.getDeviceType().equals(DeviceTypeEnums.MONITOR.getDeviceType())) {
            HikDeviceInfoDto hikDeviceInfo = HikHttpUtils.getHikDeviceInfo(createReqVO.getMonitorCode());
            if (hikDeviceInfo == null) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEVICE_NAME_IS_ERROR_HIK_MONITOR);
            }
        }

        this.verifyDeviceType(createReqVO, businessInfo.getId(), null);

        if (createReqVO.getGatewayType().equals(GatewayTypeEnums.YOUREN.getGatewayType()) && createReqVO.getDeviceType().equals(DeviceTypeEnums.SENSOR.getDeviceType())) {
            if (createReqVO.getSlaveDevices().isEmpty()) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.CAN_NOT_BE_EMPTY_WHEN_TYPE_IS_YOUREN);
            }
            List<Long> envTypeIds = createReqVO.getSlaveDevices().stream().map(SlaveDeviceSaveReqVO::getEnvType).collect(Collectors.toList());
            // 校验环境类型ID
            List<EnvTypeDO> envTypes = envTypeMapper.selectList(new LambdaQueryWrapperX<EnvTypeDO>().in(EnvTypeDO::getId, envTypeIds));
            if (envTypes.size() != envTypeIds.size()) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.ENV_TYPE_NOT_EXISTS);
            }
        }

        DeviceInfoDO deviceInfo = DeviceInfoConvert.INSTANCE.convert(createReqVO);
        deviceInfo.setBelongBusinessName(businessInfo.getBusinessName());

        // 插入
        deviceInfoMapper.insert(deviceInfo);
        if (createReqVO.getGatewayType().equals(GatewayTypeEnums.YOUREN.getGatewayType()) && createReqVO.getDeviceType().equals(DeviceTypeEnums.SENSOR.getDeviceType())) {
            List<SlaveDeviceDO> slaveDevices = new ArrayList<>();
            createReqVO.getSlaveDevices().forEach(item -> {
                SlaveDeviceDO slaveDevice = new SlaveDeviceDO();
                BeanUtil.copyProperties(item, slaveDevice);
                slaveDevice.setId(null);
                slaveDevice.setBelongBusinessId(businessInfo.getId());
                slaveDevice.setBelongBusinessName(businessInfo.getBusinessName());
                slaveDevice.setDeviceId(deviceInfo.getId());
                slaveDevices.add(slaveDevice);
            });
            slaveDeviceMapper.insertBatch(slaveDevices);
        }
        // 返回
        return deviceInfo.getId();
    }

    @Override
    @Transactional
    public void updateDeviceInfo(DeviceInfoUpdateReqVO updateReqVO) {
        // 校验存在
        DeviceInfoDO deviceInfoDO = this.validateDeviceInfoExists(updateReqVO.getId());
        this.verifyDeviceType(updateReqVO, deviceInfoDO.getBelongBusinessId(), deviceInfoDO);

        // 校验监控编码是否正确
        if (updateReqVO.getDeviceType().equals(DeviceTypeEnums.MONITOR.getDeviceType())) {
            HikDeviceInfoDto hikDeviceInfo = HikHttpUtils.getHikDeviceInfo(updateReqVO.getMonitorCode());
            if (hikDeviceInfo == null) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEVICE_NAME_IS_ERROR_HIK_MONITOR);
            }
        }

        List<SlaveDeviceDO> addSlaveDevices = new ArrayList<>();
        List<SlaveDeviceDO> updateSlaveDevices = new ArrayList<>();
        List<Long> willDeleteIds = new ArrayList<>();

        if (updateReqVO.getGatewayType().equals(GatewayTypeEnums.YOUREN.getGatewayType()) && updateReqVO.getDeviceType().equals(DeviceTypeEnums.SENSOR.getDeviceType())) {
            if (updateReqVO.getSlaveDevices().isEmpty()) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.CAN_NOT_BE_EMPTY_WHEN_TYPE_IS_YOUREN);
            }
            List<Long> willUpdateIds = new ArrayList<>();
            List<Long> envTypeIds = updateReqVO.getSlaveDevices().stream().map(item -> {
                if (item.getId() != null) {
                    willUpdateIds.add(item.getId());
                }
                return item.getEnvType();
            }).collect(Collectors.toList());
            // 校验环境类型ID
            List<EnvTypeDO> envTypes = envTypeMapper.selectList(new LambdaQueryWrapperX<EnvTypeDO>().in(EnvTypeDO::getId, envTypeIds));
            if (envTypes.size() != envTypeIds.size()) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.ENV_TYPE_NOT_EXISTS);
            }
            List<SlaveDeviceDO> slaveDeviceDB = slaveDeviceMapper.selectList(new LambdaQueryWrapperX<SlaveDeviceDO>().eq(SlaveDeviceDO::getDeviceId, updateReqVO.getId()));
            List<Long> slaveIdsDB = slaveDeviceDB.stream().map(SlaveDeviceDO::getId).collect(Collectors.toList());

            // 循环从设备
            updateReqVO.getSlaveDevices().forEach(item -> {
                if (item.getId() == null) {
                    // 新增流程
                    SlaveDeviceDO slaveDevice = new SlaveDeviceDO();
                    BeanUtil.copyProperties(item, slaveDevice);
                    slaveDevice.setBelongBusinessId(deviceInfoDO.getBelongBusinessId());
                    slaveDevice.setBelongBusinessName(deviceInfoDO.getBelongBusinessName());
                    slaveDevice.setDeviceId(deviceInfoDO.getId());
                    addSlaveDevices.add(slaveDevice);
                } else {
                    if (!slaveIdsDB.contains(item.getId())) {
                        throw ServiceExceptionUtil.exception(ErrorCodeConstants.SLAVE_DEVICE_NOT_EXISTS);
                    }
                    SlaveDeviceDO slaveDevice = new SlaveDeviceDO();
                    BeanUtil.copyProperties(item, slaveDevice);
                    slaveDevice.setDeviceId(deviceInfoDO.getId());
                    updateSlaveDevices.add(slaveDevice);
                }
            });

            willDeleteIds.addAll(slaveIdsDB.stream().filter(id -> !willUpdateIds.contains(id)).collect(Collectors.toList()));
        }

        DeviceInfoDO updateObj = DeviceInfoConvert.INSTANCE.convert(updateReqVO);

        // 更新
        deviceInfoMapper.updateById(updateObj);

        if (!willDeleteIds.isEmpty()) {
            slaveDeviceMapper.deleteBatchIds(willDeleteIds);
        }

        if (!addSlaveDevices.isEmpty()) {
            slaveDeviceMapper.insertBatch(addSlaveDevices);
        }

        if (!updateSlaveDevices.isEmpty()) {
            slaveDeviceMapper.updateBatch(updateSlaveDevices, Integer.MAX_VALUE);
        }

    }

    private void verifyDeviceType(DeviceInfoSaveReqVO saveReqVO, Long businessId, DeviceInfoDO deviceInfoDO) {
        List<DeviceInfoDO> devices;
        // 校验设备类型
        if (saveReqVO.getDeviceType().equals(DeviceTypeEnums.CONTROLLER.getDeviceType())) {
            saveReqVO.setEnvType(0L);// 默认将监视器类型会写入的字段置空
        } else if (saveReqVO.getDeviceType().equals(DeviceTypeEnums.SENSOR.getDeviceType())) {
            saveReqVO.setControllerField("");
            EnvTypeDO envType = envTypeMapper.selectOne(new LambdaQueryWrapperX<EnvTypeDO>().eq(EnvTypeDO::getId, saveReqVO.getEnvType()));
            if (envType == null) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.ENV_TYPE_NOT_EXISTS);
            }
        } else if (saveReqVO.getDeviceType().equals(DeviceTypeEnums.MONITOR.getDeviceType())) {
            saveReqVO.setControllerField("");
        }

        // 校验设备是否重复，每个商户下的每个地块只能拥有一个类型的设备
        if (saveReqVO.getControllerField().isEmpty()) {
            saveReqVO.setControllerField(null);
        }
        if (!saveReqVO.getDeviceType().equals(DeviceTypeEnums.MONITOR.getDeviceType())) {
            devices = deviceInfoMapper.selectList(new LambdaQueryWrapperX<DeviceInfoDO>().eq(DeviceInfoDO::getBelongBusinessId, businessId)
                    .eqIfPresent(DeviceInfoDO::getDeviceType, saveReqVO.getDeviceType())
                    .eqIfPresent(DeviceInfoDO::getGatewayType, saveReqVO.getGatewayType()).eqIfPresent(DeviceInfoDO::getEnvType, saveReqVO.getEnvType())
                    .eqIfPresent(DeviceInfoDO::getControllerField, saveReqVO.getControllerField()));
        } else {
            devices = deviceInfoMapper.selectList(new LambdaQueryWrapper<DeviceInfoDO>().eq(DeviceInfoDO::getBelongBusinessId, businessId)
                    .eq(DeviceInfoDO::getDeviceName, saveReqVO.getDeviceName()).or()
                    .eq(DeviceInfoDO::getBelongBusinessId, businessId)
                    .eq(DeviceInfoDO::getMonitorCode, saveReqVO.getMonitorCode()));
        }

        if (!devices.isEmpty()) {
            if (deviceInfoDO == null) {
                if (!saveReqVO.getDeviceType().equals(DeviceTypeEnums.MONITOR.getDeviceType())) {
                    // 同一个网关内无法添加重复类型的设备
                    throw ServiceExceptionUtil.exception(ErrorCodeConstants.CAN_NOT_ADD_THE_SAME_DEVICE);
                } else {
                    // 监控设备名称不可重复，监控点名称不可重复
                    throw ServiceExceptionUtil.exception(ErrorCodeConstants.HIK_MONITOR_DEVICE_IS_EXISTS);
                }
            }
            devices.forEach(device -> {
                if (!device.getId().equals(deviceInfoDO.getId())) {
                    if (saveReqVO.getDeviceType() != 3) {
                        // 同一个网关内无法添加重复类型的设备
                        throw ServiceExceptionUtil.exception(ErrorCodeConstants.CAN_NOT_ADD_THE_SAME_DEVICE);
                    } else {
                        // 监控设备名称不可重复，监控点名称不可重复
                        throw ServiceExceptionUtil.exception(ErrorCodeConstants.HIK_MONITOR_DEVICE_IS_EXISTS);
                    }
                }
            });
        }
    }

    @Override
    public void deleteDeviceInfo(Long id) {
        // 校验存在
        validateDeviceInfoExists(id);
        // 删除
        deviceInfoMapper.deleteById(id);
    }

    private DeviceInfoDO validateDeviceInfoExists(Long id) {
        DeviceInfoDO deviceInfoDO = deviceInfoMapper.selectById(id);
        if (deviceInfoDO == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.DEVICE_INFO_NOT_EXISTS);
        }
        return deviceInfoDO;
    }

    @Override
    public DeviceInfoRespVO getDeviceInfo(Long id) {
        DeviceInfoRespVO result = new DeviceInfoRespVO();
        DeviceInfoDO deviceInfo = deviceInfoMapper.selectById(id);
        BeanUtil.copyProperties(deviceInfo, result);
        List<SlaveDeviceDO> slaveDeviceDOS = slaveDeviceMapper.selectList(new LambdaQueryWrapperX<SlaveDeviceDO>().eq(SlaveDeviceDO::getDeviceId, id));
        List<SlaveDeviceRespVO> slaveDevices = new ArrayList<>();
        slaveDeviceDOS.forEach(item -> {
            SlaveDeviceRespVO slaveDeviceReq = new SlaveDeviceRespVO();
            BeanUtil.copyProperties(item, slaveDeviceReq);
            slaveDevices.add(slaveDeviceReq);
        });
        result.setSlaveDevices(slaveDevices);
        return result;
    }

    @Override
    public PageResult<DeviceInfoRespVO> getDeviceInfoPage(DeviceInfoPageReqVO pageReqVO) {
        Page<DeviceInfoRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
        List<DeviceInfoRespVO> result = deviceInfoMapper.selectDeviceInfoPage(page, pageReqVO);
        return new PageResult<>(result, page.getTotal());
    }

    @Override
    public void changeSwitch(Long id, Boolean switchOn) {
        DeviceInfoDO deviceInfo = this.validateDeviceInfoExists(id);
        if (!deviceInfo.getDeviceType().equals(DeviceTypeEnums.CONTROLLER.getDeviceType())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.ONLY_CONTROLLER_CAN_CHANGE_SWITCH);
        }

        boolean so = switchOn != null && switchOn;
        deviceInfo.setSwitchOn(so);

        try {
            String topic = mqttProperties.getControllerTopic() + deviceInfo.getTenantId() + "/" + deviceInfo.getBelongBusinessId();
            // 向emqx发送开关命令
            if (deviceInfo.getGatewayType().equals(GatewayTypeEnums.YOUREN.getGatewayType())) {
                String cmd = so ? deviceInfo.getOpenInstruction() : deviceInfo.getCloseInstruction();
                byte[] cmdData = HexUtil.decodeHex(cmd);
                deviceMqttService.sendMessage(topic, cmdData);
            } else {
                // 获取设备类型
                // 必联未做测试，如果需要启用  需要本地先进行测试，否则后果自负
                EnvTypeDO envTypeDO = envTypeMapper.selectById(deviceInfo.getEnvType());
                if (envTypeDO == null) {
                    throw ServiceExceptionUtil.exception(ErrorCodeConstants.ENV_TYPE_NOT_EXISTS);
                }
                Map<String, Boolean> params = new HashMap<>();
                params.put(envTypeDO.getEnvFieldName(), switchOn);
                String message = new JSONObject(params).toString();
                deviceMqttService.sendMessage(topic, message);
            }
        } catch (Exception e) {
            log.error("发送命令失败: ", e);
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SEND_CMD_FAIL);
        }

        // 更新数据
        deviceInfoMapper.updateById(deviceInfo);
    }

    @Override
    public void changeEnabled(Long id, Boolean enabled) {
        DeviceInfoDO deviceInfo = this.validateDeviceInfoExists(id);
        deviceInfo.setEnabled(enabled);
        // 更新数据
        deviceInfoMapper.updateById(deviceInfo);
    }

    /**
     * 获取监控播放地址
     *
     * @param monitorCode 监控点编码
     */
    @Override
    public MonitorUrLRespVO getMonitorPreviewURL(String monitorCode, String protocol) {
        // 根据indexCode获得设备在线状态
        List<Boolean> isOnlineList = HikHttpUtils.getCameraIsOnline(Collections.singletonList(monitorCode));
        assert isOnlineList != null;
        if (isOnlineList.isEmpty() || !isOnlineList.get(0)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.MONITOR_DEVICE_IS_OFFLINE);
        }

        MonitorUrLRespVO monitorUrLRespVO = new MonitorUrLRespVO();
        monitorUrLRespVO.setMonitorCode(monitorCode);
        monitorUrLRespVO.setUrl(HikHttpUtils.getCameraPreviewURL(monitorCode, protocol));
        return monitorUrLRespVO;
    }

    /**
     * 获取监控播放地址
     *
     * @param landId 地块/鱼塘/牧场编码
     * @param protocol   类型 ws(http),wss(https)
     */
    @Override
    public List<MonitorUrLRespVO> getMonitorPreviewURLByLandId(String landId, String protocol) {
        List<DeviceInfoDO> deviceInfoDOS = deviceInfoMapper.selectList(new LambdaQueryWrapperX<DeviceInfoDO>()
                .eq(DeviceInfoDO::getDeviceType, DeviceTypeEnums.MONITOR.getDeviceType()).eq(DeviceInfoDO::getEnabled, Boolean.TRUE));

        List<MonitorUrLRespVO> urlList = new ArrayList<>();
        if (!deviceInfoDOS.isEmpty()) {
            List<Boolean> isOnlineList = HikHttpUtils.getCameraIsOnline(deviceInfoDOS.stream().map(DeviceInfoDO::getMonitorCode).collect(Collectors.toList()));
            if (isOnlineList == null || isOnlineList.size() != deviceInfoDOS.size()) {
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.MONITOR_DEVICE_INDEX_CODE_IS_NOT_EXISTS);
            }

            for (int i = 0; i < deviceInfoDOS.size(); i++) {
                MonitorUrLRespVO monitorUrLRespVO = new MonitorUrLRespVO();
                monitorUrLRespVO.setMonitorPoints(deviceInfoDOS.get(i).getMonitorPoints());
                monitorUrLRespVO.setMonitorCode(deviceInfoDOS.get(i).getMonitorCode());
                monitorUrLRespVO.setIsOnline(isOnlineList.get(i));
                if (isOnlineList.get(i)) {
                    // 如果在线
                    // 通过indexCode 获得播放地址, 因为官方提供的接口只有单个的所以这里循环获取
                    monitorUrLRespVO.setUrl(HikHttpUtils.getCameraPreviewURL(deviceInfoDOS.get(i).getMonitorCode(), protocol));
                }
                urlList.add(monitorUrLRespVO);
            }
        }
        return urlList;
    }
}
