/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.attence.record.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.util.StrUtil;
import org.springblade.attence.record.dto.QueryMarkDaysDTO;
import org.springblade.attence.record.entity.Record;
import org.springblade.attence.record.vo.MarkDayVo;
import org.springblade.attence.record.vo.MyStatisticalVo;
import org.springblade.attence.record.vo.RecordVO;
import org.springblade.attence.record.mapper.RecordMapper;
import org.springblade.attence.record.service.IRecordService;
import org.springblade.attence.ruleperson.entity.RulePerson;
import org.springblade.attence.ruleperson.service.IRulePersonService;
import org.springblade.attence.rulespectime.entity.RuleSpecTime;
import org.springblade.attence.rulespectime.service.IRuleSpecTimeService;
import org.springblade.attence.ruletime.entity.RuleTime;
import org.springblade.attence.ruletime.service.IRuleTimeService;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.mp.support.Condition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Collectors;

/**
 *  服务实现类
 *
 * @author BladeX
 * @since 2021-04-12
 */
@Service
public class RecordServiceImpl extends BaseServiceImpl<RecordMapper, Record> implements IRecordService {

	@Resource
	private  IRulePersonService rulePersonService;
	@Resource
	private IRuleSpecTimeService ruleSpecTimeService;
	@Resource
	private IRuleTimeService ruleTimeService;

	@Override
	public IPage<RecordVO> selectRecordPage(IPage<RecordVO> page, RecordVO record) {
		return page.setRecords(baseMapper.selectRecordPage(page, record));
	}

	@Override
	public List<MarkDayVo> getMarkByDate(RulePerson rulePerson, LocalDate localDate) {
		TimeInterval timer = DateUtil.timer();

		List<MarkDayVo> markDayVoList = new ArrayList<>();

		//获取ruleId
		RulePerson person = rulePersonService.getOne(Condition.getQueryWrapper(rulePerson));
		Long ruleId = person.getRuleId();


		//根据人员的id和时间获取月的打卡记录
		List<Record> records = baseMapper.selectList(Condition.getQueryWrapper(new Record()).lambda()
			.eq(Record::getPersonId, rulePerson.getPersonId())
			.eq(Record::getPersonType, rulePerson.getPersonType())
			.ge(Record::getCheckinDate, localDate.with(TemporalAdjusters.firstDayOfMonth()))
			.le(Record::getCheckinDate, localDate.with(TemporalAdjusters.lastDayOfMonth()))
			.orderByAsc(Record::getCheckinDate));

		//获取特殊日期
		List<RuleSpecTime> specTimes = ruleSpecTimeService.list(Condition.getQueryWrapper(new RuleSpecTime()).lambda()
			.eq(RuleSpecTime::getRuleId, ruleId)
		);
		//获取必须打卡的特殊日期
		List<LocalDate> needPunchDates = specTimes.stream().filter(item -> item.getIsMust() == 1).map(RuleSpecTime::getDate).collect(Collectors.toList());
		//获取不需要打卡的特殊日期
		List<LocalDate> dontNeedPunchDates = specTimes.stream().filter(item -> item.getIsMust() == 0).map(RuleSpecTime::getDate).collect(Collectors.toList());
		//正常日期
		List<RuleTime> ruleTimeList = ruleTimeService.list(Condition.getQueryWrapper(new RuleTime()).lambda().eq(RuleTime::getRuleId, ruleId));

		//获取该月的最后一天
		LocalDate lastDay = localDate.with(TemporalAdjusters.lastDayOfMonth());
		//获取绑定该人员打卡规则的日期
		LocalDate bindDate = person.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
		//获取当天的日期
		LocalDate now = LocalDate.now();

		//循环第一天到最后一天
		for (int i = 1; i <= lastDay.getDayOfMonth(); i++) {
			LocalDate date = LocalDate.of(localDate.getYear(), localDate.getMonth().getValue(), i);
			//如果当前日期在绑定人员规则前，不再判断
			if (date.isBefore(bindDate) || date.isEqual(bindDate)) {
				continue;
			}
			//如果在当前日之后,不再判断
			if (now.isBefore(date)) {
				break;
			}
			MarkDayVo markDayVo = this.getRuleByDate(specTimes, needPunchDates, dontNeedPunchDates, ruleTimeList, date);
			if (markDayVo.getIsInWork()) {
				//在工作日内，判断当天打卡的异常情况
				this.handelException(markDayVo, date, records, ruleTimeList);
			} else {
				//不在工作日内
				markDayVo.setLocalDate(date);
				markDayVo.setStatus(2);
				markDayVo.setTotalWorkTime("0");
			}
			markDayVoList.add(markDayVo);
		}

		System.out.println("花费毫秒数==>" + timer.interval());
		;
		timer.intervalRestart();//返回花费时间，并重置开始时间
		System.out.println("花费分钟数==>" + timer.intervalMinute());

		return markDayVoList;
	}

