package com.xy.service.factory.device.impl.alipay;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.xy.alipay.SpiDeviceService;
import com.xy.annotate.Factory;
import com.xy.collections.list.JArrayList;
import com.xy.collections.list.JList;
import com.xy.collections.map.JMap;
import com.xy.config.DeviceThreadPoolConfig;
import com.xy.constants.SpiResponseConst;
import com.xy.device.EnumDeviceOnlineStatus;
import com.xy.dto.*;
import com.xy.dto.spi.*;
import com.xy.entity.DeviceInfo;
import com.xy.entity.DeviceStatus;
import com.xy.event.DeviceEvent;
import com.xy.service.*;
import com.xy.service.factory.device.DeviceFactory;
import com.xy.service.factory.device.impl.open.OpenDeviceFactoryImpl;
import com.xy.utils.*;
import com.xy.utils.enums.DeviceErrorRecordTypesEnum;
import com.xy.vo.DeviceDetailVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;

/**
 * 支付宝开门柜设备 接口实现
 */
@Slf4j
@Factory(type = "2")
@AllArgsConstructor
@Api(tags = "支付宝开门柜设备")
public class AliPayOpenDeviceFatoryImpl implements DeviceFactory, SpiDeviceService {

    private OpenDeviceFactoryImpl openDeviceFactoryImpl;

    private AlipayDeviceService alipayDeviceService;

    private DeviceStatusServiceImpl deviceStatusService;

    private DeviceInfoServiceImpl deviceInfoService;

    private DeviceEventMsgServiceImpl deviceEventMsgService;

    private DeviceSysinfoServiceImpl deviceSysinfoService;

    private DeviceMqttSendService deviceMqttSendService;

    private DeviceTempSetServiceImpl deviceTempSetService;

    private DeviceSetRecordsServiceImpl deviceSetRecordsService;

    @Override
    public R save(DeviceRegisterDto.Save save) {
        R r = openDeviceFactoryImpl.save(save);
        //支付宝设备注册
        if (r.getCode() == R.Enum.SUCCESS.getCode()) {
            alipayDeviceService.deviceAssign(new DeviceAssignDTO()
                    .setTerminalId(String.valueOf(save.getDeviceId()))
                    .setDeviceIdentifyType("DYNAMIC")
                    .setDeviceSn(save.getDeviceSn())
                    .setDeviceName(String.valueOf(save.getDeviceId()))
            );
        }
        return r;
    }

    @Override
    public R active(MercMiniDeviceDto.Active2 active) {
        active.setIsDeviceAlgorithmCharging(false);
        R<List<MercMiniDeviceDto.ActiveVo>> result = openDeviceFactoryImpl.active(active);
        if (R.Enum.FAIL.getCode() == result.getCode()) {
            return result;
        }
        //获取差集，得到已激活设备
        JList<Long> costDeviceIds = new JArrayList<>(result.getData()).getProperty(MercMiniDeviceDto.ActiveVo::getDeviceId);
        JList<Long> deviceIds = new JArrayList<>(active.getDeviceId());
        JList<Long> diff = deviceIds.diff(costDeviceIds);
        //查询设备系统信息
        List<DeviceSysinfoDto.Vo> deviceSysinfos = deviceSysinfoService.list(new DeviceSysinfoDto.SelectList().setDeviceIds(diff)).getData();
        JMap<Long, DeviceSysinfoDto.Vo> deviceSysinfosJMaps = new JArrayList<>(deviceSysinfos).toMap(DeviceSysinfoDto.Vo::getDeviceId).cover();
        //查询设备信息
        List<DeviceInfoDto.Vo> deviceInfos = deviceInfoService.list(new DeviceInfoDto.ListDto().setDeviceIds(diff)).getData();
        JMap<Long, DeviceInfoDto.Vo> deviceInfosJMaps = new JArrayList<>(deviceInfos).toMap(DeviceInfoDto.Vo::getDeviceId).cover();
        ThreadPoolUtils.Execute execute = ThreadPoolUtils.excPoll(DeviceThreadPoolConfig.ALI_DEVICE_ACTIVE, diff.size());
        diff.forEach(deviceId -> execute.execute(() -> {
            DeviceSysinfoDto.Vo deviceSysinfo = deviceSysinfosJMaps.get(deviceId);
            DeviceInfoDto.Vo deviceInfo = deviceInfosJMaps.get(deviceId);
            //调用支付宝绑定
            R.feignCheckData(alipayDeviceService.deviceBind(new BindDeviceDTO()
                    .setTerminalId(String.valueOf(deviceId))
                    .setBoardSn(deviceSysinfo.getDeviceSn()))
            );
            //调用支付宝激活
            R.feignCheckData(alipayDeviceService.deviceActive(new BindActiveDTO()
                    .setTerminalId(String.valueOf(deviceId))
                    .setBoardSn(deviceSysinfo.getDeviceSn())
                    .setDeviceName(StrUtil.isNotEmpty(deviceInfo.getDeviceName()) ? deviceInfo.getDeviceName() : String.valueOf(deviceId)))
            );
        }));
        execute.end();
        return result;
    }

