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.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.device.EnumDeviceQrCode;
import com.xy.device.EnumDeviceType;
import com.xy.dto.DeviceCreateIdsDto;
import com.xy.dto.DeviceQualityDto;
import com.xy.dto.MercDeviceQrConfigDto;
import com.xy.dto.SysWorkUser.ListDto;
import com.xy.dto.SysWorkUser.SysWorkVo;
import com.xy.dto.be.MercDto;
import com.xy.entity.DeviceCreateIds;
import com.xy.entity.DeviceInfo;
import com.xy.mapper.DeviceCreateIdsMapper;
import com.xy.service.be.MercService;
import com.xy.utils.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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.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.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import static com.xy.utils.Beans.copy;
import static com.xy.utils.PlusBeans.toIPage;
import static com.xy.utils.PlusBeans.toPageBean;

/**
 * <p>
 * 设备-机器ID号生成 服务实现类
 * </p>
 *
 * @author lijin
 * @since 2022-12-29
 */
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = @Lazy)
@Api(tags = "设备-机器ID号生成")
public class DeviceCreateIdsServiceImpl extends ServiceImpl<DeviceCreateIdsMapper, DeviceCreateIds> implements DeviceCreateIdsService {

    private final SysWorkUserService sysWorkUserService;

    private final MercDeviceQrConfigService mercDeviceQrConfigService;

    private final MercService mercService;

    private final DeviceInfoServiceImpl deviceInfoService;

    @SneakyThrows
    @PostMapping("save")
    @ApiOperation("添加")
    @Lock("device-create-ids-save")
    public R<JList<DeviceCreateIds>> save(@RequestBody @Validated DeviceCreateIdsDto.Save save) {
        LocalDate localDate = LocalDate.now();
        String year = String.valueOf(localDate.getYear());
        String month = String.valueOf(localDate.getMonthValue());
        String yearMonth = year.substring(2) + (month.length() == 1 ? "0" + month : month);
        String property = LambdaUtils.getProperty(DeviceCreateIds::getDeviceId);
        QueryWrapper<DeviceCreateIds> queryWrapper = new QueryWrapper<DeviceCreateIds>()
                .select(String.format("max(%s) as %s", StringTools.humpToLine(property), property))
                .eq(StringTools.humpToLine(LambdaUtils.getProperty(DeviceCreateIds::getIsCustom)), false);
        DeviceCreateIds deviceCreateIds = getOne(queryWrapper);
        JMap<Long, DeviceCreateIds> deviceCreateIdss = new JHashMap<>(save.getNum());
        Long loginId = AuthorizeUtils.getLoginId(Long.class);
        for (int i = 0; i < save.getNum(); i++) {
            String tail = null;
            if (deviceCreateIds != null) {
                String substring = String.valueOf(deviceCreateIds.getDeviceId()).substring(0, 4);
                if (substring.equals(yearMonth)) {
                    tail = String.valueOf(deviceCreateIds.getDeviceId() + (i + 1)).substring(4);
                }
            }
            if (tail == null) {
                tail = String.valueOf((i + 1));
                int i0 = 6 - tail.length();
                for (int i1 = 0; i1 < i0; i1++) {
                    tail = 0 + tail;
                }
            }
            Long deviceId = Long.valueOf(yearMonth + tail);
            DeviceCreateIds deviceCreateIdsInfo = copy(DeviceCreateIds.class, save)
                    .setDeviceId(deviceId)
                    .setCreateUserId(loginId)
                    .setCreateTime(LocalDateTime.now());
            deviceCreateIdss.put(deviceId, deviceCreateIdsInfo);
        }
        //去重复
        weight(deviceCreateIdss, save.getNum());
        //添加
        JList<DeviceCreateIds> value = deviceCreateIdss.toList().value();
        JList<JList<DeviceCreateIds>> partition = value.partition(20);
        ThreadPoolUtils.Execute execute = ThreadPoolUtils.excPoll(DeviceThreadPoolConfig.DEVICE_COMMON_POLL, partition.size());
        partition.forEach(createIds -> execute.execute(() -> saveBatch(createIds)));
        execute.end();
        return R.ok(value);
    }

