package com.sptg.device.biz.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.toolkit.IdWorker;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.sptg.ad.api.stream.AdvertTaskCallbackProduceInterface;
import com.sptg.common.core.entity.PublishTask;
import com.sptg.common.core.entity.PublishTaskResult;
import com.sptg.common.core.enums.Instruct;
import com.sptg.common.core.enums.InstructDH;
import com.sptg.common.core.util.JsonUtils;
import com.sptg.device.api.dto.BatchFaceDto;
import com.sptg.device.api.entity.Device;
import com.sptg.device.api.entity.InstructPublishLog;
import com.sptg.device.api.entity.MqConf;
import com.sptg.device.api.util.RabbitMqConnectionUtil;
import com.sptg.device.biz.service.*;
import com.sptg.estate.api.dto.UserInfoDto;
import com.sptg.estate.api.stream.NoticeTaskCallbackProduceInterface;
import com.sptg.third.api.entity.qinlin.QLinUser;
import com.sptg.third.api.entity.qinlin.QLinUserDevice;
import com.sptg.third.api.feigns.qinlin.QLinFeign;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.util.Asserts;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 *
 *
 * description:
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MsgPublishServiceImpl implements MsgPublishService {

    private final DeviceServiceInterface deviceServiceInterface;
    private final MqConfServiceInterface mqConfServiceInterface;
    private final AdvertTaskCallbackProduceInterface advertTaskCallbackProduceInterface;
    private final NoticeTaskCallbackProduceInterface noticeTaskCallbackProduceInterface;
    private final TimeStrategyTaskDeviceDetailsServiceInterface timeStrategyTaskDeviceDetailsServiceInterface;
    private final InstructPublishLogService instructPublishLogService;
    private final QLinFeign qLinFeign;

    @Override
    @SneakyThrows
    public void publish(Object obj, Device device, Instruct instruct, Map<String,String> m) {
        MqConf conf = this.mqConfServiceInterface.selectById(device.getMqId());
        if (null == conf){
            throw new Exception("设备未绑定队列");
        }
        //  获取队列连接
        Connection connection = RabbitMqConnectionUtil.openConnection(conf);
        Channel channel = null;
        try {
            //  创建管道
            channel = connection.createChannel();
            long id = IdWorker.getId();
            //  构建数据
            Map<String,Object> map = new HashMap<>(3);
            map.put("id",id);
            map.put("code",instruct.getCode());
            map.put("data",obj);
            map.put("serialNumber",device.getSerialNumber());
            String msg = JSON.toJSONString(map, SerializerFeature.WriteNonStringValueAsString, SerializerFeature.WriteDateUseDateFormat);
//            String msg = JsonUtils.objectToJson(map);
            //  推送消息
            channel.basicPublish(instruct.getExchangeName(), device.getSerialNumber(), null, msg.getBytes(StandardCharsets.UTF_8));


            //  保存指令推送日志
//            InstructPublishLog publishLog = new InstructPublishLog(id,device, instruct, JsonUtils.objectToJson(obj), m);
            InstructPublishLog publishLog = new InstructPublishLog(id,device, instruct, JSON.toJSONString(obj, SerializerFeature.WriteNonStringValueAsString, SerializerFeature.WriteDateUseDateFormat), m);
            this.instructPublishLogService.insert(publishLog);
        } finally {
            //  关闭连接,抛出异常
            RabbitMqConnectionUtil.closeConnection(connection,channel);
        }
    }

    @Override
    @SneakyThrows
    public void topicMsgPublish(Object obj, Device device, InstructDH instructDH, Map<String,String> m) {
        MqConf conf = this.mqConfServiceInterface.selectById(device.getMqId());
        if (null == conf){
            throw new Exception("设备未绑定队列");
        }
        //  获取队列连接
        Connection connection = RabbitMqConnectionUtil.openConnection(conf);
        Channel channel = null;
        try {
            //  创建管道
            channel = connection.createChannel();
            long id = IdWorker.getId();
            //  构建数据
            Map<String,Object> map = new HashMap<>(3);
            map.put("id",id);
            map.put("code",instructDH.getCode());
            map.put("data",obj);
            map.put("serialNumber",device.getSerialNumber());
            map.put("adminPwd", device.getAdminPwd());
//            String msg = JsonUtils.objectToJson(map);
            String msg = JSON.toJSONString(map, SerializerFeature.WriteNonStringValueAsString, SerializerFeature.WriteDateUseDateFormat);
            //  推送消息
//            AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
//            builder.priority(instruct.getPriority()); //设置权重
//            AMQP.BasicProperties properties = builder.build();
//            channel.exchangeDeclare(instructDH.getExchangeName(),"topic");

            channel.basicPublish(instructDH.getExchangeName(), device.getDeviceFactory() + "." + instructDH.getCode(),  null, msg.getBytes());

            //  保存指令推送日志
            InstructPublishLog publishLog = new InstructPublishLog(id,device, instructDH, JSON.toJSONString(obj, SerializerFeature.WriteNonStringValueAsString, SerializerFeature.WriteDateUseDateFormat), m);
//            InstructPublishLog publishLog = new InstructPublishLog(id,device, instructDH, JsonUtils.objectToJson(obj), m);
            this.instructPublishLogService.insert(publishLog);
        } finally {
            //  关闭连接,抛出异常
            RabbitMqConnectionUtil.closeConnection(connection,channel);
        }
    }

    @Async
    @Override
    public void publishTask(PublishTask task) {
        Assert.notNull(task.getTaskId(),"任务Id不能为空");
        Assert.notNull(task.getInstruct(),"指令不能为空");
        Assert.notNull(task.getBody(),"推动内容不能为空");
        Assert.notNull(task.getDeviceIdList(),"设备Id不能为空");
        task.getDeviceIdList().forEach(deviceId -> {
            //  获取设备信息
            Device device = this.deviceServiceInterface.selectById(deviceId);
            //  推送记录
            PublishTaskResult taskResult = new PublishTaskResult();
            taskResult.setTaskId(task.getTaskId());
            taskResult.setDeviceId(deviceId);
            taskResult.setDeviceName(device.getName());
            taskResult.setSerialNumber(device.getSerialNumber());
            taskResult.setInstallPath(device.getInstallPath());
            taskResult.setXqName(device.getXqName());
            taskResult.setDyName(device.getDyName());
            Map<String,String> map = new HashMap<>();
            //小区
            if (!StringUtils.isEmpty(device.getXqName())){
                map.put("xqName",device.getXqName());
            }else {
                map.put("xqName",null);
            }
            //单元
            if (!StringUtils.isEmpty(device.getDyName())){
                map.put("unitName",device.getDyName());
            }else {
                map.put("unitName",null);
            }
            try {
                //  推送消息,设置推送状态为：推送中
                switch (device.getDeviceFactory()){
                    case "dh":
                        Instruct instruct = task.getInstruct();
                        InstructDH instructDH = new InstructDH();
                        instructDH.setCode(instruct.getCode());
                        instructDH.setExchangeName(instructDH.getDHExchange(instruct.getExchangeName()));
                        instructDH.setDescribe(instruct.getDescribe());
                        this.topicMsgPublish(task.getBody(), device, instructDH, map);
                        break;
                    default:
                this.publish(task.getBody(),device,task.getInstruct(),map);
                        break;
                }
//                this.publish(task.getBody(), device, task.getInstruct());
                taskResult.setPushStatus(3);
            }catch (Exception e){
                //  保存失败记录,设置推送状态为：失败
                taskResult.setPushStatus(2);
                taskResult.setRemark(e.getMessage());
            } finally {
                //  判断是否需要请求回调
                if (null != task.getTaskType()){
                    switch (task.getTaskType()){
                        //  1：广告，
                        case 1:
                            this.advertTaskCallbackProduceInterface.output().send(MessageBuilder.withPayload(taskResult).build());
                            break;
                        //  2：策略
                        case 2:
                            this.timeStrategyTaskDeviceDetailsServiceInterface.insert(taskResult);
                            break;
                        //  3：公告
                        case 3:
                            this.noticeTaskCallbackProduceInterface.output().send(MessageBuilder.withPayload(taskResult).build());
                            break;
                        default:break;
                    }
                }
            }
        });
    }

    @Async
    @Override
    public void batchEditDevice(Wrapper<Device> wrapper) {
        List<Device> deviceList = this.deviceServiceInterface.selectList(wrapper);
        Map<String,String> map = new HashMap<>();
        deviceList.forEach(device -> {
            try {
                this.publish(device,device,Instruct.update_device_info,map);
            }catch (Exception e){
                log.error(e.getMessage(),e);
            }
        });
    }

    @Async
    @Override
    public void batchPushInstruct(Object obj, List<Device> deviceList, Instruct instruct, Map<String,String> map) {
        List<Device> qinlinList = deviceList.stream().filter(device -> device.getDeviceFactory().equals("qinlin")).collect(Collectors.toList());
        List<Device> otherList = deviceList.stream().filter(device -> !(device.getDeviceFactory().equals("qinlin"))).collect(Collectors.toList());
        if(qinlinList != null && qinlinList.size() > 0){
            this.qlPushInstruct(obj, qinlinList, instruct);
        }
        otherList.forEach(device -> {
            try {
                this.publish(obj,device,instruct,map);
            }catch (Exception e){
                log.error(e.getMessage(),e);
            }
        });
    }

    @Override
    public void batchPushInstruct(Object obj, Device device, Instruct instruct) {
        Map<String,String> map = new HashMap<>();
        //小区
        if (!StringUtils.isEmpty(device.getXqName())){
            map.put("xqName",device.getXqName());
        }else {
            map.put("xqName",null);
        }
        //单元
        if (!StringUtils.isEmpty(device.getDyName())){
            map.put("unitName",device.getDyName());
        }else {
            map.put("unitName",null);
        }
        this.publish(obj,device,instruct,map);
    }

    @Async
    @Override
    public void topicMsgBatchPushInstruct(Object obj, Device device, InstructDH instructDH, Map<String,String> m) {
        JSONArray sendArray = new JSONArray();
        String json = JsonUtils.objectToJson(obj);
        JSONArray jsonArray = JSONArray.parseArray(json);
        jsonArray.forEach(jsonObj -> {
            //  保存指令推送日志
            long id = IdWorker.getId();
            InstructPublishLog publishLog = null;
            try {
                publishLog = new InstructPublishLog(id,device, instructDH, JsonUtils.objectToJson(jsonObj),m);
            } catch (ParseException e) {
                log.error(e.getMessage(),e);
            }
            this.instructPublishLogService.insert(publishLog);
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("id", id);
            jsonObject.put("data", jsonObj);
            sendArray.add(jsonObject);
        });
        try {
            MqConf conf = this.mqConfServiceInterface.selectById(device.getMqId());
            if (null == conf){
                throw new Exception("设备未绑定队列");
            }
            //  获取队列连接
            Connection connection = RabbitMqConnectionUtil.openConnection(conf);
            Channel channel = null;
            try {
                //  创建管道
                channel = connection.createChannel();
                long batchId = IdWorker.getId();
                //  构建数据
                Map<String,Object> map = new HashMap<>(3);
                map.put("id",batchId);
                map.put("code",instructDH.getBatchCode());
                map.put("data",sendArray);
                map.put("serialNumber",device.getSerialNumber());
                map.put("adminPwd", device.getAdminPwd());
//                String msg = JsonUtils.objectToJson(map);
                String msg = JSON.toJSONString(map, SerializerFeature.WriteNonStringValueAsString, SerializerFeature.WriteDateUseDateFormat);
                channel.basicPublish(instructDH.getExchangeName(), device.getDeviceFactory() + "." + instructDH.getCode(),  null, msg.getBytes());
            } finally {
                //  关闭连接,抛出异常
                RabbitMqConnectionUtil.closeConnection(connection,channel);
            }
        }catch (Exception e){
            log.error(e.getMessage(),e);
        }
    }

    @Override
    @SneakyThrows
    public void resendInstruct(Object obj, String serialNumber, InstructPublishLog instructPublishLog) {
        Asserts.notNull(instructPublishLog, "指令记录不能为空");
        Instruct instruct = Instruct.getByCode(instructPublishLog.getInstructCode());
        Device device = this.deviceServiceInterface.selectBySerialNumber(serialNumber);
        MqConf conf = this.mqConfServiceInterface.selectById(device.getMqId());
        if (null == conf){
            throw new Exception("设备未绑定队列");
        }
        //  获取队列连接
        Connection connection = RabbitMqConnectionUtil.openConnection(conf);
        Channel channel = null;
        try {
            //  创建管道
            channel = connection.createChannel();
            long id = instructPublishLog.getId();
            //  构建数据
            Map<String,Object> map = new HashMap<>(3);
            map.put("id",id);

            map.put("code",instruct.getCode());
            Object o = JsonUtils.jsonToPojo((String) obj, Object.class);
            map.put("data",o);
            map.put("serialNumber",device.getSerialNumber());
//            String msg = JsonUtils.objectToJson(map);
            String msg = JSON.toJSONString(map, SerializerFeature.WriteNonStringValueAsString, SerializerFeature.WriteDateUseDateFormat);
            //  推送消息
            channel.basicPublish(instruct.getExchangeName(), device.getSerialNumber(), null, msg.getBytes(StandardCharsets.UTF_8));

            //  更新指令推送日志
            instructPublishLog.setLastResendTime(new Date());
            instructPublishLog.setPublishData(JSON.toJSONString(o, SerializerFeature.WriteNonStringValueAsString, SerializerFeature.WriteDateUseDateFormat));
            this.instructPublishLogService.updateById(instructPublishLog);
        } finally {
            //  关闭连接,抛出异常
            RabbitMqConnectionUtil.closeConnection(connection,channel);
        }
    }

    @Override
    @SneakyThrows
    public void resendInstructDH(Object obj, String serialNumber, InstructPublishLog instructPublishLog) {
        Asserts.notNull(instructPublishLog, "指令记录不能为空");
        Instruct instruct = Instruct.getByCode(instructPublishLog.getInstructCode());
        InstructDH instructDH = new InstructDH();
        instructDH.setCode(instruct.getCode());
        instructDH.setExchangeName(instructDH.getDHExchange(instruct.getExchangeName()));
        instructDH.setDescribe(instruct.getDescribe());
        Device device = this.deviceServiceInterface.selectBySerialNumber(serialNumber);
        MqConf conf = this.mqConfServiceInterface.selectById(device.getMqId());
        if (null == conf){
            throw new Exception("设备未绑定队列");
        }
        //  获取队列连接
        Connection connection = RabbitMqConnectionUtil.openConnection(conf);
        Channel channel = null;
        try {
            //  创建管道
            channel = connection.createChannel();
            long id = instructPublishLog.getId();
            //  构建数据
            Map<String,Object> map = new HashMap<>(3);
            map.put("id",id);
            map.put("code",instructDH.getCode());
            Object o = JsonUtils.jsonToPojo((String) obj, Object.class);
            map.put("data",o);
            map.put("serialNumber",device.getSerialNumber());
            map.put("adminPwd", device.getAdminPwd());
//            String msg = JsonUtils.objectToJson(map);
            String msg = JSON.toJSONString(map, SerializerFeature.WriteNonStringValueAsString, SerializerFeature.WriteDateUseDateFormat);
            channel.basicPublish(instructDH.getExchangeName(), device.getDeviceFactory() + "." + instructDH.getCode(),  null, msg.getBytes(StandardCharsets.UTF_8));

            //  更新指令推送日志
            instructPublishLog.setLastResendTime(new Date());
            instructPublishLog.setPublishData(JSON.toJSONString(o, SerializerFeature.WriteNonStringValueAsString, SerializerFeature.WriteDateUseDateFormat));
            this.instructPublishLogService.updateById(instructPublishLog);
        } finally {
            //  关闭连接,抛出异常
            RabbitMqConnectionUtil.closeConnection(connection,channel);
        }
    }

    private void qlPushInstruct(Object obj, List<Device> deviceList, Instruct instruct) {
        String code = instruct.getCode();
        switch (code){
            case "add_user_face":
                UserInfoDto add = BeanUtil.toBean(obj, UserInfoDto.class);
                QLinUser q1 = new QLinUser();
                q1.setUserId(add.getId());
                String deviceIds = deviceList.stream().map(Device::getSerialNumber).collect(Collectors.joining(","));
                q1.setDeviceIds(deviceIds);
                Map<String, Object> map = add.getFaceCodeList().get(0);
                String imgSrc = (String)map.get("imgSrc");
                q1.setFaceUrl(imgSrc);
                Date endDate = add.getEndDate();
                if(endDate != null){
                    String strDateFormat = "yyyy-MM-dd HH:mm:ss";
                    SimpleDateFormat sdf = new SimpleDateFormat(strDateFormat);
                    q1.setExpires(sdf.format(add.getEndDate()));
                }
                this.qLinFeign.addUser(q1);
                break;
            case "delete_user":
                UserInfoDto del1 = BeanUtil.toBean(obj, UserInfoDto.class);
                QLinUser q2 = new QLinUser();
                q2.setUserId(del1.getId());
                this.qLinFeign.deleteUser(q2);
                break;
            case "delete_user_face":
                UserInfoDto del2 = BeanUtil.toBean(obj, UserInfoDto.class);
                QLinUserDevice q3 = new QLinUserDevice();
                q3.setUserId(del2.getId());
                String deviceIds2 = deviceList.stream().map(Device::getSerialNumber).collect(Collectors.joining(","));
                q3.setDeviceIds(deviceIds2);
                this.qLinFeign.deleteUserPermission(q3);
                break;
        }
//        Map<String, String> map = new HashMap<>();
//        //小区
//        if (!StringUtils.isEmpty(device.getXqName())){
//            map.put("xqName",device.getXqName());
//        }else {
//            map.put("xqName",null);
//        }
//        //单元
//        if (!StringUtils.isEmpty(device.getDyName())){
//            map.put("unitName",device.getDyName());
//        }else {
//            map.put("unitName",null);
//        }
//        long id = IdWorker.getId();
//        InstructPublishLog publishLog = null;
//        String data = JsonUtils.objectToJson(obj);
//        publishLog = new InstructPublishLog();
//        id,device, instruct, data, map
//        this.instructPublishLogService.insert(publishLog);
    }
}
