package com.jjt.calc.service.impl;

import com.jjt.biz.domain.TwinDevice;
import com.jjt.biz.service.ITwinDeviceService;
import com.jjt.calc.domain.TwinCalcAlarms;
import com.jjt.calc.domain.TwinCalcDay;
import com.jjt.calc.domain.TwinCalcHour;
import com.jjt.calc.domain.TwinCalcStop;
import com.jjt.calc.mapper.TwinCalcDayMapper;
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.utils.AsyncService;
import com.jjt.utils.Tools;
import javafx.util.Pair;
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.math.BigDecimal;
import java.math.RoundingMode;
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;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

/**
 * 日统计数据Service业务层处理
 *
 * @author wukai
 * @date 2025-01-18
 */
@Service
public class TwinCalcDayServiceImpl implements ITwinCalcDayService {
    @Resource
    private TwinCalcDayMapper twinCalcDayMapper;
    @Resource
    private ITwinCalcHourService hourService;
    @Resource
    private ITwinCalcStopService stopService;
    @Resource
    private ITwinCalcAlarmsService alarmsService;
    @Resource
    private ITwinDeviceService deviceService;
    @Resource
    private AsyncService asyncService;
    @Resource
    private SqlSessionFactory factory;

    /**
     * 查询日统计数据
     *
     * @param id 日统计数据主键
     * @return 日统计数据
     */
    @Override
    public TwinCalcDay selectTwinCalcDayById(Long id) {
        return twinCalcDayMapper.selectTwinCalcDayById(id);
    }

    /**
     * 查询日统计数据列表
     *
     * @param twinCalcDay 日统计数据
     * @return 日统计数据
     */
    @Override
    public List<TwinCalcDay> selectTwinCalcDayList(TwinCalcDay twinCalcDay) {
        return twinCalcDayMapper.selectTwinCalcDayList(twinCalcDay);
    }

    /**
     * 新增日统计数据
     *
     * @param twinCalcDay 日统计数据
     * @return 结果
     */
    @Override
    public int insertTwinCalcDay(TwinCalcDay twinCalcDay) {
        return twinCalcDayMapper.insertTwinCalcDay(twinCalcDay);
    }

    /**
     * 修改日统计数据
     *
     * @param twinCalcDay 日统计数据
     * @return 结果
     */
    @Override
    public int updateTwinCalcDay(TwinCalcDay twinCalcDay) {
        return twinCalcDayMapper.updateTwinCalcDay(twinCalcDay);
    }

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

    /**
     * 删除日统计数据信息
     *
     * @param id 日统计数据主键
     * @return 结果
     */
    @Override
    public int deleteTwinCalcDayById(Long id) {
        return twinCalcDayMapper.deleteTwinCalcDayById(id);
    }

