
package org.sky.scientific.service.impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.sky.core.log.exception.ServiceException;
import org.sky.core.mp.base.BaseServiceImpl;
import org.sky.core.mp.support.Condition;
import org.sky.core.mp.support.Query;
import org.sky.core.secure.utils.SecureUtil;
import org.sky.core.tool.utils.Charsets;
import org.sky.core.tool.utils.CollectionUtil;
import org.sky.core.tool.utils.Func;
import org.sky.core.tool.utils.StringUtil;
import org.sky.scientific.excel.XmTechnicianHoursExcel;
import org.sky.scientific.excel.XmTechnicianHoursListener;
import org.sky.scientific.excel.handle.AttendanceMergeStrategy;
import org.sky.scientific.excel.handle.CustomCellStyleHandler;
import org.sky.scientific.mapper.*;
import org.sky.scientific.pojo.dto.TechnicianDTO;
import org.sky.scientific.pojo.entity.XmTechnicianHours;
import org.sky.scientific.pojo.vo.DailyAttendance;
import org.sky.scientific.pojo.vo.DailyHours;
import org.sky.scientific.pojo.vo.TechnicianHoursMonthlyVO;
import org.sky.scientific.pojo.vo.XmTechnicianHoursVO;
import org.sky.scientific.service.IXmTechnicianHoursService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedInputStream;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 研发人员研发工时记录表 服务实现类
 *
 * @author Kd
 * @since 2025-06-29
 */
@Slf4j
@Service
@AllArgsConstructor
public class XmTechnicianHoursServiceImpl extends BaseServiceImpl<XmTechnicianHoursMapper, XmTechnicianHours> implements IXmTechnicianHoursService {

	private final XmMapper xmMapper;
	private final XmTechnicianMapper xmTechnicianMapper;
	private final AttendanceMapper attendanceMapper;
	private final TechnicianMapper technicianMapper;

