package com.jjt.calc.service.impl;

import com.jjt.biz.domain.TwinDevice;
import com.jjt.biz.service.ITwinDeviceService;
import com.jjt.calc.domain.*;
import com.jjt.calc.mapper.*;
import com.jjt.calc.service.ITwinCalcAlarmsService;
import com.jjt.calc.service.ITwinCalcDayService;
import com.jjt.calc.service.ITwinCalcHourService;
import com.jjt.calc.service.ITwinCalcStopService;
import com.jjt.common.utils.DateUtils;
import com.jjt.emp.service.ITwinEmpCalcService;
import com.jjt.utils.AsyncService;
import com.jjt.utils.Tools;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * 1小时统计数据Service业务层处理
 *
 * @author wukai
 * @date 2025-01-18
 */
@Service
@Slf4j
public class TwinCalcHourServiceImpl implements ITwinCalcHourService {
    @Resource
    private TwinCalcHourMapper twinCalcHourMapper;
    @Resource
    private ITwinDeviceService deviceService;
    @Resource
    private AsyncService asyncService;
    @Resource
    private SqlSessionFactory factory;
    @Resource
    private ITwinCalcStopService stopService;
    @Resource
    private ITwinCalcAlarmsService alarmsService;
    @Resource
    private ITwinCalcDayService dayService;
    @Resource
    private ITwinEmpCalcService empCalcService;

    /**
     * 查询1小时统计数据
     *
     * @param id 1小时统计数据主键
     * @return 1小时统计数据
     */
    @Override
    public TwinCalcHour selectTwinCalcHourById(Long id) {
        return twinCalcHourMapper.selectTwinCalcHourById(id);
    }

    /**
     * 查询1小时统计数据列表
     *
     * @param twinCalcHour 1小时统计数据
     * @return 1小时统计数据
     */
    @Override
    public List<TwinCalcHour> selectTwinCalcHourList(TwinCalcHour twinCalcHour) {
        return twinCalcHourMapper.selectTwinCalcHourList(twinCalcHour);
    }

    /**
     * 新增1小时统计数据
     *
     * @param twinCalcHour 1小时统计数据
     * @return 结果
     */
    @Override
    public int insertTwinCalcHour(TwinCalcHour twinCalcHour) {
        return twinCalcHourMapper.insertTwinCalcHour(twinCalcHour);
    }

    /**
     * 修改1小时统计数据
     *
     * @param twinCalcHour 1小时统计数据
     * @return 结果
     */
    @Override
    public int updateTwinCalcHour(TwinCalcHour twinCalcHour) {
        return twinCalcHourMapper.updateTwinCalcHour(twinCalcHour);
    }

    /**
     * 批量删除1小时统计数据
     *
     * @param ids 需要删除的1小时统计数据主键
     * @return 结果
     */
    @Override
    public int deleteTwinCalcHourByIds(Long[] ids) {
        return twinCalcHourMapper.deleteTwinCalcHourByIds(ids);
    }

    /**
     * 删除1小时统计数据信息
     *
     * @param id 1小时统计数据主键
     * @return 结果
     */
    @Override
    public int deleteTwinCalcHourById(Long id) {
        return twinCalcHourMapper.deleteTwinCalcHourById(id);
    }

    /**
     * 从数据库最后一个时间段统计至当前的上一个偶数时间点
     */
    @Override
    public void calc2Curr() {
        TwinCalcHour lastHour = lastHour();
        LocalDate localDate = DateUtils.toLocalDate(lastHour.getDataDate());
        LocalDateTime start = LocalDateTime.of(localDate, LocalTime.MIN).plusHours(lastHour.getHour() + 1);
        LocalDateTime end = start.plusHours(1);

        LocalDateTime stop = Tools.currWholeTime();
        while (!end.isAfter(stop)) {
            start = start.minusSeconds(1);
            calc4device(start, end);
            log.info("补录数据===========start:{},end:{},stop:{}", start, end, stop);
            if (end.getHour() == 7) {
                //跨天,统计前一天的总数据
                log.info("----------------{},{}", start, end);
                dayService.day(start.toLocalDate().minusDays(1));
                empCalcService.calc(DateUtils.toDate(localDate));
            }
            start = end;
            end = end.plusHours(1);
        }
    }

    /**
     * 统计指定日期指定时段数据
     *
     * @param date   指定日期 yyyy-mm-dd
     * @param period 时段
     */
    @Override
    public void calc(String date, int period) {
        LocalDate localDate = LocalDate.parse(date);
        LocalDateTime ldt = LocalDateTime.of(localDate, LocalTime.MIN);
        LocalDateTime start = ldt.plusHours(period);
        LocalDateTime end = start.plusHours(1);
        start = start.minusSeconds(1);
        calc4device(start, end);
    }

