package com.xy.service;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
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.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yitter.idgen.YitIdHelper;
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.config.FileConfig;
import com.xy.device.EnumDeviceChargingHistoryStatus;
import com.xy.device.EnumSimConfig;
import com.xy.dto.DeviceSimDto;
import com.xy.dto.MercFeeConfigAlgorithmTypeDto;
import com.xy.dto.MercFeeConfigSimCardDto;
import com.xy.entity.DeviceSim;
import com.xy.entity.DeviceSimCharge;
import com.xy.entity.SysDictRedis;
import com.xy.error.CommRuntimeException;
import com.xy.mapper.DeviceSimMapper;
import com.xy.service.be.MercService;
import com.xy.util.ExcelUtils;
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.apache.commons.lang.time.DateUtils;
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.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;

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


/**
 * <p>
 * 设备流量卡 服务实现类
 * </p>
 *
 * @author lijin
 * @since 2023-10-16
 */
@Service
@AllArgsConstructor(onConstructor_ = @Lazy)
@Api(tags = "设备流量卡")
public class DeviceSimServiceImpl extends ServiceImpl<DeviceSimMapper, DeviceSim> implements DeviceSimService {

    private DeviceSimChargeServiceImpl deviceSimChargeService;

    private HttpServletResponse response;

    private FileConfig fileConfig;

    private MercFeeConfigSimCardServiceImpl mercFeeConfigSimCardService;

    private MercService mercService;


    @PostMapping("countWaitHandle")
    @ApiOperation("抄送流量卡-运营首页概况")
    public R<Long> countWaitHandle() {
        long count = count(Wrappers.<DeviceSim>lambdaQuery().eq(DeviceSim::getWaitHandle, true));
        return R.ok(count);
    }

    @Override
    @ApiOperation("购买")
    public R pay(DeviceSimDto.Pay pay) {
        LocalDateTime now = LocalDateTime.now();
        List<DeviceSimDto.Pay.DeviceSimCharge> deviceSimCharges = pay.getDeviceSimCharges();
        List<DeviceSimCharge> deviceSimChargess = new ArrayList<>(deviceSimCharges.size());
        deviceSimCharges.forEach(deviceSimCharge -> {
            DeviceSimCharge deviceSimChargeInfo = new DeviceSimCharge()
                    .setId(YitIdHelper.nextId())
                    .setOrderId(pay.getOrderId())
                    .setSimId(deviceSimCharge.getSimId())
                    .setMercId(pay.getMercId())
                    .setMoney(deviceSimCharge.getMoney())
                    .setSize(deviceSimCharge.getSize())
                    .setPayType(pay.getPayType())
                    .setNote(pay.getNote())
                    .setCreateTime(now)
                    .setUpdateTime(now);
            deviceSimChargess.add(deviceSimChargeInfo);
        });
        deviceSimChargeService.saveBatch(deviceSimChargess);
        return R.ok();
    }

    @Override
    @ApiOperation("购买回调")
    public R payNotice(DeviceSimDto.PayNotice payNotice) {
        // 查询设备流量卡充值表
        List<DeviceSimCharge> list = deviceSimChargeService.list(new LambdaQueryWrapper<DeviceSimCharge>().eq(DeviceSimCharge::getOrderId, payNotice.getOrderId()));
        if (!Emptys.check(list)) {
            return R.ok();
        }
        // 查询设备流量卡
        JList<DeviceSimCharge> deviceSimCharges = new JArrayList<>(list);
        JMap<String, DeviceSimCharge> deviceSimChargesJMaps = deviceSimCharges.toMap(DeviceSimCharge::getSimId).cover();
        List<DeviceSim> deviceSims = listByIds(deviceSimCharges.getProperty(DeviceSimCharge::getSimId));
        // 循环处理
        LocalDateTime now = LocalDateTime.now();
        deviceSims.forEach(deviceSim -> {
            DeviceSimCharge deviceSimCharge = deviceSimChargesJMaps.get(deviceSim.getId());
            String newTimeout = deviceSimCharge.getPayType() == 100 ? DataTime.getStringAround(0, 0, deviceSimCharge.getSize(), 0, 0, 0)
                    : DataTime.getStringAround(deviceSimCharge.getSize(), 0, 0, 0, 0, 0);
            long d = DataTime.diff(now, deviceSim.getTimeout(), "d");
            if (d > 0) {
                newTimeout = DataTime.getStringAround(0, 0, (int) d, 0, 0, 0, newTimeout);
            }
            deviceSim.setTimeout(DataTime.toLocal(newTimeout))
                    .setWaitHandle(true)
                    .setLastRenewalTime(now)
                    .setUpdateTime(now);
        });
        // 修改设备流量卡信息
        updateBatchById(deviceSims);
        // 修改设备流量卡充值
        Integer status = SysDictUtils.getValue(EnumDeviceChargingHistoryStatus.Code.CODE.getCode(), EnumDeviceChargingHistoryStatus.N_2.getCode(), Integer.class);
        deviceSimCharges.forEach(deviceSimCharge -> deviceSimCharge.setStatus(status).setUpdateTime(now));
        deviceSimChargeService.updateBatchById(deviceSimCharges);
        return R.ok();
    }