	@SneakyThrows
	@Transactional(rollbackFor = Exception.class)
	@Override
	public void importXmTechnicianHours(MultipartFile file, String yearAndMonth) {
		XmTechnicianHoursListener listener = new XmTechnicianHoursListener();
		listener.setYearAndMonth(yearAndMonth);
		EasyExcel.read(new BufferedInputStream(file.getInputStream()))
			.registerReadListener(listener)
			.sheet(0)
			.headRowNumber(3) // 重要：设置表头行数(0-based)
			.doRead();
		List<XmTechnicianHoursExcel> records = listener.getRecords();
		if (CollectionUtil.isEmpty(records)) {
			throw new ServiceException("未能从模板中读取数据，请检查模板格式是否正确");
		}

		//需要新增的，以便后续批量新增
		List<XmTechnicianHours> insertList = new ArrayList<>();
		//需要更新的，以便后续批量修改
		List<XmTechnicianHours> updateList = new ArrayList<>();

		int batchCount = 20;
		//月度工时列表
		List<XmTechnicianHours> dbList = baseMapper.selectByYearAndMonth(yearAndMonth, null, null);

		List<JSONObject> objects = baseMapper.countByUnicodeAndWorkDate(yearAndMonth);
		JSONObject usedHoursByUnicodeAndDate = new JSONObject();
		for (JSONObject object : objects) {
			usedHoursByUnicodeAndDate.put(object.getString("unicode") + ":" + object.getString("workDate"), object.getDoubleValue("hoursTotal"));
		}
//		Collector<T, ?, Map<K, D>> tMapCollector = Collectors.groupingBy(XmTechnicianHours::getUnicode, Collectors.summingDouble(XmTechnicianHours::getWorkHours));
		//按人员筛选、根据日期分组，再对工时求和
//		Map<String, Double> usedByUnicodeAndDateMap = new HashMap<>();
		//将研发项目信息缓存起来
		Map<String, Long> xmMap = new HashMap<>();
		//将研发人员信息缓存起来
		Map<String, String> technicianMap = new HashMap<>();

		for (XmTechnicianHoursExcel record : records) {
			//保存
			if (StringUtil.isAllBlank(record.getXmmc(), record.getXmbh())) {
				throw new ServiceException("研发项目编号、研发项目名称都为空，请检查数据");
			}
			String xmKey = record.getXmmc() + ":" + record.getXmbh();
			Long xmId = null;
			if (xmMap.containsKey(xmKey)) {
				xmId = xmMap.get(xmKey);
			} else {
				xmId = xmMapper.getXmIdByXmmcOrXmbh(yearAndMonth, record.getXmmc(), record.getXmbh());
				if (xmId == null) {
					throw new ServiceException("序号为[" + record.getSerialNumber() + "]的研发项目编号和研发项目名称都不存在");
				}
				xmMap.put(xmKey, xmId);
			}

			String unicode = null;
			String techniciankey = record.getNumber() + ":" + record.getIdCard();
			if (technicianMap.containsKey(techniciankey)) {
				unicode = technicianMap.get(techniciankey);
			} else {
				unicode = technicianMapper.getUnicodeByNumberAndIdentityCard(record.getNumber(), record.getIdCard());
				if (unicode == null) {
					throw new ServiceException("序号为[" + record.getSerialNumber() + "]的研发人员不存在");
				}
				technicianMap.put(techniciankey, unicode);
			}

			String finalUnicode = unicode;
			Long finalXmId = xmId;

//			String finalUnicode1 = unicode;
//			Map<String, Double> collect = dbList.stream().filter(e -> e.getUnicode().equals(finalUnicode1)).collect(Collectors.groupingBy(XmTechnicianHours::getWorkDate, Collectors.summingDouble(XmTechnicianHours::getWorkHours)));
//			for (String workDate : collect.keySet()) {
//				Double v = usedByUnicodeAndDateMap.get(workDate);
//			}

			//根据人员和项目获取工时列表，用于后续决定是新增还是修改
//			Long finalXmId = xmId;
//			String finalUnicode = unicode;
//			List<XmTechnicianHours> byXmAndUnicodeList = dbList.stream().filter(e->e.getUnicode().equals(finalUnicode)).toList();

			//根据unicode、yearAndMonth获取考勤明细
			List<DailyAttendance> dailyAttendanceList = attendanceMapper.selectDailyAttendanceByUnicodeAndMonth(unicode, yearAndMonth);
			if (CollectionUtil.isEmpty(dailyAttendanceList)) {
				throw new ServiceException("未设置考勤数据");
			}
			//每天的考勤总时长
			Map<String, Double> hoursByDateMap = dailyAttendanceList.stream().collect(Collectors.groupingBy(DailyAttendance::getWorkDate, Collectors.summingDouble(DailyAttendance::getWorkHours)));


			for (DailyAttendance dailyAttendance : record.getDailyAttendances()) {

				String key = unicode + ":" + dailyAttendance.getWorkDate();
				Double hoursTotal = hoursByDateMap.get(dailyAttendance.getWorkDate());
				if (hoursTotal == null || hoursTotal <= 0) {
					throw new ServiceException(dailyAttendance.getWorkDate() + "当天无考勤数据");
				}
				Double usedHours = usedHoursByUnicodeAndDate.getDoubleValue(key);

				if (hoursTotal - usedHours < dailyAttendance.getWorkHours()) {
					//当 考勤时长 - 当天以用工时 < 新的工时 ，则提示“考勤时长不足”
					throw new ServiceException("[序号" + record.getSerialNumber() + ",日期" + dailyAttendance.getWorkDate() + "工时" + dailyAttendance.getWorkHours() + "]的考勤时长不足，出勤总时长" + hoursTotal + "，已用" + usedHours + "，可用" + (hoursTotal - usedHours) + "，请核对数据");
				}

				double doubleValue = usedHoursByUnicodeAndDate.getDoubleValue(key) + dailyAttendance.getWorkHours();

				List<Long> primaryKeyList = dbList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate()) && Objects.equals(e.getXmId(), finalXmId) && Objects.equals(e.getUnicode(), finalUnicode)).map(XmTechnicianHours::getId).toList();
				Long primaryKey = CollectionUtil.isEmpty(primaryKeyList) ? null : primaryKeyList.get(0);
				XmTechnicianHours entity = new XmTechnicianHours();
				if (primaryKey == null) {
					entity.setXmId(xmId);
					entity.setUnicode(unicode);
					entity.setWorkDate(dailyAttendance.getWorkDate());
					entity.setWorkHours(dailyAttendance.getWorkHours());
					entity.setTenantId(SecureUtil.getTenantId());
					entity.setCreateUser(SecureUtil.getUserId());
					entity.setCreateDept(Func.toLong(SecureUtil.getDeptId()));
					insertList.add(entity);

					//已用工时 加上新增的工时
//					double doubleValue = usedHoursByUnicodeAndDate.getDoubleValue(key) + dailyAttendance.getWorkHours();
					usedHoursByUnicodeAndDate.put(key, doubleValue);
				} else {
					entity.setId(primaryKey);
					entity.setWorkHours(dailyAttendance.getWorkHours());
					updateList.add(entity);

					//查询数据库中的原有的工时，需要加上旧值，在减去新值
					double oldHours = dbList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate()) && Objects.equals(e.getXmId(), finalXmId) && Objects.equals(e.getUnicode(), finalUnicode)).mapToDouble(XmTechnicianHours::getWorkHours).sum();