	/**
	 * 获取员工的打卡统计
	 * 出勤天数
	 * 正常记录
	 * 迟到记录
	 * 早退记录
	 * 范围外打卡异常
	 * 旷工异常记录
	 * 缺卡记录
	 */
	@Override
	public MyStatisticalVo getPersonStatistical(RulePerson rulePerson, LocalDate localDate) {

		MyStatisticalVo myStatisticalVo = new MyStatisticalVo();

		//根据人员的id和时间获取该月的打卡记录
		List<Record> records = baseMapper.selectList(Condition.getQueryWrapper(new Record()).lambda()
			.eq(Record::getPersonId, rulePerson.getPersonId())
			.eq(Record::getPersonType, rulePerson.getPersonType())
			.ge(Record::getCheckinDate, localDate.with(TemporalAdjusters.firstDayOfMonth()))
			.le(Record::getCheckinDate, localDate.with(TemporalAdjusters.lastDayOfMonth()))
			.orderByDesc(Record::getCheckinDate));

		//获取出勤天数
		long count = records.stream().map(Record::getCheckinDate).distinct().count();
		myStatisticalVo.setAttendanceDays(count);

		//获取正常记录
		List<Record> normalRecord = records.stream().filter(item -> StrUtil.isBlank(item.getExceptionMessage()) && item.getCheckinType() != 2)
			.collect(Collectors.toList());
		myStatisticalVo.setNormalRecord(normalRecord);

		//获取正常分组
		Map<LocalDate, List<Record>> normalRecordGroupingBy = records.stream().filter(item -> StrUtil.isBlank(item.getExceptionMessage()) && item.getCheckinType() != 2)
			.collect(Collectors.groupingBy(Record::getCheckinDate));
		myStatisticalVo.setNormalRecordGroupingBy(normalRecordGroupingBy);


		//获取迟到记录
		List<Record> lateRecord = records.stream().filter(item -> StrUtil.isNotBlank(item.getExceptionMessage()) && item.getExceptionMessage().contains("迟到"))
			.collect(Collectors.toList());
		myStatisticalVo.setLateRecord(lateRecord);

		//获取早退记录
		List<Record> earlyRecord = records.stream().filter(item -> StrUtil.isNotBlank(item.getExceptionMessage()) && item.getExceptionMessage().contains("早退"))
			.collect(Collectors.toList());
		myStatisticalVo.setEarlyRecord(earlyRecord);

		//获取出差记录
		List<Record> outWorkRecord = records.stream().filter(item -> item.getCheckinType() == 2).collect(Collectors.toList());
		Map<LocalDate, List<Record>> outWorkRecordGroupingBy = records.stream().filter(item -> item.getCheckinType() == 2).collect(Collectors.groupingBy(Record::getCheckinDate));
		myStatisticalVo.setOutWorkRecord(outWorkRecord);
		myStatisticalVo.setOutWorkRecordGroupingBy(outWorkRecordGroupingBy);

		//获取缺卡记录 begin
		//获取上下班记录并按日期分组
		Map<LocalDate, List<Record>> lackRecordGroupingBy = records.stream().filter(item -> item.getCheckinType() == 0 || item.getCheckinType() == 1)
			//不计算当天记录
			.filter(item -> !item.getCheckinDate().isEqual(LocalDate.now()))
			.collect(Collectors.groupingBy(Record::getCheckinDate));
		//移除有两条记录的值，剩下的就是缺卡的记录。 因为一天内上下班记录完整是两条，上班一条，下班一条
		lackRecordGroupingBy.entrySet().removeIf(item -> item.getValue().size() == 2);

		myStatisticalVo.setLackRecordGroupingBy(lackRecordGroupingBy);
		//获取缺卡记录 end


		return myStatisticalVo;

	}