    @Override
    @ApiOperation("查询过期条数")
    public R<DeviceSimDto.DeviceSimTimeoutCountVo> deviceSimTimeoutCount(DeviceSimDto.DeviceSimTimeoutCount deviceSimTimeoutCount) {
        String now = DataTime.getSring();
        int day = SysDictUtils.getValue(EnumSimConfig.Code.CODE.getCode(), EnumSimConfig.N_200.getCode(), Integer.class);
        String end1 = DataTime.getStringAround(0, 0, day, 0, 0, 0, now);
        deviceSimTimeoutCount.setBegin1(now)
                .setEnd1(end1)
                .setEnd2(now);
        DeviceSimDto.DeviceSimTimeoutCountVo deviceSimTimeoutCountVo = baseMapper.deviceSimTimeoutCount(deviceSimTimeoutCount);
        return R.ok(deviceSimTimeoutCountVo);
    }

    @PostMapping("page")
    @ApiOperation("分页查询")
    public R<PageBean<DeviceSimDto.PageVo>> page(@RequestBody @Validated DeviceSimDto.Page page) {
        Map<String, SysDictRedis> simConfig = SysDictUtils.get(EnumSimConfig.Code.CODE.getCode());
        Integer value = Integer.valueOf(simConfig.get(EnumSimConfig.N_200.getCode()).getValue());
        String theTime = DataTime.getStringAround(0, 0, value, 0, 0, 0);
        page.setThisTime(LocalDateTime.now()).setTheTime(DataTime.toLocal(theTime));
        IPage<DeviceSimDto.PageVo> iPage = baseMapper.page(toIPage(page.getPage()), page);
        List<DeviceSimDto.PageVo> records = iPage.getRecords();
        if (Emptys.check(records)) {
//            String name = simConfig.get(EnumSimConfig.name.getCode()).getValue();
//            Integer money = Integer.valueOf(simConfig.get(EnumSimConfig.money.getCode()).getValue());
            List<MercFeeConfigSimCardDto.DeviceSimPayVO> deviceSimPayVOS = R.feignCheckData(mercFeeConfigSimCardService.paySimCardFee(new MercFeeConfigAlgorithmTypeDto.SimPayQueryDTO().setMercId(page.getMercId())));
            MercFeeConfigSimCardDto.DeviceSimPayVO deviceSimPayVO = deviceSimPayVOS.get(0);
            String name = deviceSimPayVO.getName();
            Integer money = deviceSimPayVO.getMoney();
            records.forEach(record -> {
                // 封装过期状态说明
                DeviceSimDto.Vo sim = record.getSim();
                if (Emptys.check(sim)) {
                    LocalDateTime timeout = sim.getTimeout();
                    if (timeout == null) {
                        record.getSim().setTimeoutStatus("未初始化");
                    } else {
                        String timeoutStatus;
                        long s = DataTime.diff(page.getThisTime(), timeout, "s");
                        if (s <= 0) {
                            timeoutStatus = "欠费(" + (~(s / 86400 - 1)) + "天)";
                        } else {
                            timeoutStatus = s <= value * 86400 ? "即将到期(" + s / 86400 + "天)" : "正常(" + s / 86400 + "天)";
                        }
                        record.getSim().setTimeoutStatus(timeoutStatus);
                    }
                }
                // 封装计费标准
                record.setChargingName(name).setChargingMoney(money);
            });
        }
        return R.ok(toPageBean(iPage));
    }