    @Override
    public R restActive(MercMiniDeviceDto.RestActive restActive) {
        return openDeviceFactoryImpl.restActive(restActive);
    }

    @Override
    public R<List<Tuple.Tuple3<Long, Long, Boolean>>> senCommand(List<CommandMqtt> commandMqtts) {
        commandMqtts.forEach(commandMqtt -> {
            JSONObject templet = commandMqtt.getTemplet();
            //设备ID
            String terminalId = commandMqtt.getDeviceId().toString();
            //重启设备
            if (templet.getStr("cmdType").equals("task")) {
                JSONObject data = templet.getJSONObject("data");
                if (data.getStr("type").equals("system") && data.getStr("task").equals("reboot")) {
                    alipayDeviceService.deviceReboot(commandMqtt.getDeviceId().toString());
                }
            }
            //声音、温度设置
            if (templet.getStr("cmdType").equals("sets")) {
                JSONObject data = templet.getJSONObject("data");
                if (data.getStr("type").equals("voice")) {
                    //声音设置
                    Integer value = data.getInt("alound");
                    alipayDeviceService.setAttributes(new DeviceSetAttr().setTerminalId(terminalId).setType("VOL").setValue(String.valueOf(value)));
                    DeviceSetRecordsDto.Save save = new DeviceSetRecordsDto.Save();
                    save.setType("voice")
                            .setDeviceId(commandMqtt.getDeviceId())
                            .setContent(data.toString());
                    deviceSetRecordsService.save(save);
                } else if (data.getStr("type").equals("temperature")) {
                    //温度设置
                    Integer value = data.getInt("target");
                    alipayDeviceService.setAttributes(new DeviceSetAttr().setTerminalId(terminalId).setType("TEMP").setValue(String.valueOf(value)));
                    //写入记录
                    DeviceTempSetDto.Save save = new DeviceTempSetDto.Save()
                            .setDeviceId(commandMqtt.getDeviceId())
                            .setWorkMode(data.getStr("workModel"))
                            .setTempValue(data.getInt("target"))
                            .setTempValue1(data.getInt("target1"))
                            .setTempValue2(data.getInt("target2"))
                            .setTempValue3(data.getInt("target3"))
                            .setJob1StartTime(data.getInt("start1"))
                            .setJob1EndTime(data.getInt("end1"))
                            .setJob2StartTime(data.getInt("start2"))
                            .setJob2EndTime(data.getInt("end2"))
                            .setJob3StartTime(data.getInt("start3"))
                            .setJob3EndTime(data.getInt("end3"));
                    String warningLow = data.getStr("warningLow");
                    String warningHigh = data.getStr("warningHigh");
                    if (!warningLow.equals("$warningLow$")) {
                        save.setWarningLow(Integer.valueOf(warningLow));
                    }
                    if (!warningHigh.equals("$warningHigh$")) {
                        save.setWarningHigh(Integer.valueOf(warningHigh));
                    }
                    deviceTempSetService.save(save);
                }
            }
        });
        return R.ok();
    }

    @Override
    public R netRecord(DeviceNetRecordDto.Save save) {
        DeviceStatus deviceStatus = deviceStatusService.getById(save.getDeviceId());
        if (save.getNetStatus().equals(deviceStatus.getNetState())) {
            return R.ok();
        }
        Integer value = SysDictUtils.getValue(EnumDeviceOnlineStatus.Code.CODE.getCode(), EnumDeviceOnlineStatus.CONNECTED.getCode(), Integer.class);
        JSONObject jsonObject = new JSONObject().set("clientid", save.getDeviceId());
        if (save.getNetStatus().equals(value)) {
            deviceMqttSendService.connected(new MqttDto.RequestParams().setData(jsonObject.toString()));
        } else {
            deviceMqttSendService.disconnected(new MqttDto.RequestParams().setData(jsonObject.toString()));
        }
        return R.ok();
    }