    /**
     * 近日统计数据
     *
     * @param localDate 日期
     */
    @Override
    public void day(LocalDate localDate) {
        Date date = DateUtils.toDate(localDate);
        //先删除当前日期数据
        twinCalcDayMapper.delete4date(date);
        //计算统计时间
        Pair<Date, Date> pair = Tools.calcDay(localDate);
        Date sTime = pair.getKey();
        Date eTime = pair.getValue();
        List<TwinCalcHour> hourList = hourService.selectTwinCalcHourListByDate(sTime, eTime);
        List<TwinCalcStop> stopList = stopService.selectTwinCalcStopListByDate(sTime, eTime);
        List<TwinCalcAlarms> alarmsList = alarmsService.selectTwinCalcAlarmsListByDate(sTime, eTime);
        Pair<Date, Date> teamPairA = Tools.teamA(localDate);
        Pair<Date, Date> teamPairB = Tools.teamB(localDate);
        // 提取A班和B班的开始和结束时间，避免在循环中重复调用
        Date startTimeA = teamPairA.getKey();
        Date endTimeA = teamPairA.getValue();
        Date startTimeB = teamPairB.getKey();
        Date endTimeB = teamPairB.getValue();
        Map<Long, List<TwinCalcHour>> hourDeviceGroup = hourList.stream().collect(Collectors.groupingBy(TwinCalcHour::getDeviceId, LinkedHashMap::new, Collectors.toList()));

        List<TwinCalcDay> dayList = new ArrayList<>();
        for (Map.Entry<Long, List<TwinCalcHour>> entry : hourDeviceGroup.entrySet()) {
            List<TwinCalcHour> hours = entry.getValue();
            TwinCalcDay day = new TwinCalcDay(date);
            day.setDeviceId(entry.getKey());
            //计算产量数据
            day.calcHours(hours);
            //计算停机数据
            List<TwinCalcStop> stops = stopList.stream().filter(s -> s.getDeviceId().equals(entry.getKey())).collect(Collectors.toList());
            //A班停机计数
            BiConsumer<Integer, Integer> updateDayA = (type, count) -> {
                switch (type) {
                    case 1:
                        day.setStop1A(day.getStop1A() + count);
                        break;
                    case 2:
                        day.setStop2A(day.getStop2A() + count);
                        break;
                    case 3:
                        day.setStop3A(day.getStop3A() + count);
                        break;
                    default:
                }
            };

            BiConsumer<Integer, Integer> updateDayB = (type, count) -> {
                switch (type) {
                    case 1:
                        day.setStop1B(day.getStop1B() + count);
                        break;
                    case 2:
                        day.setStop2B(day.getStop2B() + count);
                        break;
                    case 3:
                        day.setStop3B(day.getStop3B() + count);
                        break;
                    default:
                }
            };

            // 使用Stream API，结合if-else逻辑的优化
            stops.forEach(stop -> {
                // 根据停机时间判断属于哪个班次
                boolean isAshift = !stop.getStartTime().after(endTimeA) && !stop.getEndTime().before(startTimeA);
                Date shiftStart = isAshift ? startTimeA : startTimeB;
                Date shiftEnd = isAshift ? endTimeA : endTimeB;
                BiConsumer<Integer, Integer> eventCounter = isAshift ? updateDayA : updateDayB;

                // 调整停机时间
                if (stop.getEndTime().compareTo(shiftEnd) > 0) {
                    stop.setEndTime(shiftEnd);
                }
                if (stop.getStartTime().compareTo(shiftStart) < 0) {
                    stop.setStartTime(shiftStart);
                }

                // 计算并更新停机事件计数
                eventCounter.accept(stop.getStopType(), 1);
            });

            //计算稼动率
            //AB班排班时间,要乘以设备总数
            BigDecimal teamTimeA = day.getOpenTimeA().add(day.getCloseTimeA());
            BigDecimal teamTimeB = day.getOpenTimeB().add(day.getCloseTimeB());
            BigDecimal totalTeamTime = teamTimeA.add(teamTimeB);

            BigDecimal totalOpenTime = day.getOpenTimeA().add(day.getOpenTimeB());
            BigDecimal ea = day.getOpenTimeA().divide(teamTimeA, 4, RoundingMode.HALF_UP);
            BigDecimal eb = day.getOpenTimeB().divide(teamTimeB, 4, RoundingMode.HALF_UP);
            BigDecimal ee = totalOpenTime.divide(totalTeamTime, 4, RoundingMode.HALF_UP);
            day.setOpenTimeA(day.getOpenTimeA());
            day.setOpenTimeB(day.getOpenTimeB());
            day.setCloseTimeA(day.getCloseTimeA());
            day.setCloseTimeB(day.getCloseTimeB());
            day.setEfficiencyA(ea);
            day.setEfficiencyB(eb);
            day.setEfficiency(ee);
            //如果米长为0，稼动率处理
            //20250317 潘工提的需求，把没有开机的设备也需要计算稼动
            if (day.getLengthA().doubleValue() == 0d) {
                day.setEfficiencyA(BigDecimal.ZERO);
            }
            if (day.getLengthB().doubleValue() == 0d) {
                day.setEfficiencyB(BigDecimal.ZERO);
            }
            if (day.getLength().doubleValue() == 0d) {
                day.setEfficiency(BigDecimal.ZERO);
            }
            List<TwinCalcAlarms> alarms = alarmsList.stream().filter(a -> a.getDeviceId().equals(entry.getKey())).collect(Collectors.toList());
            day.setAlarm((long) alarms.size());
            dayList.add(day);
        }
        try (SqlSession sqlSession = factory.openSession(ExecutorType.BATCH, false)) {
            TwinCalcDayMapper mapper = sqlSession.getMapper(TwinCalcDayMapper.class);
            dayList.forEach(mapper::insertTwinCalcDay);
            sqlSession.commit();
        }
    }


