package com.bz.rmcp.dap.service.impl;
import cn.hutool.core.map.MapUtil;
import com.bz.rmcp.dap.common.Constants;
import com.zcxk.rmcp.api.dto.product.MeasureDataDto;
import com.zcxk.rmcp.api.dto.readmeter.MeterData;
import com.bz.rmcp.dap.model.OfflineData;
import com.bz.rmcp.dap.mq.MqMessageSender;
import com.bz.rmcp.dap.service.ProductService;

import java.math.BigDecimal;
import java.util.Date;
import com.alibaba.fastjson.JSONObject;

import java.time.LocalDateTime;
import java.util.*;

import com.alibaba.fastjson.JSON;
import com.bz.rmcp.dap.model.DeviceOrigDataDTO;
import com.bz.rmcp.dap.service.DeviceDataService;
import com.zcxk.core.common.util.SnowflakeIdWorker;
import com.zcxk.core.utils.DateUtil;
import com.zcxk.rmcp.api.enums.DeviceStatusEnum;
import com.zcxk.rmcp.api.enums.ReadStatusEnum;
import com.zcxk.rmcp.api.enums.ValveStatusEnum;
import com.zcxk.rmcp.api.vo.DeviceDetailVo;
import com.zcxk.rmcp.core.dao.DeviceMapper;
import com.zcxk.rmcp.core.dao.MeterReadRecordMapper;
import com.zcxk.rmcp.core.entity.Device;
import com.zcxk.rmcp.core.entity.Product;
import com.zcxk.rmcp.core.mongo.DeviceData;
import com.zcxk.rmcp.core.mongo.DeviceDataItem;
import com.zcxk.rmcp.core.mongo.MeterReadRecord;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author linqingwei
 * @date 2021-08-03 18:11
 */
@Slf4j
@Service
public class DeviceDataServiceImpl implements DeviceDataService {

    @Autowired
    private SnowflakeIdWorker idWorker;

    @Resource
    private DeviceMapper deviceMapper;
    @Resource
    private MeterReadRecordMapper meterReadRecordMapper;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private ProductService productService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private MqMessageSender mqMessageSender;

    @Override
    public void saveDeviceData(DeviceOrigDataDTO data) {
        log.info("begin saveDeviceData ,data = {}" , JSON.toJSONString(data));
        Device device = deviceMapper.findByDeviceNo(data.getDeviceNo());
        if (device != null) {
            // 1,保存设备测点数据
            MeasureDataDto measureDataDto = saveDeviceMeasuringData(device,data);

            // 2,更新设备信息
            MeterData meterData = updateDevice(device, measureDataDto);

            // 3,保存抄表数据
            saveReadingData(device,meterData);

            // 4,发送离线延时消息
            mqMessageSender.sendDelayMsg(new OfflineData(meterData.getDeviceId(),meterData.getReceiveDate()));

            // 5,发送告警消息
            mqMessageSender.sendDeviceDataMsg(measureDataDto);
        }
        log.info("end saveDeviceData");
    }

    /*
     * @description 保存抄表数据
     * @param meterData
     * @return void
     * @author linqingwei
     **/
    private void saveReadingData(Device device,MeterData meterData) {
        log.info("begin saveReadingData ,meterData = {}" , JSON.toJSONString(meterData));
        // 上次表数据
        MeterData lastMeterData =  getMeterLastData(meterData);

        MeterReadRecord.MeterReadInfo meterReadInfo =  buildMeterReadInfo(meterData,lastMeterData);

        //更新数据项
        Query updateQuery = new Query();
        updateQuery.addCriteria(Criteria.where("deviceId").is(meterData.getDeviceId()));
        updateQuery.addCriteria(Criteria.where("data.readDate").is(DateUtil.getDate(new Date())));
        Update update = new Update();
        update.set("data.$.readTime",meterData.getReceiveDate());
        update.set("data.$.readData",meterData.getReadData());
        update.set("data.$.readStatus",ReadStatusEnum.READ.getCode());
        update.set("data.$.valveStatus",meterData.getValveStatus());
        update.set("data.$.lastValid",meterReadInfo.getLastValid());
        update.inc("data.$.lastCost", meterReadInfo.getLastCost());
        long updateResult = mongoTemplate.updateMulti(updateQuery,update,Constants.METER_READ_RECORD_TABLE).getModifiedCount();
        if(updateResult == 0){
            //更新失败推送数据
            Query pushQuery = new Query();
            pushQuery.addCriteria(Criteria.where("deviceId").is(meterData.getDeviceId()));
            Update push = new Update();
            push.push("data",meterReadInfo);
            long pushResult = mongoTemplate.updateMulti(pushQuery,push,Constants.METER_READ_RECORD_TABLE).getModifiedCount();
            if(pushResult == 0){
                //推送失败插入数据
                MeterReadRecord meterReadRecord = buildMeterReadRecord(meterData,meterReadInfo);
                mongoTemplate.insert(meterReadRecord,Constants.METER_READ_RECORD_TABLE);
            }
        }

        //保存最新抄表信息到缓存中
        redisTemplate.opsForValue().set(Constants.PREFIX_CACHE_FLAG+meterData.getDeviceId(),meterData);

        //更新今天抄表率
        updateTodayReadRate(device,lastMeterData,meterData);

        log.info("end saveReadingData");
    }