    @PostMapping("pageByNotInit")
    @ApiOperation("未初始化分页查询")
    public R<PageBean<DeviceSimDto.PageVo>> pageByNotInit(@RequestBody DeviceSimDto.Page page) {
//        Map<String, SysDictRedis> simConfig = SysDictUtils.get(EnumSimConfig.Code.CODE.getCode());
        IPage<DeviceSimDto.PageVo> iPage = baseMapper.page2(toIPage(page.getPage()), page);
        List<DeviceSimDto.PageVo> records = iPage.getRecords();
        if (Emptys.check(records)) {
//            String name = simConfig.get(EnumSimConfig.name.getCode()).getValue();
//            Integer money = Integer.valueOf(simConfig.get(EnumSimConfig.money.getCode()).getValue());
            List<MercFeeConfigSimCardDto.DeviceSimPayVO> deviceSimPayVOS = R.feignCheckData(mercFeeConfigSimCardService.paySimCardFee(new MercFeeConfigAlgorithmTypeDto.SimPayQueryDTO().setMercId(page.getMercId())));
            MercFeeConfigSimCardDto.DeviceSimPayVO deviceSimPayVO = deviceSimPayVOS.get(0);
            String name = deviceSimPayVO.getName();
            Integer money = deviceSimPayVO.getMoney();
            records.forEach(record -> {
                // 封装计费标准
                record.setChargingName(name).setChargingMoney(money);
            });
        }
        return R.ok(toPageBean(iPage));
    }

    @PostMapping("pageCount")
    @ApiOperation("分页数量查询")
    public R<DeviceSimDto.PageCountVo> pageCount(@RequestBody @Validated DeviceSimDto.PageCount pageCount) {
        Map<String, SysDictRedis> simConfig = SysDictUtils.get(EnumSimConfig.Code.CODE.getCode());
        Integer value = Integer.valueOf(simConfig.get(EnumSimConfig.N_200.getCode()).getValue());
        String theTime = DataTime.getStringAround(0, 0, value, 0, 0, 0);
        // 并行数据
        DeviceSimDto.PageCountVo pageCountVo = new DeviceSimDto.PageCountVo();
        ThreadPoolUtils.excPoll(DeviceThreadPoolConfig.DEVICE_COMMON_POLL, 6)
                .execute(() -> {
                    // 全部
                    DeviceSimDto.PageCount paramsObj = Beans.copy(DeviceSimDto.PageCount.class, pageCount);
                    int count = baseMapper.pageCount(paramsObj);
                    pageCountVo.setAllCount(count);
                })
                .execute(() -> {
                    // 即将过期
                    DeviceSimDto.PageCount paramsObj = Beans.copy(DeviceSimDto.PageCount.class, pageCount)
                            .setChargingStatus(1)
                            .setThisTime(LocalDateTime.now())
                            .setTheTime(DataTime.toLocal(theTime));
                    int count = baseMapper.pageCount(paramsObj);
                    pageCountVo.setBeTimeoutCount(count);
                })
                .execute(() -> {
                    // 已过期
                    DeviceSimDto.PageCount paramsObj = Beans.copy(DeviceSimDto.PageCount.class, pageCount)
                            .setChargingStatus(2)
                            .setThisTime(LocalDateTime.now())
                            .setTheTime(DataTime.toLocal(theTime));
                    int count = baseMapper.pageCount(paramsObj);
                    pageCountVo.setTimeoutCount(count);
                })
                .execute(() -> {
                    // 已激活
                    DeviceSimDto.PageCount paramsObj = Beans.copy(DeviceSimDto.PageCount.class, pageCount)
                            .setIsActivate(true);
                    int count = baseMapper.pageCount(paramsObj);
                    pageCountVo.setIsActivate(count);
                })
                .execute(() -> {
                    // 未激活
                    DeviceSimDto.PageCount paramsObj = Beans.copy(DeviceSimDto.PageCount.class, pageCount)
                            .setIsActivate(false);
                    int count = baseMapper.pageCount(paramsObj);
                    pageCountVo.setIsNotActivate(count);
                })
                .execute(() -> {
                    // 未初始化
                    DeviceSimDto.PageCount paramsObj = Beans.copy(DeviceSimDto.PageCount.class, pageCount);
                    int count = baseMapper.pageCount2(paramsObj);
                    pageCountVo.setNotInitCount(count);
                })
                .end();
        return R.ok(pageCountVo);
    }