    @PostMapping("saveCustom")
    @ApiOperation("添加自定义机器号")
    @Lock("device-create-ids-save")
    public R saveCustom(@RequestBody @Validated DeviceCreateIdsDto.SaveCustom saveCustom) {
        DeviceCreateIds deviceCreateIds = getById(saveCustom.getDeviceId());
        if (deviceCreateIds != null) {
            return R.fail("机器号已存在");
        }
        DeviceCreateIds deviceCreateIdsInfo = copy(DeviceCreateIds.class, saveCustom)
                .setCreateUserId(AuthorizeUtils.getLoginId(Long.class))
                .setIsCustom(true)
                .setCreateTime(LocalDateTime.now());
        save(deviceCreateIdsInfo);
        return R.ok();
    }

    @PostMapping("page")
    @ApiOperation("分页查询")
    public R<PageBean<DeviceCreateIdsDto.Vo>> page(@RequestBody DeviceCreateIdsDto.Page page) {
        PageBean pageBean = page.getPage();
        LambdaQueryWrapper<DeviceCreateIds> lambdaQueryWrapper = new MybatisPlusQuery().eqWrapper(page, DeviceCreateIds.class)
                .ge(DeviceCreateIds::getCreateTime, page.getBeginCreateTime())
                .le(DeviceCreateIds::getCreateTime, page.getEndCreateTime())
                .ge(DeviceCreateIds::getBindTime, page.getBeginBindTime())
                .le(DeviceCreateIds::getBindTime, page.getEndBindTime())
                .build()
                .orderByDesc(!Emptys.check(pageBean.getOrders()), DeviceCreateIds::getCreateTime, DeviceCreateIds::getDeviceId);
        IPage<DeviceCreateIds> iPage = page(toIPage(pageBean), lambdaQueryWrapper);
        PageBean<DeviceCreateIdsDto.Vo> voPageBean = toPageBean(PlusBeans.copy(DeviceCreateIdsDto.Vo.class, iPage));
        List<DeviceCreateIdsDto.Vo> records = voPageBean.getRecords();
        JList<Long> createUserIds = new JArrayList<>(records).getProperty(DeviceCreateIdsDto.Vo::getCreateUserId);
        //查询创建人
        JMap<Long, SysWorkVo> sysWorkVoJMap;
        List<SysWorkVo> sysWorkVos = sysWorkUserService.list(new ListDto().setId(createUserIds)).getData();
        if (Emptys.check(sysWorkVos)) {
            sysWorkVoJMap = new JArrayList<>(sysWorkVos).toMap(SysWorkVo::getId).cover();
        } else {
            sysWorkVoJMap = new JHashMap<>();
        }
        //查询绑定商户
        JMap<Long, MercDto.Vo> mercsJMaps;
        JList<Long> mercIds = new JArrayList<>(records).filter().isNotNull(DeviceCreateIdsDto.Vo::getDeviceQrMercId).gt(DeviceCreateIdsDto.Vo::getDeviceQrMercId, 0).list().getProperty(DeviceCreateIdsDto.Vo::getDeviceQrMercId);
        if (Emptys.check(mercIds)) {
            List<MercDto.Vo> mercs = mercService.list(new MercDto.SelectList().setMercIds(mercIds)).getData();
            mercsJMaps = new JArrayList<>(mercs).toMap(MercDto.Vo::getId).cover();
        } else {
            mercsJMaps = new JHashMap<>();
        }
        records.forEach(vo -> {
            if (Emptys.check(sysWorkVoJMap)) {
                if (Emptys.check(vo.getCreateUserId())) {
                    SysWorkVo sysWorkVo = sysWorkVoJMap.get(vo.getCreateUserId());
                    if (sysWorkVo != null) {
                        vo.setCreateUserName(sysWorkVo.getAccount());
                    }
                }
            }
            if (Emptys.check(mercsJMaps)) {
                if (Emptys.check(vo.getDeviceQrMercId())) {
                    MercDto.Vo mercVo = mercsJMaps.get(vo.getDeviceQrMercId());
                    if (mercVo != null) {
                        vo.setDeviceQrMercName(mercVo.getName());
                    }
                }
            }
        });
        return R.ok(voPageBean);
    }

    @Override
    @ApiOperation("修改")
    public R update(DeviceCreateIdsDto.Update update) {
        DeviceCreateIds deviceCreateIds = copy(DeviceCreateIds.class, update);
        updateById(deviceCreateIds);
        return R.ok();
    }

    @Override
    @ApiOperation("批量修改")
    public R updateBatch(DeviceCreateIdsDto.UpdateBatch updateBatch) {
        List<Long> deviceIds = updateBatch.getDeviceIds();
        List<DeviceCreateIds> deviceCreateIdss = new ArrayList<>(deviceIds.size());
        deviceIds.forEach(deviceId -> {
            DeviceCreateIds deviceCreateIds = copy(DeviceCreateIds.class, updateBatch).setDeviceId(deviceId);
            deviceCreateIdss.add(deviceCreateIds);
        });
        updateBatchById(deviceCreateIdss);
        return R.ok();
    }

