package com.xy.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
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.github.yitter.idgen.YitIdHelper;
import com.xy.collections.list.JArrayList;
import com.xy.collections.list.JList;
import com.xy.config.DeviceThreadPoolConfig;
import com.xy.config.FileConfig;
import com.xy.device.EnumDeviceCharging;
import com.xy.dto.DeviceChargingHistoryDto;
import com.xy.dto.MercFeeConfigDto;
import com.xy.dto.OrderMercManageDto;
import com.xy.dto.be.MercDto;
import com.xy.entity.DeviceCharging;
import com.xy.entity.DeviceChargingHistory;
import com.xy.mapper.DeviceChargingHistoryMapper;
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.SneakyThrows;
import lombok.experimental.Accessors;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

/**
 * <p>
 * 设备计费历史表 服务实现类
 * </p>
 *
 * @author lijin
 * @since 2023-04-14
 */
@Service
@AllArgsConstructor(onConstructor_ = @Lazy)
@Api(tags = "设备计费历史表")
public class DeviceChargingHistoryServiceImpl extends ServiceImpl<DeviceChargingHistoryMapper, DeviceChargingHistory> implements DeviceChargingHistoryService {

    private OrderMercManageService orderMercManageService;

    private MercService mercService;

    private HttpServletResponse response;

    private FileConfig fileConfig;

    private MercFeeConfigService mercFeeConfigService;

    private DeviceChargingServiceImpl deviceChargingService;


    @Override
    @ApiOperation("分页查询")
    public R<PageBean<DeviceChargingHistoryDto.Vo>> page(DeviceChargingHistoryDto.Page page) {
        LambdaQueryWrapper<DeviceChargingHistory> lambdaQueryWrapper = new MybatisPlusQuery().eqWrapper(page, DeviceChargingHistory.class)
                .ge(DeviceChargingHistory::getCreateTime, page.getBeginCreateTime())
                .le(DeviceChargingHistory::getCreateTime, page.getEndCreateTime())
                .build()
                .orderByDesc(DeviceChargingHistory::getCreateTime);
        IPage<DeviceChargingHistory> iPage = page(PlusBeans.toIPage(page.getPage()), lambdaQueryWrapper);
        PageBean<DeviceChargingHistoryDto.Vo> pageBean = toPageBean(DeviceChargingHistoryDto.Vo.class, iPage);
        List<DeviceChargingHistoryDto.Vo> records = pageBean.getRecords();
        if (Emptys.check(records)) {
            List<OrderMercManageDto.Vo> orderMercManages = orderMercManageService.list(new OrderMercManageDto.SelectList().setId(new JArrayList<>(records).getProperty(DeviceChargingHistoryDto.Vo::getOrderId))).getData();
            List<MercDto.Vo> mercs = mercService.list(new MercDto.SelectList().setMercIds(new JArrayList<>(records).getProperty(DeviceChargingHistoryDto.Vo::getMercId).comparing())).getData();
            copy(records)
                    .target(() -> orderMercManages, DeviceChargingHistoryDto.Vo::getOrderId, DeviceChargingHistoryDto.Vo::getFiles, OrderMercManageDto.Vo::getId, OrderMercManageDto.Vo::getFiles)
                    .target(() -> orderMercManages, DeviceChargingHistoryDto.Vo::getOrderId, DeviceChargingHistoryDto.Vo::getBalanceMone, OrderMercManageDto.Vo::getId, OrderMercManageDto.Vo::getBalanceMoney)
                    .target(() -> mercs, DeviceChargingHistoryDto.Vo::getMercId, DeviceChargingHistoryDto.Vo::getMercName, MercDto.Vo::getId, MercDto.Vo::getName)
                    .builder();
        }
        return R.ok(pageBean);
    }