    @SneakyThrows
    @ApiOperation("导出流量卡数据")
    @PostMapping("download")
    public void download(@RequestBody DeviceSimDto.Download download) {
        // 生成excel
        String name = YitIdHelper.nextId() + ".xlsx";
        String path = fileConfig.getPath() + File.separator + name;
        ExcelUtils.SheetAndData<UploadSim> sheetAndData = ExcelUtils.create(path, UploadSim.class);
        sheetAndData.sheet("流量卡数据", () -> {
            // 生成导出数据
            List<DeviceSimDto.PageVo> pageVos;
            if (download.getIsNotInit()) {
                pageVos = baseMapper.page2(download);
            } else {
                Map<String, SysDictRedis> simConfig = SysDictUtils.get(EnumSimConfig.Code.CODE.getCode());
                Integer value = Integer.valueOf(simConfig.get(EnumSimConfig.N_200.getCode()).getValue());
                String theTime = DataTime.getStringAround(0, 0, value, 0, 0, 0);
                download.setThisTime(LocalDateTime.now()).setTheTime(DataTime.toLocal(theTime));
                pageVos = baseMapper.page(download);
            }
            List<UploadSim> uploadSims = new ArrayList<>();
            pageVos.forEach(pageVo -> {
                UploadSim uploadSim = new UploadSim();
                uploadSim.setSimId(pageVo.getSimIccid());
                DeviceSimDto.Vo sim = pageVo.getSim();
                if (sim != null) {
                    uploadSim.setType(sim.getType());
                    uploadSim.setActivateTime(Emptys.check(sim.getActivateTime()) ? DataTime.toString(sim.getActivateTime(), "yyyy-MM-dd") : null);
                    uploadSim.setTimeout(Emptys.check(sim.getTimeout()) ? DataTime.toString(sim.getTimeout(), "yyyy-MM-dd") : null);
                    uploadSim.setLastRenewalTime(Emptys.check(sim.getLastRenewalTime()) ? DataTime.toString(sim.getLastRenewalTime(), "yyyy-MM-dd") : null);
                    uploadSim.setBatchNo(sim.getBatchNo());
                    uploadSim.setIsTest(Emptys.check(sim.getIsTest()) && sim.getIsTest() ? "是" : "否");
                }
                uploadSims.add(uploadSim);
            });
            // 修改批次号
            String updateBatchNo = download.getUpdateBatchNo();
            Boolean isUpdateBatchNo = download.getIsUpdateBatchNo();
            if (Emptys.check(updateBatchNo) && Emptys.check(isUpdateBatchNo) && isUpdateBatchNo) {
                LocalDateTime now = LocalDateTime.now();
                List<DeviceSim> deviceSims = new ArrayList<>(uploadSims.size());
                uploadSims.forEach(uploadSim -> {
                    DeviceSim deviceSim = new DeviceSim()
                            .setId(uploadSim.getSimId())
                            .setBatchNo(updateBatchNo)
                            .setWaitHandle(false)
                            .setUpdateTime(now);
                    deviceSims.add(deviceSim);
                });
                updateBatchById(deviceSims);
            }
            return uploadSims;
        }).builder();
        // 下载文件
        InputStream inputStream = IoUtils.inputStream(path).get();
        response.setHeader("Content-Disposition", "attachment; filename=" + "sim_data.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();
        // 删除文件
        new File(path).delete();
    }

    @SneakyThrows
    @ApiOperation("导入流量卡号数据")
    @PostMapping("uploadSim")
    public R uploadSim(@RequestParam("file") MultipartFile file) {
        EasyExcel.read(file.getInputStream(), UploadSim.class, new UploadSimListener(this)).sheet().doRead();
        return R.ok();
    }

    @PostMapping("update")
    @ApiOperation("修改流量卡信息")
    public void update(@RequestBody @Validated DeviceSimDto.Update update) {
        List<String> ids = update.getId();
        LocalDateTime now = LocalDateTime.now();
        List<DeviceSim> deviceSims = new ArrayList<>(ids.size());
        ids.forEach(id -> {
            DeviceSim deviceSim = new DeviceSim()
                    .setId(id)
                    .setType(update.getType())
                    .setActivateTime(update.getActivateTime())
                    .setTimeout(update.getTimeout())
                    .setLastRenewalTime(update.getLastRenewalTime())
                    .setBatchNo(update.getBatchNo())
                    .setUpdateTime(now)
                    .setIsTest(update.getIsTest());
            deviceSims.add(deviceSim);
        });
        updateBatchById(deviceSims);
    }

    @Slf4j
    @RequiredArgsConstructor
    public static class UploadSimListener implements ReadListener<UploadSim> {

        private final DeviceSimServiceImpl deviceSimService;

        private JList<UploadSim> sims = new JArrayList<>();

        /**
         * 这个每一条数据解析都会来调用
         *
         * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
         * @param context
         */
        @Override
        public void invoke(UploadSim data, AnalysisContext context) {
            sims.add(data);
        }

        /**
         * 所有数据解析完成了 都会来调用
         *
         * @param context
         */
        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            StringJoiner errorJoiner = new StringJoiner(",");
            LocalDateTime now = LocalDateTime.now();
            JList<DeviceSim> saveDeviceSims = new JArrayList<>();
            sims.forEach(uploadSim -> {
                try {
                    DeviceSim deviceSim = new DeviceSim()
                            .setId(uploadSim.getSimId().trim().replace("\"", "").replace("'", ""))
                            .setType(uploadSim.getType())
                            .setActivateTime(toLocalDateTime(uploadSim.getActivateTime()))
                            .setTimeout(toLocalDateTime(uploadSim.getTimeout()))
                            .setLastRenewalTime(toLocalDateTime(uploadSim.getLastRenewalTime()))
                            .setCreateTime(now)
                            .setUpdateTime(now)
                            .setIsTest(Emptys.check(uploadSim.getIsTest()) && uploadSim.getIsTest().equals("是"));
                    saveDeviceSims.add(deviceSim);
                } catch (Exception e) {
                    String format = String.format("%s导入异常:%s", uploadSim.getSimId(), e.getMessage());
                    errorJoiner.add(format);
                    log.error("", e);
                }
            });
            if (Emptys.check(saveDeviceSims)) {
                deviceSimService.saveOrUpdateBatch(saveDeviceSims);
            }
            if (errorJoiner.length() > 0) {
                throw new CommRuntimeException(errorJoiner.toString());
            }
        }

        private LocalDateTime toLocalDateTime(String date) {
            if (!Emptys.check(date)) {
                return null;
            }
            if (date.matches("\\d+")) {
                Calendar calendar = new GregorianCalendar(1900, 0, -1);
                Date dd = DateUtils.addDays(calendar.getTime(), Integer.parseInt(date));
                return dd.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            } else {
                LocalDateTime localDateTime = null;
                List<String> patterns = Arrays.asList(
                        "yyyy-MM-dd", "yyyy-MM-dd HH", "yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm:ss",
                        "yyyy-MM-dd H", "yyyy-MM-dd H:m", "yyyy-MM-dd H:m:s"
                );
                for (String pattern : patterns) {
                    try {
                        if (pattern.equals("yyyy-MM-dd")) {
                            localDateTime = LocalDate.parse(date.trim(), DateTimeFormatter.ofPattern(pattern)).atTime(0, 0, 0);
                        } else {
                            localDateTime = LocalDateTime.parse(date.trim(), DateTimeFormatter.ofPattern(pattern));
                        }
                        break;
                    } catch (Exception ignored) {

                    }
                }
                return localDateTime;
            }
        }
    }

    @Data
    public static class UploadSim {

        @ColumnWidth(25)
        @ExcelProperty(value = "流量卡号")
        private String simId;

        @ColumnWidth(25)
        @ExcelProperty(value = "类型")
        private String type;

        @ColumnWidth(25)
        @ExcelProperty(value = "批次号")
        private String batchNo;

        @ColumnWidth(50)
        @ExcelProperty(value = "激活时间")
        private String activateTime;

        @ColumnWidth(50)
        @ExcelProperty(value = "过期时间")
        private String timeout;

        @ColumnWidth(50)
        @ExcelProperty(value = "最后续费时间")
        private String lastRenewalTime;

        @ColumnWidth(25)
        @ExcelProperty(value = "是否测试卡")
        private String isTest;

    }
}