    /*
     * @description 更新今天抄表率
     * @param device
     * @param lastMeterData
     * @param meterData
     * @return
     * @author linqingwei
     **/
    private void updateTodayReadRate(Device device, MeterData lastMeterData, MeterData meterData) {
        //不是同一天则更新
        if(lastMeterData.getReceiveDate() == null || !cn.hutool.core.date.DateUtil.isSameDay(lastMeterData.getReceiveDate(),meterData.getReceiveDate())){
            meterReadRecordMapper.updateTodayReadRate(device,meterData.getReceiveDate());
        }
    }

    /*
     * @description 构建MeterReadInfo
     * @param device
     * @return com.zcxk.rmcp.core.mongo.MeterReadRecord.MeterReadInfo
     * @author linqingwei
     **/
    private MeterReadRecord.MeterReadInfo buildMeterReadInfo(MeterData meterData,MeterData lastMeterData) {

        // 计算今日用水量
        BigDecimal todayCost = calculateTodayCost(meterData.getReadData(), lastMeterData.getReadData());
        
        MeterReadRecord.MeterReadInfo readInfo = new MeterReadRecord.MeterReadInfo();
        readInfo.setReadDate(DateUtil.getDate(meterData.getReceiveDate()));
        readInfo.setReadTime(meterData.getReceiveDate().getTime());
        readInfo.setReadStatus(ReadStatusEnum.READ.getCode());
        readInfo.setValveStatus(meterData.getValveStatus());
        readInfo.setReadData(meterData.getReadData());
        readInfo.setLastValid(lastMeterData.getReadData());
        readInfo.setLastCost(todayCost.doubleValue());//用水量
        return readInfo;
    }

    private BigDecimal calculateTodayCost(Double currentReading, Double lastValid) {
        BigDecimal currentValidDecimal = new BigDecimal(currentReading);
        BigDecimal lastValidDecimal = new BigDecimal(lastValid);
        return currentValidDecimal.subtract(lastValidDecimal);
    }


    /*
     * @description 获取最后抄表数据
     * @param waterMeter
     * @param readDate
     * @return java.lang.String
     * @author linqingwei
     **/
    private MeterData getMeterLastData(MeterData meterData){
        MeterData data = null;
        data = getMeterLastDataFromCache(meterData.getDeviceId());
        if(data==null){
            data = new MeterData();
        }

        return data;
    }

    private MeterData getMeterLastDataFromCache(Long deviceId) {
        return (MeterData) redisTemplate.opsForValue().get(Constants.PREFIX_CACHE_FLAG+deviceId);
    }

    /*
     * @description 构建MeterReadRecord
     * @param device
     * @return com.zcxk.rmcp.core.mongo.MeterReadRecord
     * @author linqingwei
     **/
    private MeterReadRecord buildMeterReadRecord(MeterData meterData,MeterReadRecord.MeterReadInfo meterReadInfo) {
        DeviceDetailVo vo =  deviceMapper.findDetail(meterData.getDeviceId());

        List<MeterReadRecord.MeterReadInfo> list = new ArrayList<>();
        list.add(meterReadInfo);

        MeterReadRecord readRecord = new MeterReadRecord();
        readRecord.setId(idWorker.nextId());
        readRecord.setYear(DateUtil.getYear(new Date()));
        readRecord.setTenantId(vo.getTenantId());
        readRecord.setCategoryId(vo.getCategoryId());
        readRecord.setCompanyOrgId(vo.getCompanyOrgId());
        readRecord.setCompanyOrgName(vo.getCompanyName());
        readRecord.setDeptOrgId(vo.getDeptOrgId());
        readRecord.setDeptOrgName(vo.getDeptName());
        readRecord.setCommunityId(vo.getCommunityId());
        readRecord.setCommunityName(vo.getCommunityName());
        //readRecord.setConcentratorId(0);
        //readRecord.setConcentratorCode("");
        //readRecord.setCollectorId(0);
        //readRecord.setCollectorCode("");
        //readRecord.setChannelNumber("");
        readRecord.setDeviceId(vo.getId());
        readRecord.setDeviceNo(vo.getDeviceNo());
        readRecord.setLocation(vo.getDeviceNo());
        readRecord.setProductId(vo.getProductId());
        readRecord.setDeviceModel(vo.getProductModel());
        readRecord.setMeterNo(vo.getMeterNo());
        readRecord.setFileNo(vo.getFileNo());
        readRecord.setCreateTime(new Date());
        readRecord.setCreatorName(Constants.SYS_FLAG);
        readRecord.setUpdateTime(new Date());
        readRecord.setData(list);

        return readRecord;
    }