    /**
     * 统计今天的产量数据
     *
     * @return 当日数据 7:00至第二天7点（不含）
     */
    @Override
    public TwinCalcDay today() {
        //计算统计时间
        Pair<Date, Date> pair = Tools.calcToday();
        Date sTime = pair.getKey();
        Date eTime = pair.getValue();
        List<TwinCalcHour> hourList = hourService.selectTwinCalcHourListByDate(sTime, eTime);

        // 按照deviceId、density、mick、team，并统计每组的length总和
        //按照deviceId分组统计
        Map<Long, BigDecimal> resultMap = hourList.stream().collect(Collectors.groupingBy(TwinCalcHour::getDeviceId, Collectors.reducing(BigDecimal.ZERO, TwinCalcHour::getLength, BigDecimal::add)));
        TwinCalcDay calcDay = new TwinCalcDay(sTime);
        if (hourList.size() > 0) {
            calcDay.calcHours(hourList);
        }
        List<TwinCalcHour> hours = new ArrayList<>();
        TwinCalcHour lastHour = hourService.lastHour();
        LocalDate localDate = DateUtils.toLocalDate(lastHour.getDataDate());
        LocalDateTime start = LocalDateTime.of(localDate, LocalTime.MIN).plusHours(lastHour.getHour() + 1);

        //为了避免多线程同时获取token导致重复执行，先执行一次获取token
        TwinDevice search = new TwinDevice();
        //查询所有在线的设备
        search.setOnline("1");
        List<TwinDevice> list = deviceService.selectTwinDeviceList(search);
        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, LocalDateTime.now()));
        }
        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");
                    hours.addAll(calcHours);
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
        if (hours.size() > 0) {
            calcDay.calcHours(hours);
            int num = 0;
            for (TwinCalcHour hour : hours) {
                BigDecimal v = BigDecimal.ZERO;
                if (resultMap.get(hour.getDeviceId()) != null) {
                    v = resultMap.get(hour.getDeviceId());
                }
                v.add(hour.getLength());
                if (v.doubleValue() > 0d) {
                    num++;
                }
            }
            int deviceNum = list.size();
            BigDecimal ratio = BigDecimal.valueOf(num).divide(BigDecimal.valueOf(deviceNum), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100));
            calcDay.setRunningRatio(ratio.doubleValue());
        }
        BigDecimal totalTimeA = calcDay.getOpenTimeA().add(calcDay.getCloseTimeA());
        BigDecimal totalTimeB = calcDay.getOpenTimeB().add(calcDay.getCloseTimeB());
        if (!totalTimeA.equals(BigDecimal.ZERO)) {
            BigDecimal eff = calcDay.getOpenTimeA().divide(totalTimeA, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100));
            calcDay.setEfficiencyA(eff);
        }
        if (!totalTimeB.equals(BigDecimal.ZERO)) {
            BigDecimal eff = calcDay.getOpenTimeB().divide(totalTimeB, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100));
            calcDay.setEfficiencyB(eff);
        }
        return calcDay;
    }

    /**
     * 按月度查询
     *
     * @param month 月份
     * @return 结果
     */
    @Override
    public List<TwinCalcDay> selectTwinCalcDayListByMonth(String month) {
        return twinCalcDayMapper.selectTwinCalcDayListByMonth(month);
    }

    /**
     * 查询指定日期及之后的数据
     *
     * @param sd 开始日期
     * @return 列表
     */
    @Override
    public List<TwinCalcDay> selectTwinCalcDayListByTime(Date sd) {
        return twinCalcDayMapper.selectTwinCalcDayListByTime(sd, null);
    }

    /**
     * 查询指定日期的数据
     *
     * @param sd 开始日期
     * @param ed 结束日期
     * @return 列表
     */
    @Override
    public List<TwinCalcDay> selectTwinCalcDayListByTime(Date sd, Date ed) {
        return twinCalcDayMapper.selectTwinCalcDayListByTime(sd, ed);
    }
}
