package cn.iocoder.yudao.module.system.job;

import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.system.controller.admin.nursing.vo.HighFrequencyPlanItemVO;
import cn.iocoder.yudao.module.system.dal.dataobject.elderly.ElderlyNursingLogDO;
import cn.iocoder.yudao.module.system.dal.mysql.biz.NursingPlanItemMapper;
import cn.iocoder.yudao.module.system.dal.mysql.elderly.ElderlyNursingLogMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 每天生成护理日志
 */
@Component
@Slf4j
public class NurseLogSyncJob implements JobHandler {

    @Autowired
    private ElderlyNursingLogMapper elderlyNursingLogMapper;

    @Resource
    private NursingPlanItemMapper nursingPlanItemMapper;

    /**
     * 获取当前机构（tenantId）下，高频护理计划明细（frequency_category_type=1 且护理项目启用）列表。
     *
     * 返回字段来自：elderly_nursing_plan_item + nurse_item（例如 timeout）。
     */
    public List<HighFrequencyPlanItemVO> getCurrentOrgHighFrequencyNursingPlanItems() {
        Long tenantId = TenantContextHolder.getTenantId();
        if (tenantId == null) {
            return Collections.emptyList();
        }
        return nursingPlanItemMapper.selectHighFrequencyPlanItems(tenantId, LocalDateTime.now().toLocalDate());
    }

    /**
     * 根据护理明细的 frequencyType + frequency，计算“最小生成间隔”。
     *
     * 规则：在一个自然周期（天/周/月/年）内，期望生成 frequency 次，则间隔约等于 周期时长 / frequency。
     * 这里使用向上取整（ceil）避免生成过于频繁。
     *
     * frequencyType：
     * 1：次/日；2：次/周；3：次/月；4：次/年；5：时/次
     */
    private Duration calculateInterval(BigDecimal frequency, Integer frequencyType, LocalDateTime now) {
        if (frequency == null || frequency.compareTo(BigDecimal.ZERO) <= 0) {
            return null;
        }
        if (frequencyType == null) {
            return Duration.ofDays(1);
        }

        // frequencyType=5（时/次）语义不同：frequency 表示“每次间隔多少小时”，例如 frequency=2 => 间隔 2h
        if (frequencyType == 5) {
            long intervalSeconds = (long) Math.ceil(frequency.doubleValue() * Duration.ofHours(1).getSeconds());
            intervalSeconds = Math.max(1L, intervalSeconds);
            return Duration.ofSeconds(intervalSeconds);
        }

        long secondsPerPeriod;
        switch (frequencyType) {
            case 1:
                secondsPerPeriod = Duration.ofDays(1).getSeconds();
                break;
            case 2:
                secondsPerPeriod = Duration.ofDays(7).getSeconds();
                break;
            case 3:
                secondsPerPeriod = Duration.ofDays(now.toLocalDate().lengthOfMonth()).getSeconds();
                break;
            case 4:
                secondsPerPeriod = Duration.ofDays(now.toLocalDate().lengthOfYear()).getSeconds();
                break;
            default:
                secondsPerPeriod = Duration.ofDays(1).getSeconds();
                break;
        }

        double freq = frequency.doubleValue();
        if (freq <= 0D) {
            return null;
        }
        long intervalSeconds = (long) Math.ceil(secondsPerPeriod / freq);
        intervalSeconds = Math.max(1L, intervalSeconds);
        return Duration.ofSeconds(intervalSeconds);
    }

    /**
     * 将已超时但仍处于“待完成”的护理日志，更新为“未完成”状态。
     *
     * 判定标准：timeout 不为空 且 timeout 早于当前时间。
     */
    private int markOverdueLogsAsUnfinished(Long tenantId, LocalDateTime now) {
        List<ElderlyNursingLogDO> overdueLogs = elderlyNursingLogMapper.selectList(
                new LambdaQueryWrapperX<ElderlyNursingLogDO>()
                        .eq(ElderlyNursingLogDO::getTenantId, tenantId)
                        .eq(ElderlyNursingLogDO::getStatus, 0)
                        .isNotNull(ElderlyNursingLogDO::getTimeout)
                        .lt(ElderlyNursingLogDO::getTimeout, now)
                        .orderByDesc(ElderlyNursingLogDO::getId)
        );
        if (overdueLogs.isEmpty()) {
            return 0;
        }
        int updated = 0;
        for (ElderlyNursingLogDO logDO : overdueLogs) {
            logDO.setStatus(2);
            elderlyNursingLogMapper.updateById(logDO);
            updated++;
        }
        return updated;
    }