    @Override
    public R eventMsg(DeviceEventMsgDto.Save save) {
        return openDeviceFactoryImpl.eventMsg(save);
    }

    @Override
    public R busyStatus(MercMiniDeviceDto.BusySate busySate) {
        //支付宝设备运营状态修改为异步，业务方仅仅发送了修改通知，结果由支付宝异步通知
        DeviceChangeStatusDTO deviceChangeStatusDTO = new DeviceChangeStatusDTO()
                .setTerminalId(String.valueOf(busySate.getDeviceId()))
                .setOperatorId(AuthorizeUtils.getLoginId(String.class))
                .setStatus(busySate.getBusyState());
        alipayDeviceService.changeStatus(deviceChangeStatusDTO);
        //发布事件
        DeviceEvent.BusyStateEvent busyStateEvent = new DeviceEvent.BusyStateEvent()
                .setBusyState(busySate.getBusyState());
        busyStateEvent.setValue(busySate.getDeviceId());
        EventUtils.push(() -> busyStateEvent);
        return R.ok();
    }


    @Override
    @ApiOperation("设备告警通知SPI")
    public String deviceAlarmNotify(DeviceAlarmNotifyDTO deviceAlarmNotifyDTO) {
        //查询设备信息
        DeviceInfo deviceInfo = deviceInfoService.getById(Long.valueOf(deviceAlarmNotifyDTO.getTerminalId()));
        String faultCode = deviceAlarmNotifyDTO.getFaultCode();
        DeviceErrorRecordTypesEnum deviceErrorRecordTypesEnum = faultCode.equals("DeviceOffline") ? DeviceErrorRecordTypesEnum.NET
                : faultCode.equals("DoorOpenedOnNoTrade") ? DeviceErrorRecordTypesEnum.DOOR_LOCK
                : null;
        if (deviceErrorRecordTypesEnum == null) {
            return SpiResponseConst.SUCCESS;
        }
        //添加设备异常记录
        DeviceEventMsgDto.Save save = new DeviceEventMsgDto.Save()
                .setDeviceId(deviceInfo.getDeviceId());
        save.setCode(deviceErrorRecordTypesEnum.getCode());
        save.setMsg(deviceAlarmNotifyDTO.getFaultMsg());
        deviceEventMsgService.save(save);
        return SpiResponseConst.SUCCESS;
    }

    /**
     * 设备故障恢复通知
     * 触发条件：设备故障会恢复时，货柜云平台也会展示
     * 使用场景：接收设备恢复，进行相应业务处理和任务触发
     * <p>
     * 1	NFC1001	NFC和上位机断连	请检查NFC设备硬件连接情况，无法恢复请报修
     * 2	NFC1002	NFC模块初始化失败	请重启设备，若故障未恢复，请报修；
     * 3	NFC1003	NFC写入失败 	请重启设备，若故障未恢复，请报修；
     * 4	DST1001	设备停止营业	后台设置为停业
     * 5	NFC1004  	Nx设备无网络或与服务器失联	请检查网络连接
     *
     * @param deviceAlarmRecoverNotifyDTO
     * @return
     */
    @Override
    public String recoveryInform(DeviceAlarmRecoverNotifyDTO deviceAlarmRecoverNotifyDTO) {
        //TODO 设备故障恢复通知
        log.info("设备故障恢复通知：{}", JSONUtil.toJsonPrettyStr(deviceAlarmRecoverNotifyDTO));
        return SpiResponseConst.SUCCESS;
    }

    @ApiOperation("设备异常通知SPI")
    @Override
    public String deviceExceptionNotify(DeviceExceptionNotifyDTO deviceExceptionNotifyDTO) {
        String terminalId = deviceExceptionNotifyDTO.getTerminalId();
        String action = deviceExceptionNotifyDTO.getAction();
        String exceptionDesc = deviceExceptionNotifyDTO.getExceptionDesc();
        //triggerTime 示例 1551156574510
        Long triggerTime = deviceExceptionNotifyDTO.getTriggerTime();
        LocalDateTime triggerDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(triggerTime), ZoneId.systemDefault());
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String formattedDateTime = triggerDateTime.format(formatter);
        String devCode = "";
        if ("恢复".equals(action)) {
            if (exceptionDesc.contains("重力")) {
                devCode = "DEV6004";
            } else if (exceptionDesc.contains("摄像头")) {
                devCode = "DEV6001";
            }
        } else if ("故障".equals(action)) {
            if (exceptionDesc.contains("重力")) {
                devCode = "DEV6003";
            } else if (exceptionDesc.contains("摄像头")) {
                devCode = "DEV6000";
            }
        }

