package jnpf.attendance.service.impl.preschedule;

import jnpf.attendance.mapper.AttendanceGroupMapper;
import jnpf.entity.AttendanceGroup;
import jnpf.exception.HandleException;
import jnpf.exception.QueryException;
import jnpf.model.attendance.dto.scheduling.PreScheduleDayRevenueDto;
import jnpf.model.attendance.dto.scheduling.PreScheduleTableQueryDto;
import jnpf.util.StringUtil;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 预排班生成主表请求业务校验。
 */
public final class PreScheduleQueryValidator {

    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ISO_LOCAL_DATE;

    /** 预排班区间最大自然日数（闭区间）。 */
    public static final int MAX_SCHEDULE_DAY_SPAN = 62;

    private PreScheduleQueryValidator() {
    }

    /**
     * 校验请求并解析日期区间与按日营业额。
     *
     * @return key: startDate, endDate, revenueByDate
     */
    public static ValidatedPreScheduleQuery validate(
            PreScheduleTableQueryDto dto, AttendanceGroupMapper attendanceGroupMapper)
            throws HandleException, QueryException {
        if (dto == null) {
            throw new HandleException("请求体不能为空");
        }

        LocalDate start = parseDate(dto.getStartDate(), "startDate");
        LocalDate end = parseDate(dto.getEndDate(), "endDate");
        if (end.isBefore(start)) {
            throw new HandleException("结束日期不能早于开始日期");
        }

        long daySpan = ChronoUnit.DAYS.between(start, end) + 1;
        if (daySpan > MAX_SCHEDULE_DAY_SPAN) {
            throw new HandleException("排班区间不能超过" + MAX_SCHEDULE_DAY_SPAN + "天");
        }

        BigDecimal effTarget = dto.getEffTarget();
        if (effTarget == null || effTarget.compareTo(BigDecimal.ZERO) < 0) {
            throw new HandleException("人效目标须大于等于 0");
        }

        String groupId = dto.getGroupId();
        if (StringUtil.isBlank(groupId)) {
            throw new HandleException("考勤组ID不能为空");
        }
        assertAttendanceGroupExists(groupId, attendanceGroupMapper);

        Map<LocalDate, BigDecimal> revenueByDate = parseDailyRevenues(dto.getDailyRevenues(), start, end, (int) daySpan);

        ValidatedPreScheduleQuery validated = new ValidatedPreScheduleQuery();
        validated.setGroupId(groupId.trim());
        validated.setStartDate(start);
        validated.setEndDate(end);
        validated.setEffTarget(effTarget);
        validated.setRevenueByDate(revenueByDate);
        return validated;
    }

    private static LocalDate parseDate(String text, String fieldLabel) throws HandleException {
        if (StringUtil.isBlank(text)) {
            throw new HandleException(fieldLabel + "不能为空");
        }
        try {
            return LocalDate.parse(text.trim(), DATE_FMT);
        } catch (DateTimeParseException ex) {
            throw new HandleException(fieldLabel + "格式须为 yyyy-MM-dd");
        }
    }

    private static Map<LocalDate, BigDecimal> parseDailyRevenues(
            List<PreScheduleDayRevenueDto> dailyRevenues,
            LocalDate start,
            LocalDate end,
            int expectedDayCount)
            throws HandleException {
        if (dailyRevenues == null || dailyRevenues.isEmpty()) {
            throw new HandleException("按日预估营业额不能为空");
        }
        if (dailyRevenues.size() != expectedDayCount) {
            throw new HandleException(
                    "按日预估营业额条数须等于排班区间自然日数（期望 " + expectedDayCount + " 条）");
        }

        Map<LocalDate, BigDecimal> revenueByDate = new LinkedHashMap<>();
        Set<LocalDate> seen = new HashSet<>();
        for (PreScheduleDayRevenueDto item : dailyRevenues) {
            if (item == null) {
                throw new HandleException("按日预估营业额存在空项");
            }
            LocalDate day = parseDate(item.getDate(), "dailyRevenues.date");
            if (day.isBefore(start) || day.isAfter(end)) {
                throw new HandleException("按日预估营业额日期须落在排班区间内：" + day);
            }
            if (!seen.add(day)) {
                throw new HandleException("按日预估营业额日期重复：" + day);
            }
            if (item.getRevenue() == null) {
                throw new HandleException("按日预估营业额不能为空：" + day);
            }
            revenueByDate.put(day, item.getRevenue());
        }

        for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) {
            if (!revenueByDate.containsKey(d)) {
                throw new HandleException("缺少排班日预估营业额：" + d);
            }
        }
        return revenueByDate;
    }

    public static void assertAttendanceGroupExists(String groupId, AttendanceGroupMapper attendanceGroupMapper)
            throws QueryException {
        AttendanceGroup group = attendanceGroupMapper.selectById(groupId);
        if (group == null || (group.getDeleteMark() != null && group.getDeleteMark() != 0)) {
            throw new QueryException("考勤组不存在或已删除");
        }
    }

    /**
     * 校验通过后的查询上下文。
     */
    public static final class ValidatedPreScheduleQuery {
        private String groupId;
        private LocalDate startDate;
        private LocalDate endDate;
        private BigDecimal effTarget;
        private Map<LocalDate, BigDecimal> revenueByDate = new HashMap<>();

        public String getGroupId() {
            return groupId;
        }

        public void setGroupId(String groupId) {
            this.groupId = groupId;
        }

        public LocalDate getStartDate() {
            return startDate;
        }

        public void setStartDate(LocalDate startDate) {
            this.startDate = startDate;
        }

        public LocalDate getEndDate() {
            return endDate;
        }

        public void setEndDate(LocalDate endDate) {
            this.endDate = endDate;
        }

        public BigDecimal getEffTarget() {
            return effTarget;
        }

        public void setEffTarget(BigDecimal effTarget) {
            this.effTarget = effTarget;
        }

        public Map<LocalDate, BigDecimal> getRevenueByDate() {
            return revenueByDate;
        }

        public void setRevenueByDate(Map<LocalDate, BigDecimal> revenueByDate) {
            this.revenueByDate = revenueByDate;
        }
    }
}