    @PostMapping("getQrCode")
    @ApiOperation("获取二维码")
    public R<List<String>> getQrCode(@RequestBody @Validated DeviceQualityDto.Qrcode qrcode) {
        //默认二维码
        String qrUrl = SysDictUtils.getValue(EnumDeviceQrCode.Code.CODE.getCode(), EnumDeviceQrCode.URL.getCode(), String.class);
        //查询商户特有二维码地址
        DeviceCreateIds deviceCreateIds = getById(qrcode.getDeviceId());
        if (Emptys.check(deviceCreateIds.getDeviceQrMercId())) {
            MercDeviceQrConfigDto.Vo mercDeviceQrConfig = mercDeviceQrConfigService.getQrUrl(new MercDeviceQrConfigDto.GetQrUrl().setMercId(deviceCreateIds.getDeviceQrMercId())).getData();
            if (Emptys.check(mercDeviceQrConfig)) {
                qrUrl = mercDeviceQrConfig.getQrUrl() + "?deviceId=";
            }
        }
        //验证是否是双门柜
        int deviceType = SysDictUtils.getValue(EnumDeviceType.Code.CODE.getCode(), EnumDeviceType.N_2.getCode(), Integer.class);
        String deviceId = qrcode.getDeviceId().toString();
        JList<String> deviceIds = new JArrayList<String>()
                .set(qrUrl + deviceId);
        JList<String> deviceIdInfos = new JArrayList<String>()
                .set(deviceId);

        if (deviceCreateIds.getDeviceType() == deviceType) {
            deviceIds.clear();
            deviceIdInfos.clear();
            for (int i = 0; i < 2; i++) {
                deviceIds.add(String.format("%s%s&door=%d", qrUrl, deviceId, i));
                deviceIdInfos.add(String.format("%s-%d", deviceId, i));
            }
        }
        List<String> base64s = QRCodeUtils.create(deviceIds, 400, 400, "back1.png", deviceIdInfos).base64();
        return R.ok(base64s);
    }

    @PostMapping("exportQrCode")
    @ApiOperation("导出二维码")
    public void exportQrCode(@RequestBody @Validated DeviceCreateIdsDto.ExportQrCode exportQrCode) {
        String url = SysDictUtils.getValue(EnumDeviceQrCode.Code.CODE.getCode(), EnumDeviceQrCode.URL.getCode(), String.class);
        int deviceType = SysDictUtils.getValue(EnumDeviceType.Code.CODE.getCode(), EnumDeviceType.N_2.getCode(), Integer.class);
        LocalDateTime now = LocalDateTime.now();
        JList<String> deviceIds = new JArrayList<>();
        List<String> deviceIdInfos = new ArrayList<>();
        List<DeviceCreateIds> updateDeviceCreateIds = new ArrayList<>();
        JList<DeviceCreateIds> deviceCreateIdss = new JArrayList<>(listByIds(exportQrCode.getDeviceIds()));
        List<DeviceInfo> deviceInfos = deviceInfoService.listByIds(exportQrCode.getDeviceIds());
        JMap<Long, DeviceInfo> deviceInfoJMaps = new JArrayList<>(deviceInfos).toMap(DeviceInfo::getDeviceId).cover();
        //循环
        deviceCreateIdss.forEach(deviceCreateIds -> {
            String qrUrl = url;
            //查询商户特有二维码地址
            if (Emptys.check(deviceCreateIds.getDeviceQrMercId())) {
                MercDeviceQrConfigDto.Vo mercDeviceQrConfig = mercDeviceQrConfigService.getQrUrl(new MercDeviceQrConfigDto.GetQrUrl().setMercId(deviceCreateIds.getDeviceQrMercId())).getData();
                if (Emptys.check(mercDeviceQrConfig)) {
                    qrUrl = mercDeviceQrConfig.getQrUrl() + "?deviceId=";
                }
            }
            //验证是否是双门柜
            if (deviceCreateIds.getDeviceType() == deviceType) {
                for (int i = 0; i < 2; i++) {
                    deviceIds.add(String.format("%s%s&door=%d", qrUrl, deviceCreateIds.getDeviceId(), i));
                    deviceIdInfos.add(String.format("%s-%d", deviceCreateIds.getDeviceId(), i));
                }
            } else {
                deviceIds.add(qrUrl + deviceCreateIds.getDeviceId());
                deviceIdInfos.add(String.valueOf(deviceCreateIds.getDeviceId()));
            }
            updateDeviceCreateIds.add(deviceCreateIds.setLastExportQrCodeTime(now));
        });
        updateBatchById(updateDeviceCreateIds);
        QRCodeUtils.create(deviceIds, 400, 400, "back1.png", deviceIdInfos).download(deviceIdInfos);
    }

