package jnpf.attendance.service.impl.preschedule;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import jnpf.attendance.schedule.ShiftPlanAssignmentResult.AssignedEmployeeView;
import jnpf.attendance.schedule.ShiftPlanAssignmentResult.AssignedShiftRef;
import jnpf.constants.RedisConstant;
import jnpf.exception.HandleException;
import jnpf.model.attendance.vo.scheduling.PreScheduleTableVo;
import jnpf.util.StringUtil;
import jnpf.util.UserProvider;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 预排班按员工排班结果 Redis 暂存。
 */
@Component
public class PreScheduleRedisSupport {

    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ISO_LOCAL_DATE;

    /** 预排班 byEmployee 暂存过期时间（分钟）。 */
    public static final long PRE_SCHEDULE_CACHE_TTL_MINUTES = 10L;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 写入排班算法 {@code byEmployee} 列表（直接 JSON 序列化，不包装额外类型）。
     */
    public void saveByEmployee(
            String groupId,
            LocalDate startDate,
            LocalDate endDate,
            List<AssignedEmployeeView> byEmployee) {
        String redisKey = resolveByEmployeeRedisKey(groupId, startDate, endDate);
        List<AssignedEmployeeView> payload = byEmployee == null ? Collections.emptyList() : byEmployee;
        stringRedisTemplate
                .opsForValue()
                .set(
                        redisKey,
                        JSON.toJSONString(payload),
                        PRE_SCHEDULE_CACHE_TTL_MINUTES,
                        TimeUnit.MINUTES);
    }

    /**
     * 读取缓存的 {@code byEmployee}；不存在或已过期时返回空列表。
     */
    public List<AssignedEmployeeView> loadByEmployee(
            String groupId, LocalDate startDate, LocalDate endDate) {
        String redisKey = resolveByEmployeeRedisKey(groupId, startDate, endDate);
        String json = stringRedisTemplate.opsForValue().get(redisKey);
        if (StringUtil.isBlank(json)) {
            return Collections.emptyList();
        }
        return parseByEmployeeJson(json);
    }

    /**
     * 读取并校验 {@code byEmployee} 缓存必须存在（提交保存前须先执行预排班生成）。
     */
    public List<AssignedEmployeeView> requireByEmployee(
            String groupId, LocalDate startDate, LocalDate endDate) throws HandleException {
        List<AssignedEmployeeView> cached = loadByEmployee(groupId, startDate, endDate);
        if (cached.isEmpty()) {
            throw new HandleException("排班数据已失效，请重新执行预排班。");
        }
        return cached;
    }

    /**
     * 完整 Redis key（租户 + 考勤组 + 排班区间）。
     */
    public static String resolveByEmployeeRedisKey(
            String tenantId, String groupId, LocalDate startDate, LocalDate endDate) {
        return String.format(RedisConstant.ATTENDANCE_SMART_PRE_SCHEDULE_BY_EMPLOYEE_PREFIX, tenantId, formatRedisKeySuffix(groupId, startDate, endDate));
    }

    /**
     * 除固定前缀与租户外的 key 片段：{@code 考勤组:yyyy-MM-dd_yyyy-MM-dd}，供前端后续确认落库时回传。
     */
    public static String formatRedisKeySuffix(
            String groupId, LocalDate startDate, LocalDate endDate) {
        String dateRangeKey = PreScheduleSaveValidator.formatDateRangeKey(startDate, endDate);
        return groupId + ":" + dateRangeKey;
    }

    private String resolveByEmployeeRedisKey(String groupId, LocalDate startDate, LocalDate endDate) {
        return resolveByEmployeeRedisKey(
                UserProvider.getUser().getTenantId(), groupId, startDate, endDate);
    }

    private static List<AssignedEmployeeView> parseByEmployeeJson(String json) {
        JSONArray arr = JSON.parseArray(json);
        if (arr == null || arr.isEmpty()) {
            return Collections.emptyList();
        }
        List<AssignedEmployeeView> list = new ArrayList<>(arr.size());
        for (int i = 0; i < arr.size(); i++) {
            JSONObject empObj = arr.getJSONObject(i);
            if (empObj == null) {
                continue;
            }
            String userId = empObj.getString("userId");
            String userName = empObj.getString("userName");
            JSONArray shiftsArr = empObj.getJSONArray("shifts");
            List<AssignedShiftRef> shifts = parseShifts(shiftsArr);
            list.add(new AssignedEmployeeView(userId, userName, shifts));
        }
        return list;
    }

    private static List<AssignedShiftRef> parseShifts(JSONArray shiftsArr) {
        if (shiftsArr == null || shiftsArr.isEmpty()) {
            return Collections.emptyList();
        }
        List<AssignedShiftRef> shifts = new ArrayList<>(shiftsArr.size());
        for (int j = 0; j < shiftsArr.size(); j++) {
            JSONObject shiftObj = shiftsArr.getJSONObject(j);
            if (shiftObj == null) {
                continue;
            }
            LocalDate scheduleDay = parseLocalDate(shiftObj.get("scheduleDay"));
            if (scheduleDay == null) {
                continue;
            }
            shifts.add(
                    new AssignedShiftRef(
                            scheduleDay,
                            shiftObj.getString("shiftId"),
                            shiftObj.getString("timeRangeText"),
                            shiftObj.getBooleanValue("fixedScheduling"),
                            shiftObj.getString("postId")));
        }
        return shifts;
    }

    private static LocalDate parseLocalDate(Object raw) {
        if (raw == null) {
            return null;
        }
        if (raw instanceof LocalDate) {
            return (LocalDate) raw;
        }
        if (raw instanceof String) {
            String text = ((String) raw).trim();
            if (text.isEmpty()) {
                return null;
            }
            try {
                return LocalDate.parse(text, DATE_FMT);
            } catch (DateTimeParseException ex) {
                return null;
            }
        }
        if (raw instanceof JSONObject) {
            JSONObject o = (JSONObject) raw;
            Integer year = o.getInteger("year");
            Integer month = o.getInteger("monthValue");
            if (month == null) {
                month = o.getInteger("month");
            }
            Integer day = o.getInteger("dayOfMonth");
            if (day == null) {
                day = o.getInteger("day");
            }
            if (year != null && month != null && day != null) {
                return LocalDate.of(year, month, day);
            }
        }
        return null;
    }

    /**
     * 按租户 + 草稿 id 读取 {@link ShiftPlanAssignmentResult#getByEmployee()} 序列化结果。
     *
     * <p>Redis 值须为 {@link AssignedEmployeeView} JSON 数组；key 不存在或解析失败时返回空列表。
     * 若仍为旧版 {@link PreScheduleTableVo}，则只读转换为 byEmployee（不写 Redis）。
     */
    public List<AssignedEmployeeView> getByEmployeeByDraftId(String draftId) {
        if (StringUtil.isEmpty(draftId)) {
            return Collections.emptyList();
        }
        String tenantId = UserProvider.getUser().getTenantId();
        String redisKey = String.format(RedisConstant.ATTENDANCE_SMART_PRE_SCHEDULE_BY_EMPLOYEE_PREFIX, tenantId, draftId);
        String json = stringRedisTemplate.opsForValue().get(redisKey);
        if (StringUtil.isEmpty(json)) {
            return Collections.emptyList();
        }
        String trimmed = json.trim();
        if (trimmed.startsWith("[")) {
            List<AssignedEmployeeView> list = JSON.parseArray(trimmed, AssignedEmployeeView.class);
            return list == null ? Collections.emptyList() : list;
        }

        return Collections.emptyList();

    }
}
