package jnpf.attendance.schedule;

import jnpf.model.attendance.vo.DayShiftRevenueStatVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 租户无数据分析营业额（或全部为 0 / null）时：不按营业额筛选，按<strong>有效历史排班日</strong>选相似样本。
 *
 * <p>与产品文档（无营业额场景）一致：
 * <ol>
 *   <li>最近 30 个自然日内，与目标日<strong>同星期几</strong>、且带有效班次列表的历史日 → 若至少 1 天则全部采用；若为 0 天则</li>
 *   <li>目标日前连续 {@value ScheduleTemplateSimilarDaysResult#PATTERN_SCHEDULE_LAST_NATURAL_DAY_WINDOW} 个自然日中，须<strong>每日均</strong>有有效班次
 *       → 采用该窗口内<strong>全部</strong>有班次日；若不满足则</li>
 *   <li>失败，提示请先手动补排 {@code n} 天，
 *       {@code n = }{@link ScheduleTemplateSimilarDaysResult#PATTERN_SCHEDULE_LAST_NATURAL_DAY_WINDOW}
 *       −（该窗口内有有效班次的天数）（见 {@link ScheduleTemplateSimilarDaysResult#msgInsufficientHistoryForPatternSchedule(int)}）。
 * </ol>
 */
public final class SchedulePatternSimilarDaysAlgorithm {

    private static final Logger LOG = LoggerFactory.getLogger(SchedulePatternSimilarDaysAlgorithm.class);

    /** 与产品「最近30天」口径一致（不含目标日） */
    public static final int SAME_WEEKDAY_LOOKBACK_DAYS = 30;

    /** 最近自然日窗口长度（应与 {@link ScheduleTemplateSimilarDaysResult#PATTERN_SCHEDULE_LAST_NATURAL_DAY_WINDOW} 一致） */
    public static final int LAST_NATURAL_DAY_WINDOW = ScheduleTemplateSimilarDaysResult.PATTERN_SCHEDULE_LAST_NATURAL_DAY_WINDOW;

    /**
     * 条件（2）中要求最少「有效排班」天数：连续窗口内<strong>均需</strong>有班次 ⇒ 等与窗口长度相等。
     */
    public static final int MIN_EFFECTIVE_DAYS_IN_LAST_NATURAL_WINDOW = LAST_NATURAL_DAY_WINDOW;

    /**
     * 与 {@link #findForScheduleDays(List, Map)} 相同入参语义：仅使用
     * {@code estimatedRevenueByDay} 的 key 集合作为目标排班日。
     *
     * @param dayShiftRevenueStatVoList 通常为近 90 天历史；内部按 ISO 日历日聚合
     * @param estimatedRevenueByDay     key 必填；营业额值在本算法中不参与筛选
     */
    public Map<LocalDate, ScheduleTemplateSimilarDaysResult> findForScheduleDays(
            List<DayShiftRevenueStatVo> dayShiftRevenueStatVoList,
            Map<LocalDate, BigDecimal> estimatedRevenueByDay) {
        if (estimatedRevenueByDay == null || estimatedRevenueByDay.isEmpty()) {
            LOG.error("[排班模板-规律模式][多日] estimatedRevenueByDay 为空，跳过");
            return Collections.emptyMap();
        }
        Map<String, DayShiftRevenueStatVo> byIso = normalizedHistoryIndex(dayShiftRevenueStatVoList);
        Map<LocalDate, ScheduleTemplateSimilarDaysResult> out = new LinkedHashMap<>();
        for (LocalDate scheduleDay : estimatedRevenueByDay.keySet()) {
            if (scheduleDay == null) {
                continue;
            }
            ScheduleTemplateSimilarDaysResult r = findSimilarPatternDays(byIso, scheduleDay);
            out.put(scheduleDay, r);
            LOG.error(
                    "[排班模板-规律模式][多日] scheduleDay={} success={} tier={} similarCount={} message={}",
                    scheduleDay,
                    r.isSuccess(),
                    r.getTier(),
                    r.getSimilarDates().size(),
                    r.getMessage());
        }
        return Collections.unmodifiableMap(out);
    }

    public ScheduleTemplateSimilarDaysResult findSimilarPatternDaysForOneScheduleDay(
            List<DayShiftRevenueStatVo> dayShiftRevenueStatVoList, LocalDate targetScheduleDay) {
        Map<String, DayShiftRevenueStatVo> byIso = normalizedHistoryIndex(dayShiftRevenueStatVoList);
        return findSimilarPatternDays(byIso, targetScheduleDay);
    }

    /** key: ISO yyyy-MM-dd（与相似日返回的 LocalDate.toString() 对齐） */
    public static Map<String, DayShiftRevenueStatVo> normalizedHistoryIndex(
            List<DayShiftRevenueStatVo> dayShiftRevenueStatVoList) {
        Map<String, DayShiftRevenueStatVo> map = new LinkedHashMap<>();
        if (dayShiftRevenueStatVoList == null || dayShiftRevenueStatVoList.isEmpty()) {
            return map;
        }
        for (DayShiftRevenueStatVo vo : dayShiftRevenueStatVoList) {
            if (vo == null || vo.getDay() == null || vo.getDay().trim().isEmpty()) {
                continue;
            }
            String trimmed = vo.getDay().trim();
            String key =
                    AttendanceScheduleDayParse.normalizeToIsoDayString(trimmed).orElse(trimmed);
            if (key.isEmpty()) {
                continue;
            }
            map.putIfAbsent(key, vo);
        }
        return map;
    }

    private ScheduleTemplateSimilarDaysResult findSimilarPatternDays(
            Map<String, DayShiftRevenueStatVo> byIso, LocalDate targetScheduleDay) {
        Objects.requireNonNull(targetScheduleDay, "targetScheduleDay");
        Objects.requireNonNull(byIso, "byIso");

        DayOfWeek dow = targetScheduleDay.getDayOfWeek();
        // （1）最近 30 个自然日内、同星期的有效排班日（≥1 天即接受）
        List<LocalDate> sameWeekEffective = new ArrayList<>();
        for (int delta = 1; delta <= SAME_WEEKDAY_LOOKBACK_DAYS; delta++) {
            LocalDate d = targetScheduleDay.minusDays(delta);
            if (d.getDayOfWeek() != dow) {
                continue;
            }
            DayShiftRevenueStatVo vo = byIso.get(d.toString());
            if (hasEffectiveShiftStructure(vo)) {
                sameWeekEffective.add(d);
            }
        }
        Collections.sort(sameWeekEffective);
        if (!sameWeekEffective.isEmpty()) {
            SchedulingForTestCheckLog.info(
                    LOG,
                    SchedulingForTestCheckLog.TEST_ISSUE_1_SIMILAR_HISTORICAL_DATES,
                    "similar_days_pattern",
                    String.format(
                            "algo=SchedulePatternSimilarDaysAlgorithm | targetScheduleDay=%s | branch=1_SAME_WEEKDAY_30_NATURAL_DAYS "
                                    + "| lookBackDays=%d | targetDow=%s | effectiveSameWeekdayHistoryCount=%d | similarDates=%s",
                            targetScheduleDay,
                            SAME_WEEKDAY_LOOKBACK_DAYS,
                            dow,
                            sameWeekEffective.size(),
                            SchedulingForTestCheckLog.formatDatesBrief(
                                    sameWeekEffective, SchedulingForTestCheckLog.DEFAULT_DATE_CAP)));
            return ScheduleTemplateSimilarDaysResult.ok(
                    sameWeekEffective, ScheduleTemplateSimilarDaysResult.SelectionTier.PATTERN_SAME_WEEKDAY_30D);
        }

        // （2）连续「最近 LAST_NATURAL_DAY_WINDOW」个自然日均须有有效班次
        List<LocalDate> last7Effective = new ArrayList<>();
        for (int delta = 1; delta <= LAST_NATURAL_DAY_WINDOW; delta++) {
            LocalDate d = targetScheduleDay.minusDays(delta);
            DayShiftRevenueStatVo vo = byIso.get(d.toString());
            if (hasEffectiveShiftStructure(vo)) {
                last7Effective.add(d);
            }
        }
        Collections.sort(last7Effective);
        if (last7Effective.size() >= MIN_EFFECTIVE_DAYS_IN_LAST_NATURAL_WINDOW) {
            SchedulingForTestCheckLog.info(
                    LOG,
                    SchedulingForTestCheckLog.TEST_ISSUE_1_SIMILAR_HISTORICAL_DATES,
                    "similar_days_pattern",
                    String.format(
                            "algo=SchedulePatternSimilarDaysAlgorithm | targetScheduleDay=%s | branch=2_LAST_%d_NATURAL_DAYS_ALL_EFFECTIVE "
                                    + "| naturalWindowDays=%d | requiredEffectiveEq=%d | effectiveDaysWithShifts=%d | similarDates=%s",
                            targetScheduleDay,
                            LAST_NATURAL_DAY_WINDOW,
                            LAST_NATURAL_DAY_WINDOW,
                            MIN_EFFECTIVE_DAYS_IN_LAST_NATURAL_WINDOW,
                            last7Effective.size(),
                            SchedulingForTestCheckLog.formatDatesBrief(
                                    last7Effective, SchedulingForTestCheckLog.DEFAULT_DATE_CAP)));
            return ScheduleTemplateSimilarDaysResult.ok(
                    last7Effective, ScheduleTemplateSimilarDaysResult.SelectionTier.PATTERN_LAST_7_NATURAL_GE7);
        }

        String failMsg =
                ScheduleTemplateSimilarDaysResult.msgInsufficientHistoryForPatternSchedule(last7Effective.size());

        LOG.error(
                "[排班模板-规律模式] target={} 同周30天内无班次，且近{}日有效排班仅{}天（需={}天才可）→ 拒绝智能排班",
                targetScheduleDay,
                LAST_NATURAL_DAY_WINDOW,
                last7Effective.size(),
                MIN_EFFECTIVE_DAYS_IN_LAST_NATURAL_WINDOW);
        SchedulingForTestCheckLog.info(
                LOG,
                SchedulingForTestCheckLog.TEST_ISSUE_1_SIMILAR_HISTORICAL_DATES,
                "similar_days_pattern",
                String.format(
                        "algo=SchedulePatternSimilarDaysAlgorithm | targetScheduleDay=%s | outcome=FAIL | branch=FALLBACK "
                                + "| sameWeekday30dCandidates=0 | lastNaturalWindowEffectiveDays=%d | requiredEffectiveEq=%d | bizMessage=%s",
                        targetScheduleDay,
                        last7Effective.size(),
                        MIN_EFFECTIVE_DAYS_IN_LAST_NATURAL_WINDOW,
                        failMsg));
        return ScheduleTemplateSimilarDaysResult.failure(failMsg);
    }

    /** 班次列表非空即视为可作半小时表输入（与§2.4「有班次结构」一致）。 */
    private static boolean hasEffectiveShiftStructure(DayShiftRevenueStatVo vo) {
        return vo != null && vo.getShifts() != null && !vo.getShifts().isEmpty();
    }
}