    @SneakyThrows
    @ApiOperation("下载自定义设备id模板")
    @PostMapping("downloadIdTemplet")
    public void downloadIdTemplet(HttpServletResponse response) {
        InputStream inputStream = IoUtils.inputStream("id_templet.xlsx").get();
        response.setHeader("Content-Disposition", "attachment; filename=" + "id_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();
    }

    @ApiOperation("批量添加自定义机器号")
    @PostMapping("uploadSaveCustom")
    public R uploadSaveCustom(@RequestParam("file") MultipartFile file) {
        ThreadPoolUtils.excPoll(DeviceThreadPoolConfig.DEVICE_COMMON_POLL, 1)
                .execute(() -> {
                    try {
                        EasyExcel.read(file.getInputStream(), UploadSaveCustom.class, new UploadSaveCustomListener(AuthorizeUtils.getLoginId(Long.class), this)).sheet().doRead();
                    } catch (IOException e) {
                        log.error("", e);
                    }
                });
        return R.ok();
    }

    private void weight(JMap<Long, DeviceCreateIds> deviceCreateIdss, int num) {
        List<DeviceCreateIds> list = list(new LambdaQueryWrapper<DeviceCreateIds>().in(DeviceCreateIds::getDeviceId, deviceCreateIdss.toList().key()));
        if (Emptys.check(list)) {
            list.forEach(deviceCreateIdsInfo -> {
                //添加
                DeviceCreateIds deviceCreateIdsOld = deviceCreateIdss.get(deviceCreateIdsInfo.getDeviceId());
                Long deviceId = deviceCreateIdsOld.getDeviceId() + num;
                deviceCreateIdss.put(deviceId, copy(DeviceCreateIds.class, deviceCreateIdsOld).setDeviceId(deviceId));
                //删除
                deviceCreateIdss.remove(deviceCreateIdsInfo.getDeviceId());
            });
            weight(deviceCreateIdss, num);
        }
    }

    @Slf4j
    @RequiredArgsConstructor
    public static class UploadSaveCustomListener implements ReadListener<UploadSaveCustom> {

        private final Long loginId;

        private final DeviceCreateIdsServiceImpl deviceCreateIdsService;

        private JMap<Long, UploadSaveCustom> deviceIds = new JHashMap<>();

        /**
         * 这个每一条数据解析都会来调用
         *
         * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
         * @param context
         */
        @Override
        public void invoke(UploadSaveCustom 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);
            deviceIds.put(data.getDeviceId(), data);
        }

        /**
         * 所有数据解析完成了 都会来调用
         *
         * @param context
         */
        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            List<DeviceCreateIds> list = deviceCreateIdsService.list(new LambdaQueryWrapper<DeviceCreateIds>().in(DeviceCreateIds::getDeviceId, deviceIds.toList().key()));
            if (Emptys.check(list)) {
                list.forEach(deviceCreateIds -> {
                    deviceIds.remove(deviceCreateIds.getDeviceId());
                    log.info("重复的设备号:{}", deviceCreateIds.getDeviceId());
                });
            }
            if (Emptys.check(deviceIds)) {
                LocalDateTime now = LocalDateTime.now();
                List<DeviceCreateIds> saves = new ArrayList<>(deviceIds.size());
                deviceIds.forEach((deviceId, uploadSaveCustom) -> {
                    DeviceCreateIds deviceCreateIdsInfo = new DeviceCreateIds()
                            .setDeviceId(deviceId)
                            .setDeviceType(uploadSaveCustom.getDeviceTypeValue())
                            .setCreatePc(uploadSaveCustom.getDevicePc())
                            .setCreateUserId(loginId)
                            .setIsCustom(true)
                            .setCreateTime(now);
                    saves.add(deviceCreateIdsInfo);
                });
                deviceCreateIdsService.saveBatch(saves);
                log.info("批量添加自定义机器号完成，共{}条", saves.size());
            }
        }
    }

    @Data
    public static class UploadSaveCustom {

        /**
         * 设备id
         */
        private Long deviceId;

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

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

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