    public R<PageBean<DeviceChargingHistoryDto.PageVo>> pageByTopMerc(DeviceChargingHistoryDto.PageByTopMerc page) {
        Long curMercId = page.getCurMercId();
        Long chooseMercId = page.getChooseMercId();
        List<Long> mercIds = new ArrayList<>();
        if (chooseMercId == null) {
            mercIds = R.feignCheckData(mercService.getChildMercIds(new MercDto.QuerySubDTO().setParentMercID(curMercId)));
        } else {
            // 指定商户ID
            mercIds.add(chooseMercId);
        }
        if (CollUtil.isEmpty(mercIds)) {
            return R.ok();
        }

        LambdaQueryWrapper<DeviceChargingHistory> lambdaQueryWrapper = new MybatisPlusQuery().eqWrapper(page, DeviceChargingHistory.class)
                .ge(DeviceChargingHistory::getCreateTime, page.getBeginCreateTime())
                .le(DeviceChargingHistory::getCreateTime, page.getEndCreateTime())
                .in(DeviceChargingHistory::getMercId, mercIds)
                .build()
                .orderByDesc(DeviceChargingHistory::getCreateTime);
        IPage<DeviceChargingHistory> iPage = page(PlusBeans.toIPage(page.getPage()), lambdaQueryWrapper);
        PageBean<DeviceChargingHistoryDto.PageVo> pageBean = toPageBean(DeviceChargingHistoryDto.PageVo.class, iPage);
        List<DeviceChargingHistoryDto.PageVo> records = pageBean.getRecords();
        if (Emptys.check(records)) {
            // 佣金配置查询
            List<MercFeeConfigDto.ListFeeConfigByMercVO> feeConfigByMercVOS = R.feignCheckData(mercFeeConfigService.listFeeConfigByMerc(new MercFeeConfigDto.ListFeeConfigByMercDTO().setMercId(curMercId)));
            Map<Long, MercFeeConfigDto.ListFeeConfigByMercVO> configByMercVOMap = MapUtil.newHashMap();
            if (CollUtil.isNotEmpty(feeConfigByMercVOS)) {
                configByMercVOMap = feeConfigByMercVOS.stream().collect(Collectors.toMap(MercFeeConfigDto.ListFeeConfigByMercVO::getMercId, f -> f));
            }

            copy(records)
                    .target(() -> orderMercManageService.list(new OrderMercManageDto.SelectList().setId(new JArrayList<>(records).getProperty(DeviceChargingHistoryDto.PageVo::getOrderId))).getData()
                            , DeviceChargingHistoryDto.PageVo::getOrderId, DeviceChargingHistoryDto.PageVo::getFiles, OrderMercManageDto.Vo::getId, OrderMercManageDto.Vo::getFiles
                    )
                    .target(() -> mercService.list(new MercDto.SelectList().setMercIds(new JArrayList<>(records).getProperty(DeviceChargingHistoryDto.PageVo::getMercId).comparing())).getData(),
                            DeviceChargingHistoryDto.PageVo::getMercId, DeviceChargingHistoryDto.PageVo::getMercName, MercDto.Vo::getId, MercDto.Vo::getName)
                    .builder();
            List<Long> deviceIds = records.stream().map(DeviceChargingHistoryDto.PageVo::getDeviceId).distinct().collect(Collectors.toList());

//            List<DeviceInfoDto.Vo> deviceList = R.feignCheckData(deviceInfoService.listCommon(new DeviceInfoDto.ListCommon().setDeviceIds(deviceIds)));
//            Map<Long, DeviceInfoDto.Vo> devcieMap = deviceList.stream().collect(Collectors.toMap(DeviceInfoDto.Vo::getDeviceId, d -> d));

            List<DeviceCharging> deviceChargings = deviceChargingService.listByIds(deviceIds);
            Map<Long, DeviceCharging> deviceChargingMap = MapUtil.newHashMap();
            if (CollUtil.isNotEmpty(deviceChargings)) {
                deviceChargingMap = deviceChargings.stream().collect(Collectors.toMap(DeviceCharging::getDeviceId, d -> d));
            }
            for (DeviceChargingHistoryDto.PageVo record : records) {
                Long mercId = record.getMercId();
                Long deviceId = record.getDeviceId();
                // 初始0 设备管理费佣金
                record.setAgentMoney(0);
                if (!configByMercVOMap.isEmpty() && configByMercVOMap.get(mercId) != null) {
                    MercFeeConfigDto.ListFeeConfigByMercVO listFeeConfigByMercVO = configByMercVOMap.get(mercId);
                    Integer extraMoney = listFeeConfigByMercVO.getFeeConfig().getFeeDevice();
                    // 设备管理费佣金
                    record.setAgentMoney(extraMoney);
                }
                // 封装过期状态说明
                String timeoutStatus;
                DeviceCharging dc = deviceChargingMap.get(deviceId);
                if (dc != null) {
                    Integer value = SysDictUtils.getValue(EnumDeviceCharging.Code.CODE.getCode(), EnumDeviceCharging.N_200.getCode(), Integer.class);

                    LocalDateTime timeout = dc.getTimeout();
                    long s = DataTime.diff(LocalDateTime.now(), timeout, "s");
                    if (s <= 0) {
                        timeoutStatus = "欠费(" + (~(s / 86400 - 1)) + "天)";
                    } else {
                        timeoutStatus = s <= value * 86400 ? "即将到期(" + s / 86400 + "天)" : "正常(" + s / 86400 + "天)";
                    }
                    // 过期状态说明
                    record.setTimeoutStatus(timeoutStatus);
                    // 过期时间
                    record.setTimeout(timeout);
                }
            }
            pageBean.setRecords(records);

        }
        return R.ok(pageBean);
    }