					//double oldHours = byXmAndUnicodeList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate())).mapToDouble(XmTechnicianHours::getWorkHours).sum();
//					double doubleValue = usedHoursByUnicodeAndDate.getDoubleValue(key) + dailyAttendance.getWorkHours();
					usedHoursByUnicodeAndDate.put(key, usedHours - oldHours + dailyAttendance.getWorkHours());
				}
				if (!insertList.isEmpty() && insertList.size() % batchCount == 0) {
					if (this.saveBatch(insertList)) {
						insertList.clear();
					}

				}
				if (!updateList.isEmpty() && updateList.size() % batchCount == 0) {
					baseMapper.batchUpdate(updateList);
					updateList.clear();
				}
			}
		}
		if (!insertList.isEmpty()) {
			saveBatch(insertList);
		}
		if (!updateList.isEmpty()) {
			baseMapper.batchUpdate(updateList);
		}
	}

	@Override
	public IPage<XmTechnicianHoursVO> selectPage(XmTechnicianHoursVO dto, IPage<XmTechnicianHoursVO> page) {
		if (StringUtil.isBlank(dto.getYearAndMonth())) {
			throw new ServiceException("参数yearAndMonth不能为空");
		}
		List<XmTechnicianHoursVO> list = baseMapper.selectHoursPage(page, dto);
		for (XmTechnicianHoursVO temp : list) {
			List<DailyAttendance> dailyAttendanceList = baseMapper.selectByXmIdAndUnicode(temp.getXmId(), temp.getUnicode(), dto.getYearAndMonth());
			if (CollectionUtil.isEmpty(dailyAttendanceList)) {
				continue;
			}
			temp.setDailyAttendances(dailyAttendanceList);
		}
		return page.setRecords(list);
	}

	@Override
	public boolean batchSubmit(XmTechnicianHoursVO dto) {
		if (StringUtil.isBlank(dto.getYearAndMonth())) {
			throw new ServiceException("参数yearAndMonth不能为空");
		}
		if (dto.getXmId() == null) {
			throw new ServiceException("参数xmId不能为空");
		}
		if (dto.getUnicode() == null) {
			throw new ServiceException("参数unicode不能为空");
		}

		//获取考勤数据，具体人员在某个月的考勤明细
		List<DailyAttendance> dailyAttendanceList = attendanceMapper.selectDailyAttendanceByUnicodeAndMonth(dto.getUnicode(), dto.getYearAndMonth());
		if (CollectionUtil.isEmpty(dailyAttendanceList)) {
			throw new ServiceException("未设置考勤数据");
		}

		//获取工时数据，具体项目、具体人员在某月的工时数据
		List<XmTechnicianHours> dbList = baseMapper.selectByYearAndMonth(dto.getYearAndMonth(), null, dto.getUnicode());

		//需要新增的，以便后续批量新增
		List<XmTechnicianHours> insertList = new ArrayList<>();
		//需要更新的，以便后续批量修改
		List<XmTechnicianHours> updateList = new ArrayList<>();

		for (DailyAttendance dailyAttendance : dto.getDailyAttendances()) {
			//查询一天的考勤时长
			double attendanceHours = dailyAttendanceList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate())).mapToDouble(DailyAttendance::getWorkHours).sum();
			if (attendanceHours <= 0) {
				throw new ServiceException(dailyAttendance.getWorkDate() + "当天无考勤数据");
			}
			//查询一天所有项目的工时总和
			double hoursTotal = dbList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate())).mapToDouble(XmTechnicianHours::getWorkHours).sum();

			List<XmTechnicianHours> tempList = dbList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate()) && Objects.equals(e.getXmId(), dto.getXmId())).toList();
			Long primaryKey = CollectionUtil.isEmpty(tempList) ? null : tempList.get(0).getId();

			XmTechnicianHours entity = new XmTechnicianHours();
			if (primaryKey == null) {

				//当 考勤时长 - 项目工时总和 - 新的工时 < 0，则提示“考勤时长不足”
				if (attendanceHours - hoursTotal - dailyAttendance.getWorkHours() < 0) {
					throw new ServiceException(dailyAttendance.getWorkDate() + "的考勤时长为" + attendanceHours + "小时，可填写的工时不能超过" + (attendanceHours - hoursTotal) + "小时，请核对数据");
				}

				entity.setXmId(dto.getXmId());
				entity.setUnicode(dto.getUnicode());
				entity.setWorkDate(dailyAttendance.getWorkDate());
				entity.setWorkHours(dailyAttendance.getWorkHours());
				entity.setTenantId(SecureUtil.getTenantId());
				entity.setCreateUser(SecureUtil.getUserId());
				entity.setCreateDept(Func.toLong(SecureUtil.getDeptId()));
				insertList.add(entity);
			} else {
//当 			考勤时长 - 项目工时总和 + 旧工时 - 新的工时 < 0，则提示“考勤时长不足”
				if (attendanceHours + tempList.get(0).getWorkHours() - hoursTotal - dailyAttendance.getWorkHours() < 0) {
					throw new ServiceException(dailyAttendance.getWorkDate() + "的考勤时长为" + attendanceHours + "小时，可填写的工时不能超过" + (attendanceHours + tempList.get(0).getWorkHours() - hoursTotal) + "小时，请核对数据");
				}
				entity.setId(primaryKey);
				entity.setWorkHours(dailyAttendance.getWorkHours());
				updateList.add(entity);
			}
		}

		if (!insertList.isEmpty()) {
			this.saveBatch(insertList);
			insertList.clear();
		}
		if (!updateList.isEmpty()) {
			baseMapper.batchUpdate(updateList);
			updateList.clear();
		}
		return true;
	}

	@Override
	public boolean clear(JSONObject dto) {
		JSONArray xmAndUnicodeList = dto.getJSONArray("xmAndUnicodeList");
		if (xmAndUnicodeList == null || xmAndUnicodeList.isEmpty()) {
			throw new ServiceException("参数xmAndUnicodeList不能为空");
		}
		for (Object xmAndUnicode : xmAndUnicodeList) {
			Map<String, String> map = (HashMap<String, String>) xmAndUnicode;
			baseMapper.clear(dto.getString("yearAndMonth"), Long.valueOf(map.get("xmId")), map.get("unicode"));
		}
		return true;
	}

	@Override
	public void export(HttpServletResponse response, XmTechnicianHoursVO dto) {
		try {
			response.setContentType("application/vnd.ms-excel");
			response.setCharacterEncoding("utf-8");
			response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("每月考勤信息.xls", Charsets.UTF_8));

			Query query = new Query().setCurrent(1).setSize(Integer.MAX_VALUE);

			List<XmTechnicianHoursVO> list = this.selectPage(dto, Condition.getPage(query)).getRecords();
			AtomicInteger index = new AtomicInteger(1);
			for (XmTechnicianHoursVO vo : list) {
				vo.setXh(index.getAndIncrement());
			}
			Integer year = Integer.valueOf(dto.getYearAndMonth().substring(0, 4));
			Integer month = Integer.valueOf(dto.getYearAndMonth().substring(5));

			// 构建数据
			List<List<Object>> data = buildData(list, year, month);

			// 导出Excel（包含合并单元格策略）
			EasyExcel.write(response.getOutputStream())
				.registerWriteHandler(new AttendanceMergeStrategy(year, month))
				.registerWriteHandler(new CustomCellStyleHandler())
				.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
				.autoCloseStream(true)
				.sheet("考勤信息")
				.doWrite(data);

		} catch (Exception e) {
			throw new ServiceException("导出失败：" + e.getLocalizedMessage());
		}
	}

	/**
	 * 构建数据行
	 */
	private List<List<Object>> buildData(List<XmTechnicianHoursVO> list, Integer year, Integer month) {
		List<List<Object>> data = new ArrayList<>();

//		LocalDate startDate = LocalDate.of(year, month, 1);
//		int daysInMonth = startDate.lengthOfMonth();
//		DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
//		DateTimeFormatter weekFormatter = DateTimeFormatter.ofPattern("E", Locale.CHINA);
//		DateTimeFormatter dayFormatter = DateTimeFormatter.ofPattern("d");
//
//		// 第一行：标题行
//		List<Object> titleRow = new ArrayList<>();
//		titleRow.add("研发工时记录表" + year + "年" + month + "月");
//		for (int i = 1; i < daysInMonth + 7; i++) { // 7 = 5个固定列 + 2个统计列
//			titleRow.add("");
//		}
//		data.add(titleRow);
//
//		// 第二行：纵向表头标题
//		List<Object> verticalHeaderRow = new ArrayList<>();
//		verticalHeaderRow.add("序号");
//		verticalHeaderRow.add("姓名");
//		verticalHeaderRow.add("工号");
//		verticalHeaderRow.add("身份证号码");
//		verticalHeaderRow.add("科研人员类别");
//		//第二行：周几行
//		for (int i = 1; i <= daysInMonth; i++) {
//			LocalDate date = LocalDate.of(year, month, i);
//			verticalHeaderRow.add(date.format(weekFormatter).substring(1));
//		}
//		verticalHeaderRow.add("本月出勤/小时");
//		verticalHeaderRow.add("本月出勤/天");
//		data.add(verticalHeaderRow);
//
//		// 第三行：日期数字行
//		List<Object> dayRow = new ArrayList<>();
//		dayRow.add("");
//		dayRow.add("");
//		dayRow.add("");
//		dayRow.add("");
//		dayRow.add("");
//		for (int day = 1; day <= daysInMonth; day++) {
//			LocalDate date = LocalDate.of(year, month, day);
//			dayRow.add(date.format(dayFormatter));
//		}
//		dayRow.add("");
//		dayRow.add("");
//		data.add(dayRow);
//
//		// 第4行开始：数据行
//		for (AttendanceVO attendance : attendanceList) {
//			List<Object> dataRow = new ArrayList<>();
//
//			// 序号
//			dataRow.add(attendance.getXh());
//			// 姓名
//			dataRow.add(attendance.getName());
//			// 工号
//			dataRow.add(attendance.getNumber());
//			// 身份证号码
//			dataRow.add(attendance.getIdCard());
//			// 科研人员类别
//			dataRow.add(attendance.getPersonnelType());
//
//			// 每日考勤数据
//			Map<String, Double> dailyMap = new HashMap<>();
//			if (attendance.getDailyAttendances() != null) {
//				for (DailyAttendance daily : attendance.getDailyAttendances()) {
//					dailyMap.put(daily.getWorkDate(), daily.getWorkHours());
//				}
//			}
//
//			for (int day = 1; day <= daysInMonth; day++) {
//				LocalDate currentDate = LocalDate.of(year, month, day);
//				String dateKey = currentDate.format(dateFormatter);
//				dataRow.add(dailyMap.getOrDefault(dateKey, 0.0));
//			}
//
//			// 统计信息
//			dataRow.add(attendance.getMonthlyHours());
//			dataRow.add(attendance.getMonthlyDays());
//
//			data.add(dataRow);
//		}

		return data;
	}

	@Override
	public List<JSONObject> selectListForPrint(XmTechnicianHoursVO dto) {
		return baseMapper.selectListForPrint(dto);
	}

	@Override
	public IPage<TechnicianHoursMonthlyVO> meiyueyanfagongshifenpeibiao(IPage<TechnicianHoursMonthlyVO> page, TechnicianDTO dto) {
		if (StringUtil.isBlank(dto.getYearAndMonth())) {
			throw new ServiceException("参数yearAndMonth不能为空");
		}
		List<TechnicianHoursMonthlyVO> list = baseMapper.meiyueyanfagongshifenpeibiao(page, dto);
		for (TechnicianHoursMonthlyVO vo : list) {
			List<DailyHours> dailyHours = baseMapper.selectDailyHoursByUnicodeAndMonth(dto.getYearAndMonth(), vo.getUnicode());
			//总出勤小时
			double attendanceTotal = 0;
			//总研发工时
			double workTotal = 0;
			if (CollectionUtil.isNotEmpty(dailyHours)) {
				for (DailyHours dailyHour : dailyHours) {
					//设置一天的非研发工时=当天考勤-研发工时
					dailyHour.setUnworkHours(Math.max(dailyHour.getAttendanceHours() - dailyHour.getWorkHours(), 0));
				}
				attendanceTotal = dailyHours.stream().mapToDouble(DailyHours::getAttendanceHours).sum();
				workTotal = dailyHours.stream().mapToDouble(DailyHours::getWorkHours).sum();
			}
			vo.setWorkHoursTotal(workTotal);
			//总非研发工时 = 总出勤 - 总研发
			vo.setUnworkHoursTotal(Math.max(attendanceTotal - workTotal, 0));
			//研发工时分配率 = 总研发工时/总出勤小时
			if (workTotal == 0 || attendanceTotal == 0) {
				vo.setWorkHoursRate(0.0);
			} else {
				vo.setWorkHoursRate(workTotal / attendanceTotal);
			}

			//非研发工时分配率 = 总非研发工时/总出勤小时
			if (vo.getUnworkHoursTotal() == 0 || attendanceTotal == 0) {
				vo.setUnworkHoursRate(0.0);
			} else {
				vo.setUnworkHoursRate(vo.getUnworkHoursTotal() / attendanceTotal);
			}
			vo.setDailyHoursList(dailyHours);
		}
		return page.setRecords(list);
	}
}
