package com.xy.service;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yitter.idgen.YitIdHelper;
import com.xy.annotation.Lock;
import com.xy.collections.list.JArrayList;
import com.xy.collections.list.JList;
import com.xy.collections.map.JHashMap;
import com.xy.collections.map.JMap;
import com.xy.config.DeviceThreadPoolConfig;
import com.xy.dto.DeviceCreateIdsDto;
import com.xy.dto.DeviceQualityDto;
import com.xy.dto.DeviceRegisterDto;
import com.xy.dto.be.MercDto;
import com.xy.entity.*;
import com.xy.mapper.DeviceCreateIdsMapper;
import com.xy.mapper.DeviceRegisterMapper;
import com.xy.service.be.MercService;
import com.xy.service.factory.device.DeviceFactory;
import com.xy.utils.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import static com.xy.utils.PlusBeans.*;

/**
 * <p>
 * 设备-注册登记 服务实现类
 * </p>
 *
 * @author lijin
 * @since 2022-12-29
 */
@Service
@Api(tags = "设备-注册登记")
@AllArgsConstructor(onConstructor_ = @Lazy)
public class DeviceRegisterServiceImpl extends ServiceImpl<DeviceRegisterMapper, DeviceRegister> implements DeviceRegisterService {

    private DeviceCreateIdsMapper deviceCreateIdsMapper;

    private DeviceQualityServiceImpl deviceQualityService;

    private MqttUserServiceImpl mqttUserService;

    private DeviceSysinfoServiceImpl deviceSysinfoService;

    private DeviceCreateIdsServiceImpl deviceCreateIdsService;

    private MercService mercService;

    private DeviceInfoServiceImpl deviceInfoService;

    @PostMapping("save")
    @ApiOperation("添加")
    @Transactional(rollbackFor = Exception.class)
    @Lock(value = "save.deviceId", prefix = "device_register_")
    public R save(@RequestBody @Validated DeviceRegisterDto.Save save) {
        //重复校验
        DeviceRegister deviceRegister = copy(DeviceRegister.class, save.setDeviceSn(save.getDeviceSn().toUpperCase()));
        DeviceCreateIds deviceCreateIds = deviceCreateIdsMapper.selectById(deviceRegister.getDeviceId());
        if (deviceCreateIds == null) {
            return R.fail("设备ID不存在");
        }
        if (deviceCreateIds.getBindState()) {
            return R.fail("设备ID已被绑定");
        }
        long count = count(new LambdaQueryWrapper<DeviceRegister>().eq(DeviceRegister::getDeviceSn, deviceRegister.getDeviceSn()));
        if (count > 0) {
            return R.fail("设备SN已被绑定");
        }
        return FactoryUtils.getServiceRoute(DeviceFactory.class, deviceCreateIds.getDeviceType()).save(save.setDeviceType(deviceCreateIds.getDeviceType()));
    }