    public R<PageBean<DeviceChargingHistoryDto.MySpendPageVO>> mySpendPage(DeviceChargingHistoryDto.PageByTopMerc page) {
        Long curMercId = page.getCurMercId();
        List<Long> mercIds = new ArrayList<>();
        // 指定商户ID
        mercIds.add(curMercId);
        LambdaQueryWrapper<DeviceChargingHistory> lambdaQueryWrapper = new MybatisPlusQuery().eqWrapper(page, DeviceChargingHistory.class)
                .ge(DeviceChargingHistory::getCreateTime, page.getBeginCreateTime())
                .le(DeviceChargingHistory::getCreateTime, page.getEndCreateTime())
                .in(DeviceChargingHistory::getMercId, mercIds)
                .build()
                .orderByDesc(DeviceChargingHistory::getCreateTime);
        IPage<DeviceChargingHistory> iPage = page(PlusBeans.toIPage(page.getPage()), lambdaQueryWrapper);
        PageBean<DeviceChargingHistoryDto.MySpendPageVO> pageBean = toPageBean(DeviceChargingHistoryDto.MySpendPageVO.class, iPage);
        List<DeviceChargingHistoryDto.MySpendPageVO> records = pageBean.getRecords();
        if (Emptys.check(records)) {
            copy(records)
                    .target(() -> orderMercManageService.list(new OrderMercManageDto.SelectList().setId(new JArrayList<>(records).getProperty(DeviceChargingHistoryDto.MySpendPageVO::getOrderId))).getData()
                            , DeviceChargingHistoryDto.MySpendPageVO::getOrderId, DeviceChargingHistoryDto.MySpendPageVO::getFiles, OrderMercManageDto.Vo::getId, OrderMercManageDto.Vo::getFiles
                    )
                    .target(() -> mercService.list(new MercDto.SelectList().setMercIds(new JArrayList<>(records).getProperty(DeviceChargingHistoryDto.MySpendPageVO::getMercId).comparing())).getData(),
                            DeviceChargingHistoryDto.MySpendPageVO::getMercId, DeviceChargingHistoryDto.MySpendPageVO::getMercName, MercDto.Vo::getId, MercDto.Vo::getName)
                    .builder();
            List<Long> deviceIds = records.stream().map(DeviceChargingHistoryDto.MySpendPageVO::getDeviceId).distinct().collect(Collectors.toList());

            List<DeviceCharging> deviceChargings = deviceChargingService.listByIds(deviceIds);
            Map<Long, DeviceCharging> deviceChargingMap = MapUtil.newHashMap();
            if (CollUtil.isNotEmpty(deviceChargings)) {
                deviceChargingMap = deviceChargings.stream().collect(Collectors.toMap(DeviceCharging::getDeviceId, d -> d));
            }
            for (DeviceChargingHistoryDto.MySpendPageVO record : records) {
                Long deviceId = record.getDeviceId();
                // 封装过期状态说明
                String timeoutStatus;
                DeviceCharging dc = deviceChargingMap.get(deviceId);
                if (dc != null) {
                    Integer value = SysDictUtils.getValue(EnumDeviceCharging.Code.CODE.getCode(), EnumDeviceCharging.N_200.getCode(), Integer.class);

                    LocalDateTime timeout = dc.getTimeout();
                    long s = DataTime.diff(LocalDateTime.now(), timeout, "s");
                    if (s <= 0) {
                        timeoutStatus = "欠费(" + (~(s / 86400 - 1)) + "天)";
                    } else {
                        timeoutStatus = s <= value * 86400 ? "即将到期(" + s / 86400 + "天)" : "正常(" + s / 86400 + "天)";
                    }
                    // 过期状态说明
                    record.setTimeoutStatus(timeoutStatus);
                    // 过期时间
                    record.setTimeout(timeout);
                }
            }
            pageBean.setRecords(records);

        }
        return R.ok(pageBean);
    }