    /**
     * 统计上一时段数据
     */
    @Override
    public void last() {
        LocalDateTime ldt = Tools.currWholeTime();
        //上一个小时
        ldt = ldt.minusHours(1);
        //开始时间需要向前取一秒
        LocalDateTime start = ldt.minusSeconds(1);
        LocalDateTime end = ldt.plusHours(1);
        calc4device(start, end);
    }

    /**
     * 按设备循环计算
     *
     * @param start 开始时间戳
     * @param end   结束时间戳
     */
    private void calc4device(LocalDateTime start, LocalDateTime end) {
        Date s = new Date();
        TwinDevice search = new TwinDevice();
        //查询所有在线的设备
        search.setOnline("1");
        //TODO 临时设置单个设备，上线需取消
//        search.setDeviceId(175L);
        List<TwinDevice> list = deviceService.selectTwinDeviceList(search);
        List<TwinDevice> errList = exec(list, start, end);
        //重试2次
        int repTimes = 1;
        while (errList.size() != 0) {
            errList = exec(errList, start, end);
            if (repTimes++ > 1) {
                break;
            }
        }
        Date d = new Date();
        log.info("总共消耗：{}ms", d.getTime() - s.getTime());

    }

    /**
     * 执行数据获取操作
     *
     * @param list  设备列表
     * @param start 开始时间
     * @param end   结束时间
     * @return 返回未获取成功的数据列表
     */

    private List<TwinDevice> exec(List<TwinDevice> list, LocalDateTime start, LocalDateTime end) {
        List<Future<Map<String, List<?>>>> futureList = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            TwinDevice twinDevice = list.get(i);
            futureList.add(asyncService.process(twinDevice, start, end));
        }

        List<TwinDevice> errList = new ArrayList<>();

        List<TwinCalcHour> calcList = new ArrayList<>();
        List<TwinRecordStop> stopList = new ArrayList<>();
        List<TwinRecordAlarms> alarmList = new ArrayList<>();
        List<TwinCalcHourSpec> specList = new ArrayList<>();
        try {
            for (Future<Map<String, List<?>>> future : futureList) {
                // 任务完成后获取结果
                try {
                    Map<String, List<?>> map = future.get(10L, TimeUnit.SECONDS);
                    List<TwinCalcHour> calcHours = (List<TwinCalcHour>) map.get("calc");
                    calcList.addAll(calcHours);
                    List<TwinRecordStop> stops = (List<TwinRecordStop>) map.get("stopRecord");
                    stopList.addAll(stops);
                    List<TwinRecordAlarms> alarms = (List<TwinRecordAlarms>) map.get("alarmRecord");
                    alarmList.addAll(alarms);
                    List<TwinCalcHourSpec> specs = (List<TwinCalcHourSpec>) map.get("specList");
                    specList.addAll(specs);
                } catch (TimeoutException e) {
                    errList.add(list.get(futureList.indexOf(future)));
                }
            }
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }

        try (SqlSession sqlSession = factory.openSession(ExecutorType.BATCH, false)) {
            if (calcList.size() > 0) {
                TwinCalcHourMapper mapper = sqlSession.getMapper(TwinCalcHourMapper.class);
                calcList.forEach(mapper::insertTwinCalcHour);
                sqlSession.commit();
            }

            if (stopList.size() > 0) {
                TwinRecordStopMapper mapper = sqlSession.getMapper(TwinRecordStopMapper.class);
                stopList.forEach(mapper::insertTwinRecordStop);
                sqlSession.commit();
            }

            if (alarmList.size() > 0) {
                TwinRecordAlarmsMapper mapper = sqlSession.getMapper(TwinRecordAlarmsMapper.class);
                alarmList.forEach(mapper::insertTwinRecordAlarms);
                sqlSession.commit();
            }

            if (specList.size() > 0) {
                TwinCalcHourSpecMapper mapper = sqlSession.getMapper(TwinCalcHourSpecMapper.class);
                specList.forEach(mapper::insertTwinCalcHourSpec);
                sqlSession.commit();
            }
        }
        //开始时间都减了一秒的，这里要加回来,用于计算时段等数据
        LocalDateTime ldt = start.plusSeconds(1);
        stopService.process(ldt, end, list);
        alarmsService.process(ldt, list);
        return errList;
    }


    /**
     * 按开始和结束时间查询数据
     *
     * @param sTime 开始时间
     * @param eTime 结束时间
     */
    @Override
    public List<TwinCalcHour> selectTwinCalcHourListByDate(Date sTime, Date eTime) {
        TwinCalcHour hour = new TwinCalcHour();
        Map<String, Object> params = new HashMap<>(16);
        params.put("sTime", sTime);
        params.put("eTime", eTime);
        hour.setParams(params);
        return selectTwinCalcHourList(hour);
    }

    /**
     * 获取数据库最后一条记录
     *
     * @return 对象
     */
    @Override
    public TwinCalcHour lastHour() {
        return twinCalcHourMapper.lastHour();
    }
}
