package jnpf.attendance.service.impl;

import jnpf.attendance.mapper.AttendanceGroupMapper;
import jnpf.attendance.schedule.ShiftPlanAssignmentResult;
import jnpf.attendance.schedule.ShiftPlanAssignmentResult.AssignedEmployeeView;
import jnpf.attendance.service.SmartPreScheduleService;
import jnpf.attendance.service.WorkstationService;
import jnpf.attendance.service.impl.preschedule.PreScheduleByEmployeeFilter;
import jnpf.attendance.service.impl.preschedule.PreScheduleIncompleteMsgBuilder;
import jnpf.attendance.service.impl.preschedule.PreScheduleQueryValidator;
import jnpf.attendance.service.impl.preschedule.PreScheduleQueryValidator.ValidatedPreScheduleQuery;
import jnpf.attendance.service.impl.preschedule.PreScheduleRedisSupport;
import jnpf.attendance.service.impl.preschedule.PreScheduleResultMapper;
import jnpf.attendance.service.impl.preschedule.PreScheduleSaveValidator;
import jnpf.attendance.service.impl.preschedule.PreScheduleSaveValidator.ValidatedPreScheduleSave;
import jnpf.exception.HandleException;
import jnpf.exception.QueryException;
import jnpf.model.attendance.dto.scheduling.PreScheduleTableQueryDto;
import jnpf.model.attendance.vo.WorkstationWithUsersVo;
import jnpf.model.attendance.vo.scheduling.PreScheduleTableVo;
import jnpf.util.StringUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;

/**
 * 预排班主表：校验请求、调用 {@link AutoScheduleService} 排班并映射为界面主表结构。
 */
@Slf4j
@Service
public class SmartPreScheduleServiceImpl implements SmartPreScheduleService {

    @Resource
    private AutoScheduleService autoScheduleService;

    @Resource
    private WorkstationService workstationService;

    @Resource
    private AttendanceGroupMapper attendanceGroupMapper;

    @Resource
    private PreScheduleRedisSupport preScheduleRedisSupport;

    /**
     * 预排班主表生成流水线：入参校验 → 自动排班引擎 → 拉取工位人员 → 组装表格 VO（剔除全程无人员的岗位/工作站），并将 byEmployee 写入 Redis。
     * 排班结果若含不完整说明仅记 warn，不阻断返回。
     */
    @SneakyThrows
    @Override
    public PreScheduleTableVo buildPreScheduleTable(PreScheduleTableQueryDto dto) {
        long startMs = System.currentTimeMillis();
        log.info("预排班生成主表，入参=>{}", dto);

        ValidatedPreScheduleQuery query =
                PreScheduleQueryValidator.validate(dto, attendanceGroupMapper);

        String groupId = query.getGroupId();
        LocalDate startDate = query.getStartDate();
        LocalDate endDate = query.getEndDate();

        ShiftPlanAssignmentResult assignmentResult =
                autoScheduleService.start(groupId, startDate, endDate, query.getRevenueByDate());

        List<WorkstationWithUsersVo> workstations =
                workstationService.listWorkstationsByGroupId(
                        groupId, toDateAtStartOfDay(startDate), toDateAtStartOfDay(endDate));

        PreScheduleTableVo result =
                PreScheduleResultMapper.toTableVo(
                        groupId,
                        startDate,
                        endDate,
                        query.getRevenueByDate(),
                        query.getEffTarget(),
                        assignmentResult,
                        workstations);

        String scheduleMsg = PreScheduleIncompleteMsgBuilder.build(assignmentResult);
        result.setMsg(scheduleMsg);
        if (StringUtil.isNotBlank(scheduleMsg)) {
            log.warn("预排班存在不完整说明，groupId=>{}，msg=>{}", groupId, scheduleMsg);
        }

        List<AssignedEmployeeView> byEmployee =
                assignmentResult == null ? List.of() : assignmentResult.getByEmployee();
        preScheduleRedisSupport.saveByEmployee(groupId, startDate, endDate, byEmployee);

        log.info(
                "预排班生成主表完成，groupId=>{}，行数=>{}，byEmployee人数=>{}，msg=>{}，耗时=>{}毫秒",
                groupId,
                result.getRows() == null ? 0 : result.getRows().size(),
                byEmployee.size(),
                scheduleMsg,
                System.currentTimeMillis() - startMs);
        return result;
    }

    /**
     * 按提交主表过滤 Redis 中的 byEmployee，写回缓存并返回 key 可变片段；不保存主表 VO、不重新跑排班。
     */
    @Override
    public String savePreScheduleTable(PreScheduleTableVo vo) throws HandleException, QueryException {
        long startMs = System.currentTimeMillis();
        log.info("预排班主表提交保存，入参=>{}", vo);
        try {
            ValidatedPreScheduleSave validated =
                    PreScheduleSaveValidator.validate(vo, attendanceGroupMapper);
            String groupId = validated.getGroupId();
            LocalDate startDate = validated.getStartDate();
            LocalDate endDate = validated.getEndDate();

            List<AssignedEmployeeView> cachedByEmployee =
                    preScheduleRedisSupport.requireByEmployee(groupId, startDate, endDate);
            List<AssignedEmployeeView> filteredByEmployee =
                    PreScheduleByEmployeeFilter.filterByTable(cachedByEmployee, validated.getTable());
            preScheduleRedisSupport.saveByEmployee(groupId, startDate, endDate, filteredByEmployee);

            String redisKeySuffix =
                    PreScheduleRedisSupport.formatRedisKeySuffix(groupId, startDate, endDate);

            log.info(
                    "预排班主表提交保存完成，groupId=>{}，区间=>{}..{}，耗时=>{}毫秒",
                    groupId,
                    startDate,
                    endDate,
                    System.currentTimeMillis() - startMs);
            return redisKeySuffix;
        } catch (HandleException | QueryException ex) {
            throw ex;
        } catch (Exception ex) {
            log.error("预排班主表提交保存失败", ex);
            throw new HandleException("预排班保存失败：" + ex.getMessage());
        }
    }

    /** 将 {@link LocalDate} 转为系统默认时区当日 00:00:00 的 {@link Date}，供工位查询等遗留 API 使用。 */
    private static Date toDateAtStartOfDay(LocalDate day) {
        return Date.from(day.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }
}