    @ApiOperation("月统计分页")
    @PostMapping("moonCountPage")
    public R<PageBean<DeviceChargingHistoryDto.MoonCountVo>> moonCountPage(@RequestBody DeviceChargingHistoryDto.MoonCountPage moonCountPage) {
        JList<DeviceChargingHistoryDto.MoonCountVo> moonCountVos = new JArrayList<>();
        // 查询数据
        List<String> attrNames = Arrays.asList(
                LambdaUtils.getUnderlineCaseName(DeviceChargingHistory::getMercId)
        );
        List<String> selectNames = Arrays.asList(
                attrNames.get(0),
                String.format("count(*) %s", LambdaUtils.getProperty(DeviceChargingHistory::getChargingSize)),
                String.format("sum(%s) %s", LambdaUtils.getUnderlineCaseName(DeviceChargingHistory::getChargingMoney), LambdaUtils.getProperty(DeviceChargingHistory::getChargingMoney))
        );
        QueryWrapper<DeviceChargingHistory> queryWrapper = new QueryWrapper<DeviceChargingHistory>()
                .select(selectNames.toArray(new String[selectNames.size()]))
                .in(Emptys.check(moonCountPage.getMercIds()), attrNames.get(0), moonCountPage.getMercIds())
                .between("DATE_FORMAT(" + LambdaUtils.getUnderlineCaseName(DeviceChargingHistory::getCreateTime) + ", '%Y-%m-%d')", moonCountPage.getBeginDate(), moonCountPage.getEndDate())
                .groupBy(attrNames);
        IPage<DeviceChargingHistory> iPage = page(toIPage(moonCountPage.getPage()), queryWrapper);
        List<DeviceChargingHistory> records = iPage.getRecords();
        if (!Emptys.check(records)) {
            return R.ok(new PageBean<>());
        }
        String date = String.format("%s~%s", moonCountPage.getBeginDate(), moonCountPage.getEndDate());
        records.forEach(deviceChargingHistory -> {
            DeviceChargingHistoryDto.MoonCountVo moonCountVo = new DeviceChargingHistoryDto.MoonCountVo()
                    .setDate(date)
                    .setMercId(deviceChargingHistory.getMercId())
                    .setDeviceSize(deviceChargingHistory.getChargingSize())
                    .setChargingMoney(deviceChargingHistory.getChargingMoney());
            moonCountVos.add(moonCountVo);
        });
        // 翻译商户名称
        Beans.copy(moonCountVos).target(() -> mercService.list(new MercDto.SelectList().setMercIds(moonCountVos.getProperty(DeviceChargingHistoryDto.MoonCountVo::getMercId).comparing())).getData(),
                        DeviceChargingHistoryDto.MoonCountVo::getMercId, DeviceChargingHistoryDto.MoonCountVo::getMercName, MercDto.Vo::getId, MercDto.Vo::getName)
                .builder();
        PageBean<DeviceChargingHistoryDto.MoonCountVo> pageBean = new PageBean<DeviceChargingHistoryDto.MoonCountVo>()
                .setCurrent(iPage.getCurrent())
                .setSize(iPage.getSize())
                .setTotal(iPage.getTotal())
                .setRecords(moonCountVos);
        return R.ok(pageBean);
    }