    /*
     * @description 更新设备
     * @param device
     * @param measureMap
     * @return MeterData
     * @author linqingwei
     **/
    private MeterData updateDevice(Device device, MeasureDataDto measureDataDto) {
        log.info("begin updateDevice ,device = {},measureInfo = {}" , JSON.toJSONString(device),JSON.toJSONString(measureDataDto));
        Map<String, Object> measureMap = measureDataDto.getMeasureMap();

        Product product = productService.findProductByCache(device.getProductId());
        Device update = new Device();
        update.setId(device.getId());
        update.setDeviceStatus(DeviceStatusEnum.NORMAL.getCode());
        update.setReadData(convertReadData(measureMap,product));
        update.setValveStatus(convertValveStatus(measureMap,product));
        update.setLastReceiveTime(DateUtil.asLocalDateTime(measureDataDto.getReceiveDate()));
        update.setUpdateDate(LocalDateTime.now());
        deviceMapper.updateByDeviceData(update);

        MeterData meterData = new MeterData();
        meterData.setDeviceId(device.getId());
        if(update.getReadData()!=null)meterData.setReadData(Double.valueOf(update.getReadData()));
        if(update.getValveStatus()!=null)meterData.setValveStatus(update.getValveStatus());
        meterData.setReceiveDate(measureDataDto.getReceiveDate());

        log.info("end updateDevice");
        return meterData;
    }

    /*
     * @description 按抄表code获取读数
     * @param measureMap
     * @param product
     * @return java.lang.String
     * @author linqingwei
     **/
    private String convertReadData(Map<String, Object> measureMap,Product product){
        return MapUtil.getStr(measureMap,product.getReadingMeasuringCode());
    }

    /*
     * @description 按阀门code获取阀门状态
     * @param measureMap
     * @param product
     * @return java.lang.Integer
     * @author linqingwei
     **/
    private Integer convertValveStatus(Map<String, Object> measureMap,Product product){
        Integer valve = MapUtil.getInt(measureMap,product.getValveMeasuringCode());
        if (valve != null) {
            if(valve == 0)  return ValveStatusEnum.ON.getCode();
            if(valve == 1)  return ValveStatusEnum.OFF.getCode();
            return ValveStatusEnum.ABNORMAL.getCode();
        }
        return null;
    }

    /*
     * @description 保存设备测点数据
     * @param device
     * @param data
     * @return MeasureInfo
     * @author linqingwei
     **/
    private MeasureDataDto saveDeviceMeasuringData(Device device, DeviceOrigDataDTO data) {
        MeasureDataDto measureDataDto = new MeasureDataDto();
        // 保存数据
        for (Map.Entry<Date, String> entry : data.getParsedData().entrySet()) {

            Date date = entry.getKey();
            String json = entry.getValue();


            JSONObject jsonObject = JSONObject.parseObject(json);
            Map<String, Object> measureMap = (Map<String, Object>)jsonObject;

            measureDataDto.setDeviceId(device.getId());
            measureDataDto.setReceiveDate(date);
            measureDataDto.setMeasureMap(measureMap);

            DeviceDataItem deviceDataItem = buildDeviceDataItem(measureDataDto);


            //更新数据项
            Query query = new Query();
            query.addCriteria(Criteria.where("deviceId").is(device.getId()));
            Update update = new Update();
            update.push("data", deviceDataItem);
            long updateResult = mongoTemplate.updateMulti(query,update,Constants.DEVICE_DATA_TABLE).getModifiedCount();

            if(updateResult == 0){
                //更新失败插入数据
                DeviceData deviceData = buildDeviceData(device, measureDataDto);
                mongoTemplate.insert(deviceData,Constants.DEVICE_DATA_TABLE);
            }
        }
        return measureDataDto;
    }

    /*
     * @description 构建DeviceDataItem
     * @param date
     * @param map
     * @return com.zcxk.rmcp.core.mongo.DeviceDataItem
     * @author linqingwei
     **/
    private DeviceDataItem buildDeviceDataItem(MeasureDataDto measureDataDto) {
        DeviceDataItem dataItem = new DeviceDataItem();
        dataItem.setReceiveDate(DateUtil.getDate(measureDataDto.getReceiveDate()));
        dataItem.setReceiveTime(measureDataDto.getReceiveDate().getTime());
        dataItem.setMeasureData(measureDataDto.getMeasureMap());
        return dataItem;
    }

    /*
     * @description 构建DeviceData
     * @param device
     * @param date
     * @param map
     * @return com.zcxk.rmcp.core.mongo.DeviceData
     * @author linqingwei
     **/
    private DeviceData buildDeviceData(Device device, MeasureDataDto measureDataDto) {
        DeviceData deviceData = new DeviceData();
        deviceData.setId(idWorker.nextId());
        deviceData.setDeviceId(device.getId());
        deviceData.setYear(DateUtil.getYear(new Date()));
        deviceData.setStatus(1);
        deviceData.setCreateBy(Constants.SYS_FLAG);
        deviceData.setCreateDate(new Date());

        List<DeviceDataItem> list = new ArrayList<>();

        DeviceDataItem dataItem = buildDeviceDataItem(measureDataDto);
        list.add(dataItem);

        deviceData.setData(list);
        return deviceData;
    }
}