    @PostMapping("page")
    @ApiOperation("分页查询")
    public R<PageBean<DeviceRegisterDto.Vo>> page(@RequestBody DeviceRegisterDto.Page page) {
        PageBean pageBean = page.getPage();
        LambdaQueryWrapper<DeviceRegister> lambdaQueryWrapper = new MybatisPlusQuery().eqWrapper(page, DeviceRegister.class)
                .ge(DeviceRegister::getCreateTime, page.getBeginCreateTime())
                .le(DeviceRegister::getCreateTime, page.getEndCreateTime())
                .likeRight(DeviceRegister::getDeviceSn)
                .build()
                .and(page.getIsBind() != null && page.getIsBind(), deviceRegisterLambdaQueryWrapper -> deviceRegisterLambdaQueryWrapper
                        .isNotNull(DeviceRegister::getDeviceSn)
                        .ne(DeviceRegister::getDeviceSn, "")
                )
                .and(page.getIsBind() != null && !page.getIsBind(), deviceRegisterLambdaQueryWrapper -> deviceRegisterLambdaQueryWrapper
                        .isNull(DeviceRegister::getDeviceSn)
                        .or()
                        .eq(DeviceRegister::getDeviceSn, "")
                )
                .orderByDesc(!Emptys.check(pageBean.getOrders()), DeviceRegister::getCreateTime);
        IPage<DeviceRegister> iPage = page(toIPage(pageBean), lambdaQueryWrapper);
        PageBean<DeviceRegisterDto.Vo> voPageBean = toPageBean(DeviceRegisterDto.Vo.class, iPage);
        List<DeviceRegisterDto.Vo> list = voPageBean.getRecords();
        if (list.size() > 0) {
            //查询质检
            JArrayList<DeviceRegisterDto.Vo> deviceRegisters = new JArrayList<>(list);
            JList<Long> deviceIds = deviceRegisters.getProperty(DeviceRegisterDto.Vo::getDeviceId);
            List<DeviceQualityDto.Vo> vos = deviceQualityService.list(new DeviceQualityDto.SelectList().setDeviceIds(deviceIds)
                    .setShowStatus(true)
            ).getData();
            JMap<Long, DeviceQualityDto.Vo> map = new JArrayList<>(vos).toMap(DeviceQualityDto.Vo::getDeviceId).cover();
            List<DeviceInfo> deviceInfos = deviceInfoService.listByIds(deviceIds);
            JMap<Long, DeviceInfo> deviceInfoJMaps = new JArrayList<>(deviceInfos).toMap(DeviceInfo::getDeviceId).cover();

            //查询机器ID号
            List<DeviceCreateIds> deviceCreateIds = deviceCreateIdsService.list(new LambdaQueryWrapper<DeviceCreateIds>()
                    .in(DeviceCreateIds::getDeviceId, deviceIds)
                    .isNotNull(DeviceCreateIds::getDeviceQrMercId)
                    .ge(DeviceCreateIds::getDeviceQrMercId, 0)
            );
            JMap<Long, DeviceCreateIds> deviceCreateIdsJMaps;
            JMap<Long, MercDto.Vo> mercsJMaps;
            if (Emptys.check(deviceCreateIds)) {
                JList<DeviceCreateIds> deviceCreateIdss = new JArrayList<>(deviceCreateIds);
                deviceCreateIdsJMaps = deviceCreateIdss.toMap(DeviceCreateIds::getDeviceId).cover();
                JList<Long> mercIds = deviceCreateIdss.getProperty(DeviceCreateIds::getDeviceQrMercId);
                List<MercDto.Vo> mercs = mercService.list(new MercDto.SelectList().setMercIds(mercIds)).getData();
                mercsJMaps = new JArrayList<>(mercs).toMap(MercDto.Vo::getId).cover();
            } else {
                deviceCreateIdsJMaps = new JHashMap<>();
                mercsJMaps = new JHashMap<>();
            }
            for (DeviceRegisterDto.Vo deviceRegister : deviceRegisters) {
                //质检信息
                DeviceQualityDto.Vo vo = map.get(deviceRegister.getDeviceId());
                if (Emptys.check(vo)) {
                    deviceRegister.setFactoryState(vo.getFactoryState()).setQualityState(vo.getQualityState());
                }
                DeviceInfo deviceInfo = deviceInfoJMaps.get(deviceRegister.getDeviceId());
                if (Emptys.check(deviceInfo)) {
                    //货道数反显
                    deviceRegister.setAisleNum(deviceInfo.getAisleNum());
                }
                //机器ID号商户信息
                DeviceCreateIds deviceCreateIdsInfo = deviceCreateIdsJMaps.get(deviceRegister.getDeviceId());
                if (Emptys.check(deviceCreateIdsInfo)) {
                    MercDto.Vo merc = mercsJMaps.get(deviceCreateIdsInfo.getDeviceQrMercId());
                    if (Emptys.check(merc)) {
                        deviceRegister.setDeviceQrMercId(merc.getId()).setDeviceQrMercName(merc.getName());
                    }
                }
            }
        }
        return R.ok(voPageBean);
    }