        if (StrUtil.isEmpty(devCode)) {
            log.error("设备异常通知SPI->未知的异常类型：{}", JSONUtil.toJsonPrettyStr(deviceExceptionNotifyDTO));
            return SpiResponseConst.FAIL;
        }
        //添加设备异常记录
        DeviceEventMsgDto.Save save = new DeviceEventMsgDto.Save()
                .setDeviceId(Long.valueOf(terminalId));
        save.setCode(devCode);
        save.setMsg(exceptionDesc + ";发生时间：" + formattedDateTime);
        deviceEventMsgService.save(save);

        return SpiResponseConst.SUCCESS;
    }

    @Override
    @ApiOperation("设备状态变更通知SPI")
    public String deviceStatusChangeNotify(DeviceStatusChangeNotifyDTO deviceStatusChangeNotifyDTO) {
        //查询设备信息
        DeviceInfo deviceInfo = deviceInfoService.getById(Long.valueOf(deviceStatusChangeNotifyDTO.getTerminalId()));
        //冻结状态、运营状态
        FunctionUtils.NoParamsNoResult busyStatus = () -> {
            Integer status = deviceStatusChangeNotifyDTO.getStatus();
            DeviceInfo updateDeviceInfo = new DeviceInfo()
                    .setDeviceId(deviceInfo.getDeviceId())
                    .setBusyState(status)
                    .setFreezeStatus(status);
            deviceInfoService.updateById(updateDeviceInfo);
        };
        //联网状态
        FunctionUtils.NoParamsNoResult netRecord = () -> {
            Boolean online = deviceStatusChangeNotifyDTO.getOnline();
            if (online != null) {
                int netState = online ? SysDictUtils.getValue(EnumDeviceOnlineStatus.Code.CODE.getCode(), EnumDeviceOnlineStatus.CONNECTED.getCode(), Integer.class)
                        : SysDictUtils.getValue(EnumDeviceOnlineStatus.Code.CODE.getCode(), EnumDeviceOnlineStatus.DISCONNECT.getCode(), Integer.class);
                DeviceNetRecordDto.Save save = new DeviceNetRecordDto.Save()
                        .setDeviceId(deviceInfo.getDeviceId());
                save.setNetStatus(netState);
                netRecord(save);
            }
        };
        //激活状态
        FunctionUtils.NoParamsNoResult active = () -> {
            if (deviceStatusChangeNotifyDTO.getActiveStatus() != null && deviceStatusChangeNotifyDTO.getActiveStatus() == 1) {
                active(new MercMiniDeviceDto.Active2().setDeviceId(Arrays.asList(deviceInfo.getDeviceId())));
            }
        };
        busyStatus.run();
        netRecord.run();
        active.run();
        return SpiResponseConst.SUCCESS;
    }

    @Override
    public String deviceAttrNotify(DeviceAttributesNotifyDTO dto) {
        log.info("温度、音量查询SPI通知：{}", JSONUtil.toJsonPrettyStr(dto));
        String terminalId = dto.getTerminalId();
        Boolean success = dto.getSuccess();
        String type = dto.getType();
        String value = dto.getValue();

        //查询设备信息
        DeviceInfoDto.Vo deviceInfo = R.feignCheckData(deviceInfoService.obj(new DeviceInfoDto.Obj().setIsSysinfo(true).setDeviceId(Long.valueOf(terminalId))));
        if (deviceInfo == null) {
            log.warn("温度、音量查询SPI通知,无设备信息");
            return SpiResponseConst.SUCCESS;
        }
        DeviceSysinfoDto.Vo deviceSysinfo = deviceInfo.getDeviceSysinfo();
        boolean isHaveNfc = false;
        if (deviceSysinfo != null && StrUtil.isNotEmpty(deviceSysinfo.getNfcSn())) {
            isHaveNfc = true;
        }
        //温度更新
        FunctionUtils.NoParamsNoResult updateTmp = () -> {
            DeviceStatusDto.Up up = new DeviceStatusDto.Up();
            up.setDeviceId(deviceInfo.getDeviceId())
                    .setTempState(0)
                    .setTempValue(Integer.valueOf(value));
            deviceStatusService.up(up);
        };

        DeviceStatus deviceStatus = deviceStatusService.getById(deviceInfo.getDeviceId());
        if (deviceStatus == null && !isHaveNfc) {
            log.warn("温度、音量查询SPI通知,无设备状态记录");
            return SpiResponseConst.SUCCESS;
        }
        //音量更新
        FunctionUtils.NoParamsNoResult updateVoice = () -> {
            DeviceStatusDto.Up up = new DeviceStatusDto.Up();
            up.setDeviceId(deviceInfo.getDeviceId())
                    .setVoiceVolume(Integer.valueOf(value));
            deviceStatusService.up(up);
        };

        if (BooleanUtil.isTrue(success) && !isHaveNfc) {
            //温度：TEMP 音量：VOL
            if ("VOL".equals(type)) {
                updateVoice.run();
            } else if ("TEMP".equals(type)) {
                updateTmp.run();
            }
        }
        return SpiResponseConst.SUCCESS;
    }

    @Override
    public String deviceAttrSetNotify(DeviceSetAttributesNotifyDTO dto) {
        log.info("温度、音量设置SPI通知：{}", JSONUtil.toJsonPrettyStr(dto));
        String terminalId = dto.getTerminalId();
        Boolean success = dto.getSuccess();
        String type = dto.getType();
        String value = dto.getValue();

        //查询设备信息
        DeviceInfoDto.Vo deviceInfo = R.feignCheckData(deviceInfoService.obj(new DeviceInfoDto.Obj().setIsSysinfo(true).setDeviceId(Long.valueOf(terminalId))));
        if (deviceInfo == null) {
            log.warn("温度、音量查询SPI通知,无设备信息");
            return SpiResponseConst.SUCCESS;
        }
        DeviceSysinfoDto.Vo deviceSysinfo = deviceInfo.getDeviceSysinfo();
        boolean isHaveNfc = false;
        if (deviceSysinfo != null && StrUtil.isNotEmpty(deviceSysinfo.getNfcSn())) {
            isHaveNfc = true;
        }

        DeviceStatus deviceStatus = deviceStatusService.getById(deviceInfo.getDeviceId());
        if (deviceStatus == null && !isHaveNfc) {
            log.warn("温度、音量设置SPI通知,无设备状态记录");
            return SpiResponseConst.SUCCESS;
        }
        //音量更新
        FunctionUtils.NoParamsNoResult updateVoice = () -> {
            deviceStatus.setVoiceVolume(Integer.valueOf(value));
            deviceStatusService.updateById(deviceStatus);
        };
        //温度更新
        FunctionUtils.NoParamsNoResult updateTmp = () -> {
            deviceStatus.setTempState(Integer.valueOf(value));
            deviceStatusService.updateById(deviceStatus);
        };

        if (BooleanUtil.isTrue(success) && !isHaveNfc) {
            //温度：TEMP 音量：VOL
            if ("VOL".equals(type)) {
                updateVoice.run();
            } else if ("TEMP".equals(type)) {
                updateTmp.run();
            }
        }
        return SpiResponseConst.SUCCESS;
    }

    @ApiOperation("刷新设备联网状态API")
    @PostMapping("refurbishDeviceNetWork")
    public R refurbishDeviceNetWork(@RequestBody List<Long> deviceIds) {
        ThreadPoolUtils.Execute execute = ThreadPoolUtils.excPoll(DeviceThreadPoolConfig.ALIPAY_DEVICE_DETAIL, deviceIds.size());
        deviceIds.forEach(deviceId -> execute.execute(() -> {
            DeviceDetailVO deviceDetailVO = alipayDeviceService.queryDetail(new DeviceDetailDTO().setTerminalId(String.valueOf(deviceId)));
            if (deviceDetailVO == null) {
                return;
            }
            DeviceNetRecordDto.Save save = new DeviceNetRecordDto.Save().setDeviceId(deviceId);
            save.setNetStatus(deviceDetailVO.getOnline()
                    ? SysDictUtils.getValue(EnumDeviceOnlineStatus.Code.CODE.getCode(), EnumDeviceOnlineStatus.CONNECTED.getCode(), Integer.class)
                    : SysDictUtils.getValue(EnumDeviceOnlineStatus.Code.CODE.getCode(), EnumDeviceOnlineStatus.DISCONNECT.getCode(), Integer.class));
            netRecord(save);
        }));
        execute.end();
        return R.ok();
    }

}