    @ApiOperation("月统计")
    @PostMapping("moonCount")
    public R<List<DeviceChargingHistoryDto.MoonCountVo>> moonCount(@RequestBody DeviceChargingHistoryDto.MoonCount moonCount) {
        JList<DeviceChargingHistoryDto.MoonCountVo> moonCountVos = new JArrayList<>();
        // 查询数据
        List<String> attrNames = Arrays.asList(
                LambdaUtils.getUnderlineCaseName(DeviceChargingHistory::getMercId),
                "DATE_FORMAT(" + LambdaUtils.getUnderlineCaseName(DeviceChargingHistory::getCreateTime) + ", '%Y-%m')"
        );
        List<String> selectNames = Arrays.asList(
                attrNames.get(0),
                attrNames.get(1) + " " + LambdaUtils.getProperty(DeviceChargingHistory::getNote),
                String.format("count(*) %s", LambdaUtils.getProperty(DeviceChargingHistory::getChargingSize)),
                String.format("sum(%s) %s", LambdaUtils.getUnderlineCaseName(DeviceChargingHistory::getChargingMoney), LambdaUtils.getProperty(DeviceChargingHistory::getChargingMoney))
        );
        List<String> dateDifference = getDateDifference(moonCount.getBeginDate(), moonCount.getEndDate());
        ThreadPoolUtils.Execute execute = ThreadPoolUtils.excPoll(DeviceThreadPoolConfig.DEVICE_COMMON_POLL, dateDifference.size());
        dateDifference.forEach(date -> execute.execute(() -> {
            QueryWrapper<DeviceChargingHistory> queryWrapper = new QueryWrapper<DeviceChargingHistory>()
                    .select(selectNames.toArray(new String[selectNames.size()]))
                    .in(Emptys.check(moonCount.getMercIds()), attrNames.get(0), moonCount.getMercIds())
                    .eq(attrNames.get(1), date)
                    .groupBy(attrNames);
            List<DeviceChargingHistory> deviceChargingHistories = list(queryWrapper);
            deviceChargingHistories.forEach(deviceChargingHistory -> {
                DeviceChargingHistoryDto.MoonCountVo moonCountVo = new DeviceChargingHistoryDto.MoonCountVo()
                        .setDate(deviceChargingHistory.getNote())
                        .setMercId(deviceChargingHistory.getMercId())
                        .setDeviceSize(deviceChargingHistory.getChargingSize())
                        .setChargingMoney(deviceChargingHistory.getChargingMoney());
                moonCountVos.add(moonCountVo);
            });
        }));
        execute.end();
        // 翻译商户名称
        Beans.copy(moonCountVos).target(() -> mercService.list(new MercDto.SelectList().setMercIds(moonCountVos.getProperty(DeviceChargingHistoryDto.MoonCountVo::getMercId).comparing())).getData(),
                        DeviceChargingHistoryDto.MoonCountVo::getMercId, DeviceChargingHistoryDto.MoonCountVo::getMercName, MercDto.Vo::getId, MercDto.Vo::getName)
                .builder();
        return R.ok(moonCountVos.desc(DeviceChargingHistoryDto.MoonCountVo::getDate));
    }

    @SneakyThrows
    @ApiOperation("月统计导出")
    @PostMapping("moonCountDownload")
    public void moonCountDownload(@RequestBody DeviceChargingHistoryDto.MoonCount moonCount) {
        // 生成excel
        String name = YitIdHelper.nextId() + ".xlsx";
        String path = fileConfig.getPath() + File.separator + name;
        ExcelUtils.SheetAndData<MoonCountData> sheetAndData = ExcelUtils.create(path, MoonCountData.class);
        sheetAndData.sheet("设备管理费月统计数据", () -> {
            List<DeviceChargingHistoryDto.MoonCountVo> data = moonCount(moonCount).getData();
            if (!Emptys.check(data)) {
                return null;
            }
            return Beans.copy(MoonCountData.class, data);
        }).builder();
        // 下载文件
        InputStream inputStream = IoUtils.inputStream(path).get();
        response.setHeader("Content-Disposition", "attachment; filename=" + "设备管理费月统计数据.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();
    }

    public List<String> getDateDifference(String date1, String date2) {
        List<String> dateDifference = new ArrayList<>();
        // 解析日期字符串
        YearMonth yearMonth1 = YearMonth.parse(date1);
        YearMonth yearMonth2 = YearMonth.parse(date2);
        // 计算日期差异
        long monthsDiff = ChronoUnit.MONTHS.between(yearMonth1, yearMonth2);
        // 将差异的每个月添加到集合中
        YearMonth temp = yearMonth1;
        for (int i = 0; i <= monthsDiff; i++) {
            dateDifference.add(temp.toString());
            temp = temp.plusMonths(1);
        }
        return dateDifference;
    }

    @Data
    @Accessors(chain = true)
    public static class MoonCountData {

        @ColumnWidth(25)
        @ExcelProperty(value = "统计时间 yyyy-MM")
        private String date;

        @ColumnWidth(25)
        @ExcelProperty(value = "商户id")
        private Long mercId;

        @ColumnWidth(25)
        @ExcelProperty(value = "商户名称")
        private String mercName;

        @ColumnWidth(25)
        @ExcelProperty(value = "设备数")
        private Integer deviceSize;

        @ColumnWidth(25)
        @ExcelProperty(value = "续费金额")
        private Integer chargingMoney;
    }
}