    @PostMapping("obj")
    @ApiOperation("对象查询")
    public R<DeviceRegisterDto.Vo> obj(@RequestBody DeviceRegisterDto.Vo vo) {
        LambdaQueryWrapper<DeviceRegister> lambdaQueryWrapper = new MybatisPlusQuery().eqWrapper(vo, DeviceRegister.class).build();
        List<DeviceRegister> list = list(lambdaQueryWrapper);
        if (!Emptys.check(list)) {
            return R.ok();
        }
        DeviceRegisterDto.Vo deviceRegister = copy(DeviceRegisterDto.Vo.class, list.get(0));
        DeviceQualityDto.Vo deviceQuality = deviceQualityService.obj(new DeviceQualityDto.Obj()
                .setDeviceId(deviceRegister.getDeviceId())
                .setShowStatus(true)
        ).getData();
        if (deviceQuality != null) {
            deviceRegister.setFactoryState(deviceQuality.getFactoryState()).setQualityState(deviceQuality.getQualityState());
        }
        return R.ok(deviceRegister);
    }

    @PostMapping("list")
    @ApiOperation("集合查询")
    public R<List<DeviceRegisterDto.Vo>> list(@RequestBody DeviceRegisterDto.SelectList selectList) {
        LambdaQueryWrapper<DeviceRegister> lambdaQueryWrapper = new MybatisPlusQuery().eqWrapper(selectList, DeviceRegister.class)
                .in(DeviceRegister::getDeviceId, selectList.getDeviceIds())
                .in(DeviceRegister::getDeviceSn, selectList.getDeviceSns())
                .in(DeviceRegister::getDeviceType, selectList.getDeviceTypes())
                .in(DeviceRegister::getDevicePc, selectList.getDevicePcs())
                .in(DeviceRegister::getAssetNo, selectList.getAssetNos())
                .build();
        List<DeviceRegister> list = list(lambdaQueryWrapper);
        return R.ok(copy(DeviceRegisterDto.Vo.class, list));
    }

    @ApiOperation("修改sn")
    @PostMapping("updateSn")
    @Transactional(rollbackFor = Exception.class)
    public R updateSn(@RequestBody @Validated DeviceRegisterDto.UpdateSn updateSn) {
        //校验
        String deviceSn = updateSn.getDeviceSn();
        Long deviceId = updateSn.getDeviceId();
        if (Emptys.check(deviceSn)) {
            DeviceRegister deviceRegister = getById(deviceId);
            if (!deviceSn.equals(deviceRegister.getDeviceSn())) {
                long count = count(new LambdaQueryWrapper<DeviceRegister>().eq(DeviceRegister::getDeviceSn, deviceSn));
                if (count > 0) {
                    return R.fail("sn号已被绑定");
                }
            }
        }
        //修改注册等级
        update(new LambdaUpdateWrapper<DeviceRegister>()
                .set(DeviceRegister::getDeviceSn, updateSn.getDeviceSn())
                .eq(DeviceRegister::getDeviceId, deviceId));
        //修改系统信息
        deviceSysinfoService.update(new LambdaUpdateWrapper<DeviceSysinfo>()
                .set(DeviceSysinfo::getDeviceSn, updateSn.getDeviceSn())
                .eq(DeviceSysinfo::getDeviceId, deviceId));
        //修改mqtt认证
        mqttUserService.update(new LambdaUpdateWrapper<MqttUser>()
                .set(MqttUser::getSn, deviceSn)
                .eq(MqttUser::getDeviceId, deviceId));
        //机器ID号生成清除绑定
        if (!Emptys.check(deviceSn)) {
            deviceCreateIdsService.update(new LambdaUpdateWrapper<DeviceCreateIds>()
                    .set(DeviceCreateIds::getBindState, false)
                    .set(DeviceCreateIds::getBindTime, null)
                    .eq(DeviceCreateIds::getDeviceId, deviceId)
            );
        }
        return R.ok();
    }

    @ApiOperation("上传sn注册设备")
    @PostMapping("uploadSaveBySn")
    public R uploadSaveBySn(@RequestParam("file") MultipartFile file) {
        ThreadPoolUtils.excPoll(DeviceThreadPoolConfig.DEVICE_COMMON_POLL, 1)
                .execute(() -> {
                    try {
                        EasyExcel.read(file.getInputStream(), UploadSaveBySn.class, new UploadSaveBySnListener(deviceCreateIdsService, this)).sheet().doRead();
                    } catch (IOException e) {
                        log.error("", e);
                    }
                });
        return R.ok();
    }