    @Override
    @TenantJob
    public String execute(String param) throws Exception {
        Long tenantId = TenantContextHolder.getTenantId();
        log.info("机构:{},护理日志记录定时器开始执行..........................",tenantId);

        LocalDateTime now = LocalDateTime.now();

        // 1) 先刷新历史日志状态：超时且仍待完成 => 未完成
        int overdueUpdated = markOverdueLogsAsUnfinished(tenantId, now);

        // 2) 获取本机构高频护理明细，用于按频率生成护理日志
        List<HighFrequencyPlanItemVO> items = getCurrentOrgHighFrequencyNursingPlanItems();
        if (items.isEmpty()) {
            log.info("护理日志记录定时器执行结束..........................");
            return String.format("租户{}无高频护理项目需要生成，更新未完成{}条", tenantId, overdueUpdated);
        }

        // 去重：同一个 nursingPlanItemId 只保留一条明细（避免重复生成）
        Map<Long, HighFrequencyPlanItemVO> itemByPlanItemId = items.stream()
                .filter(i -> i.getNursingPlanItemId() != null && i.getElderId() != null)
                .collect(Collectors.toMap(HighFrequencyPlanItemVO::getNursingPlanItemId, i -> i, (a, b) -> a));
        if (itemByPlanItemId.isEmpty()) {
            log.info("护理日志记录定时器执行结束..........................");
            return String.format("租户{}今日无有效护理明细需要生成，更新未完成{}条", tenantId, overdueUpdated);
        }
        List<Long> nursingPlanItemIds = new ArrayList<>(itemByPlanItemId.keySet());

        // 3) 查询每个护理明细最近一次生成的日志 createTime
        //    如果最新记录仍在“频率允许间隔”内，则不需要再生成
        Map<Long, LocalDateTime> latestCreateTimeByPlanItemId = elderlyNursingLogMapper
                .selectLatestCreateTimeByPlanItemIds(tenantId, nursingPlanItemIds)
                .stream()
                .filter(r -> r.getNursingPlanItemId() != null)
                .collect(Collectors.toMap(ElderlyNursingLogMapper.LatestCreateTimeDTO::getNursingPlanItemId,
                        ElderlyNursingLogMapper.LatestCreateTimeDTO::getCreateTime, (a, b) -> a));

        List<ElderlyNursingLogDO> toInsert = new ArrayList<>();
        for (HighFrequencyPlanItemVO item : itemByPlanItemId.values()) {
            // beginTime 未到，跳过生成
            if (item.getBeginTime() != null && now.isBefore(item.getBeginTime())) {
                continue;
            }
            Duration interval = calculateInterval(item.getFrequency(), item.getFrequencyType(), now);
            if (interval == null) {
                continue;
            }

            LocalDateTime latest = latestCreateTimeByPlanItemId.get(item.getNursingPlanItemId());
            // 最新生成时间仍在间隔内：不生成
            if (latest != null && !now.isAfter(latest.plus(interval))) {
                continue;
            }

            // 生成一条新的护理日志：
            // - timeout 由护理项目的 timeout 推算（单位：小时）
            LocalDateTime timeoutTime = null;
            if (item.getTimeout() != null && item.getTimeout() > 0) {
                timeoutTime = now.plusHours(item.getTimeout());
            }

            toInsert.add(ElderlyNursingLogDO.builder()
                    .nursingPlanItemId(item.getNursingPlanItemId())
                    .elderId(item.getElderId())
                    .tenantId(tenantId)
                    .timeout(timeoutTime)
                    .status(0)
                    .build());
        }

        int inserted = 0;
        for (ElderlyNursingLogDO logDO : toInsert) {
            elderlyNursingLogMapper.insert(logDO);
            inserted++;
        }
        log.info("护理日志记录定时器执行结束..........................");
        return String.format("租户{}新增护理日志{}条，更新未完成{}条", tenantId, inserted, overdueUpdated);
    }
}