	/**
	 * 处理异常情况
	 *
	 * @return
	 */
	private void handelException(MarkDayVo markDayVo, LocalDate date, List<Record> records, List<RuleTime> ruleTimeList) {

		LocalDate now = LocalDate.now();
		//在工作日内
		markDayVo.setLocalDate(date);
		//上班时间
		LocalDateTime workTime = LocalDateTime.of(now, markDayVo.getWorkTime());
		//查询当天是否有记录
		List<Record> recordList = records.stream().filter(item -> item.getCheckinDate().equals(date)).collect(Collectors.toList());

		//判断是否异常
		//异常的情况有
		//1：旷工异常【打卡记录为空】
		//2：迟到，早退，范围外异常【记录不为空】
		//3: 缺卡异常 【记录不完整，只有上班打卡，或者只有下班打卡】
		//4：缺卡异常不用考虑今天的记录
		//记录完整才可以计算工时
		//不完整一律0工时计算

		//获取上班打卡记录
		List<Record> workRecord = recordList.stream().filter(item -> item.getCheckinType() == 0).collect(Collectors.toList());
		//获取下班打卡记录
		List<Record> offWorkRecord = recordList.stream().filter(item -> item.getCheckinType() == 1).collect(Collectors.toList());

		if (CollUtil.isEmpty(recordList)) {
			//记录为空,旷工异常
			markDayVo.setStatus(1);
			//工作时长为0
			markDayVo.setTotalWorkTime("0");

			//如果是当天的记录，要做特殊处理
			if (now.isEqual(date)) {
				MarkDayVo todayRecord = this.handelTodayRecord(workTime, workRecord, offWorkRecord);
				markDayVo.setStatus(todayRecord.getStatus());
			}

		} else {
			//记录不为空

			//获取异常记录
			List<Record> abnormalList = recordList.stream().filter(item -> StrUtil.isNotBlank(item.getExceptionMessage())).collect(Collectors.toList());
			//外出打卡记录
			List<Record> outWorkRecord = recordList.stream().filter(item -> item.getCheckinType() == 2).collect(Collectors.toList());

			//赋值
			if (CollUtil.isNotEmpty(workRecord)) {
				markDayVo.setWorkRecord(workRecord.get(0));
			}
			if (CollUtil.isNotEmpty(offWorkRecord)) {
				markDayVo.setOffWorkRecord(offWorkRecord.get(0));
			}
			if (CollUtil.isNotEmpty(outWorkRecord)) {
				markDayVo.setOutWorkRecord(outWorkRecord);
			}

			//记录是否完整，判断是否缺卡异常
			if (CollUtil.isEmpty(workRecord) || CollUtil.isEmpty(offWorkRecord)) {

				//缺卡异常
				markDayVo.setStatus(1);
				//工时为0
				markDayVo.setTotalWorkTime("0");

				//如果是当天的记录，要做特殊处理
				if (now.isEqual(date)) {
					//上班时间
					MarkDayVo todayRecord = this.handelTodayRecord(workTime, workRecord, offWorkRecord);
					markDayVo.setStatus(todayRecord.getStatus());
				}

			} else {
				//记录完整，考虑是否迟到打卡，早退打卡，范围外打卡

				//上班打卡时间
				LocalDateTime begin = workRecord.get(0).getCheckinTime();
				//下班打卡时间
				LocalDateTime end = offWorkRecord.get(0).getCheckinTime();

				//计算工时,计算工时要考虑休息时间，不要把休息时间算进去
				if (ruleTimeList.get(0).getAllowRest() == 1) {
					//允许休息
					//休息开始时间
					LocalDateTime restBeginTime = LocalDateTime.of(date, ruleTimeList.get(0).getRestBeginTime());
					//休息结束时间
					LocalDateTime restEndTime = LocalDateTime.of(date, ruleTimeList.get(0).getRestEndTime());

					Duration duration1 = LocalDateTimeUtil.between(begin, end);
					Duration duration2 = LocalDateTimeUtil.between(restBeginTime, restEndTime);

					long totalMillis = duration1.toMillis() - duration2.toMillis();

					String totalWorkTime = DateUtil.formatBetween(totalMillis, BetweenFormatter.Level.MINUTE);
					markDayVo.setTotalWorkTime(totalWorkTime);

				} else {
					//不允许休息,计算上班打卡时间和下班打卡时间
					Duration duration = LocalDateTimeUtil.between(begin, end);
					String totalWorkTime = DateUtil.formatBetween(duration.toMillis(), BetweenFormatter.Level.MINUTE);
					markDayVo.setTotalWorkTime(totalWorkTime);
				}

				//有异常记录
				if (CollUtil.isNotEmpty(abnormalList)) {
					//异常
					markDayVo.setStatus(1);
				} else {
					//正常
					markDayVo.setStatus(0);
				}
			}
		}
	}