    @SneakyThrows
    @ApiOperation("下载sn注册设备模板")
    @PostMapping("downloadSnTemplet")
    public void downloadSnTemplet(HttpServletResponse response) {
        InputStream inputStream = IoUtils.inputStream("sn_templet.xlsx").get();
        response.setHeader("Content-Disposition", "attachment; filename=" + "sn_templet.xlsx");
        response.setContentType("application/xlsx");
        byte[] buffer = new byte[1024];
        int bytesRead;
        OutputStream outputStream = response.getOutputStream();
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        inputStream.close();
        outputStream.close();
    }

    @Slf4j
    @RequiredArgsConstructor
    public static class UploadSaveBySnListener implements ReadListener<UploadSaveBySn> {

        private final DeviceCreateIdsServiceImpl deviceCreateIdsService;

        private final DeviceRegisterServiceImpl deviceRegisterService;

        private JList<UploadSaveBySn> sns = new JArrayList<>();

        /**
         * 这个每一条数据解析都会来调用
         *
         * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
         * @param context
         */
        @Override
        public void invoke(UploadSaveBySn data, AnalysisContext context) {
            String deviceType = data.getDeviceType();
            if (deviceType == null) {
                return;
            }
            Integer deviceTypeValue = deviceType.equals("动态视觉柜(单门)") ? 1
                    : deviceType.equals("动态视觉柜(双门)") ? 2
                    : deviceType.equals("重力柜(单门)") ? 3
                    : deviceType.equals("重力柜(双门)") ? 4
                    : deviceType.equals("支付宝视动态觉柜") ? 5
                    : null;
            if (deviceTypeValue == null) {
                return;
            }
            if (!Emptys.check(data.getDevicePc())) {
                return;
            }
            data.setDeviceTypeValue(deviceTypeValue);
            sns.add(data);
        }

        /**
         * 所有数据解析完成了 都会来调用
         *
         * @param context
         */
        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            log.info("上传sn注册设备文件解析完毕，总数量：{}", sns.size());
            JMap<Integer, List<UploadSaveBySn>> group = sns.comparing(UploadSaveBySn::getSn).toMap(UploadSaveBySn::getDeviceTypeValue).group();
            group.forEach((deviceTypeValue, uploadSaveBySns) -> {
                ThreadPoolUtils.Execute execute = ThreadPoolUtils.excPoll(DeviceThreadPoolConfig.DEVICE_COMMON_POLL, uploadSaveBySns.size());
                //生成设备号
                JList<DeviceCreateIds> deviceCreateIds = deviceCreateIdsService.save(new DeviceCreateIdsDto.Save()
                        .setDeviceType(deviceTypeValue)
                        .setDoorType("1")
                        .setCreatePc(String.valueOf(YitIdHelper.nextId()))
                        .setNum(uploadSaveBySns.size())
                ).getData();
                //注册设备
                for (int i = 0; i < uploadSaveBySns.size(); i++) {
                    UploadSaveBySn uploadSaveBySn = uploadSaveBySns.get(i);
                    Long deviceId = deviceCreateIds.get(i).getDeviceId();
                    execute.execute(() -> {
                        DeviceRegisterDto.Save save = new DeviceRegisterDto.Save()
                                .setDeviceId(deviceId)
                                .setDeviceSn(uploadSaveBySn.getSn())
                                .setDevicePc(uploadSaveBySn.getDevicePc())
                                .setAssetNo(uploadSaveBySn.getAssetNo());
                        R r = deviceRegisterService.save(save);
                        if (r.getCode() == R.Enum.FAIL.getCode()) {
                            log.error("{},{}", uploadSaveBySn.getSn(), r.getMsg());
                        }
                    });
                }
                execute.end();
            });
            log.info("上传sn注册设备注册完毕");
        }
    }

    @Data
    public static class UploadSaveBySn {

        /**
         * sn
         */
        private String sn;

        /**
         * 设备类型
         */
        private String deviceType;

        /**
         * 资产编号
         */
        private String assetNo;

        /**
         * 批次号
         */
        private String devicePc;

        /**
         * 设备类型值
         */
        private Integer deviceTypeValue;

    }
}