	/**
	 * 处理今天的记录
	 *
	 * @param workRecord    上班记录
	 * @param offWorkRecord 下班记录
	 * @return
	 */
	private MarkDayVo handelTodayRecord(LocalDateTime workTime, List<Record> workRecord, List<Record> offWorkRecord) {

		/*
		要特殊处理今天的记录
		* 因为今天的时间还未走完，不能用普通日期的逻辑来判断
		* 上班记录为空不能直接记为上班未打卡，有可能是在上班时间之前，还未打卡
		* 下班记录为空也不能直接记为下班未打卡，有可能是员工忘记打卡，但是当天还未走完，还可以继续打
		* */
		MarkDayVo markDayVo = new MarkDayVo();
		//初始化为异常
		markDayVo.setStatus(1);

		//当前时间
		LocalDateTime nowTime = LocalDateTime.now();

		if (CollUtil.isNotEmpty(workRecord)) {
			//上班记录不为空

			//没异常
			if (StrUtil.isBlank(workRecord.get(0).getExceptionMessage())) {
				if (CollUtil.isEmpty(offWorkRecord)) {
					markDayVo.setStatus(0);
				}
				//下班记录不为空，且记录没异常
				if (CollUtil.isNotEmpty(offWorkRecord)
					&& StrUtil.isBlank(offWorkRecord.get(0).getExceptionMessage())) {
					markDayVo.setStatus(0);
				}
			}

		} else {
			//上班记录为空，当前时间在上班时间之前
			if (nowTime.isBefore(workTime) || nowTime.isEqual(workTime)) {
				markDayVo.setStatus(0);
			}
		}
		return markDayVo;
	}

	/**
	 * 获取工作日的打卡规则
	 *
	 * @param specTimesList      特殊打卡日期
	 * @param needPunchDates     需要打卡的日期
	 * @param dontNeedPunchDates 不需要打卡的日期
	 * @param ruleTimeList       正常打卡的日期
	 * @param localDate          当前日期
	 * @return
	 */
	private MarkDayVo getRuleByDate(List<RuleSpecTime> specTimesList,
									List<LocalDate> needPunchDates,
									List<LocalDate> dontNeedPunchDates,
									List<RuleTime> ruleTimeList,
									LocalDate localDate) {

		MarkDayVo markDayVo = new MarkDayVo();
		//设置一个默认的上下班时间
		markDayVo.setWorkTime(ruleTimeList.get(0).getWorkTime());
		markDayVo.setOffWorkTime(ruleTimeList.get(0).getOffWorkTime());

		//当前日期是否在特殊日期内
		if (needPunchDates.contains(localDate)) {
			//在工作日内
			List<RuleSpecTime> collect = specTimesList.stream().filter(item -> item.getDate().equals(localDate)).collect(Collectors.toList());
			markDayVo.setWorkTime(collect.get(0).getBeginTime());
			markDayVo.setOffWorkTime(collect.get(0).getEndTime());
			markDayVo.setIsInWork(true);
			return markDayVo;
		}
		if (dontNeedPunchDates.contains(localDate)) {
			//不在工作日内
			markDayVo.setIsInWork(false);
			return markDayVo;
		}
		for (RuleTime ruleTime : ruleTimeList) {

			List<String> list = Arrays.asList(ruleTime.getWorkDays().split(","));
			//获取日期是星期几
			String i = String.valueOf(localDate.getDayOfWeek().getValue());
			if (i == "7") {
				i = "0";
			}
			if (list.contains(i)) {
				//在工作日内
				markDayVo.setIsInWork(true);
				markDayVo.setWorkTime(ruleTime.getWorkTime());
				markDayVo.setOffWorkTime(ruleTime.getOffWorkTime());
				break;
			} else {
				//不在工作日内
				markDayVo.setIsInWork(false);
			}
		}
		return markDayVo;
	}



}
