package com.yimeng.okr.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yimeng.okr.dto.PeriodReadinessDto;
import com.yimeng.okr.entity.AssessmentPeriod;
import com.yimeng.okr.entity.KpiConfig;
import com.yimeng.okr.entity.KrKeyResult;
import com.yimeng.okr.entity.OkrObjective;
import com.yimeng.okr.entity.PerformanceFeedback;
import com.yimeng.okr.entity.PerformanceScore;
import com.yimeng.okr.entity.PeriodParticipant;
import com.yimeng.okr.entity.SysUser;
import com.yimeng.okr.enums.PeriodStatus;
import com.yimeng.okr.enums.PeriodType;
import com.yimeng.okr.enums.ScoreType;
import com.yimeng.okr.event.NotificationEvent;
import com.yimeng.okr.exception.BusinessException;
import com.yimeng.okr.mapper.AssessmentPeriodMapper;
import com.yimeng.okr.mapper.KpiConfigMapper;
import com.yimeng.okr.mapper.KrKeyResultMapper;
import com.yimeng.okr.mapper.OkrObjectiveMapper;
import com.yimeng.okr.mapper.PerformanceFeedbackMapper;
import com.yimeng.okr.mapper.PerformanceScoreMapper;
import com.yimeng.okr.mapper.PeriodParticipantMapper;
import com.yimeng.okr.mapper.SysUserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

@Slf4j
@Service
@RequiredArgsConstructor
public class AssessmentPeriodService {

    private final AssessmentPeriodMapper periodMapper;
    private final SysUserMapper userMapper;
    private final OkrObjectiveMapper objectiveMapper;
    private final KrKeyResultMapper krMapper;
    private final KpiConfigMapper kpiConfigMapper;
    private final PerformanceScoreMapper scoreMapper;
    private final PerformanceFeedbackMapper feedbackMapper;
    private final PeriodParticipantMapper participantMapper;
    private final ApplicationEventPublisher publisher;
    private final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();

    public List<AssessmentPeriod> list(boolean isAdmin) {
        LambdaQueryWrapper<AssessmentPeriod> wrapper = new LambdaQueryWrapper<AssessmentPeriod>()
                .orderByAsc(AssessmentPeriod::getStartDate);
        if (!isAdmin) {
            wrapper.le(AssessmentPeriod::getStartDate, LocalDate.now());
        }
        return periodMapper.selectList(wrapper);
    }

    public AssessmentPeriod getById(Long id) {
        AssessmentPeriod period = periodMapper.selectById(id);
        if (period == null) throw new BusinessException(404, "考核周期不存在");
        return period;
    }

    public AssessmentPeriod create(AssessmentPeriod period) {
        period.setStatus(PeriodStatus.DRAFT);
        if (period.getPeriodType() != null && period.getEndDate() == null) {
            period.setEndDate(period.getPeriodType().calculateEndDate(period.getStartDate()));
        }
        periodMapper.insert(period);
        return period;
    }

    public void update(AssessmentPeriod period) {
        AssessmentPeriod existing = getById(period.getId());
        if (existing.getStatus() != PeriodStatus.DRAFT) {
            throw new BusinessException("非待发布状态不可修改");
        }
        periodMapper.updateById(period);
    }

    @Transactional
    public void transitionStatus(Long id, PeriodStatus targetStatus) {
        AssessmentPeriod period = getById(id);
        PeriodStatus current = period.getStatus();

        if (current == targetStatus) {
            return;
        }

        if (!isValidTransition(current, targetStatus)) {
            String hint = switch (current) {
                case DRAFT -> "请先「发布 OKR 对齐」";
                case OKR_ALIGN -> "请先「锁定并开始执行」，或等待团队完成 OKR 对齐";
                case EXECUTING -> "请先「进入考评」或「直接归档」";
                case ASSESSING -> "请先「结束并归档」";
                case ARCHIVED -> "已归档的周期不可再变更";
            };
            throw new BusinessException(String.format("当前状态【%s】不能直接转为【%s】。%s",
                    current.getLabel(), targetStatus.getLabel(), hint));
        }

        requireReadyForTransition(period, targetStatus);

        period.setStatus(targetStatus);
        periodMapper.updateById(period);
        if (targetStatus == PeriodStatus.OKR_ALIGN) {
            createParticipantSnapshot(period.getId());
        }
        if (targetStatus == PeriodStatus.ARCHIVED) {
            snapshotPeriod(period.getId());
        }
        log.info("Period {} status: {} -> {}", id, current.getLabel(), targetStatus.getLabel());

        notifyPeriodTransition(period, targetStatus);
    }

    public PeriodReadinessDto checkReadiness(Long id, PeriodStatus targetStatus) {
        AssessmentPeriod period = getById(id);
        PeriodReadinessDto readiness = new PeriodReadinessDto();
        readiness.setPeriodId(period.getId());
        readiness.setCurrentStatus(period.getStatus());
        readiness.setTargetStatus(targetStatus);

        List<String> blockers = readiness.getBlockers();
        if (!isValidTransition(period.getStatus(), targetStatus) && period.getStatus() != targetStatus) {
            blockers.add("当前状态不能直接转为" + targetStatus.getLabel());
        } else {
            collectTransitionBlockers(period.getId(), targetStatus, blockers);
        }
        readiness.setReady(blockers.isEmpty());
        return readiness;
    }

    private void requireReadyForTransition(AssessmentPeriod period, PeriodStatus targetStatus) {
        List<String> blockers = new ArrayList<>();
        collectTransitionBlockers(period.getId(), targetStatus, blockers);
        if (!blockers.isEmpty()) {
            throw new BusinessException("周期尚未满足进入【" + targetStatus.getLabel() + "】条件：" + String.join("；", blockers));
        }
    }

    private void collectTransitionBlockers(Long periodId, PeriodStatus targetStatus, List<String> blockers) {
        if (targetStatus == PeriodStatus.EXECUTING || targetStatus == PeriodStatus.ASSESSING) {
            long pendingOkrs = objectiveMapper.selectCount(
                    new LambdaQueryWrapper<OkrObjective>()
                            .eq(OkrObjective::getPeriodId, periodId)
                            .in(OkrObjective::getStatus, List.of("DRAFT", "SUBMITTED")));
            if (pendingOkrs > 0) {
                blockers.add("存在 " + pendingOkrs + " 条待审核OKR");
            }

            long unlockedKpis = kpiConfigMapper.selectCount(
                    new LambdaQueryWrapper<KpiConfig>()
                            .eq(KpiConfig::getPeriodId, periodId)
                            .isNull(KpiConfig::getLockedAt));
            if (unlockedKpis > 0) {
                blockers.add("存在 " + unlockedKpis + " 份未锁定KPI");
            }

            List<Long> participantIds = getParticipantIds(periodId, false);
            if (participantIds.isEmpty()) {
                blockers.add("暂无参与考核人员");
            }
            if (!participantIds.isEmpty()) {
                Set<Long> approvedOkrUsers = userIdsFromObjectives(objectiveMapper.selectList(
                        new LambdaQueryWrapper<OkrObjective>()
                                .eq(OkrObjective::getPeriodId, periodId)
                                .eq(OkrObjective::getStatus, "APPROVED")
                                .in(OkrObjective::getUserId, participantIds)));
                long missingOkr = participantIds.stream().filter(id -> !approvedOkrUsers.contains(id)).count();
                if (missingOkr > 0) {
                    blockers.add("存在 " + missingOkr + " 人未提交或未通过OKR");
                }

                Set<Long> lockedKpiUsers = userIdsFromKpiConfigs(kpiConfigMapper.selectList(
                        new LambdaQueryWrapper<KpiConfig>()
                                .eq(KpiConfig::getPeriodId, periodId)
                                .isNotNull(KpiConfig::getLockedAt)
                                .in(KpiConfig::getUserId, participantIds)));
                long missingKpi = participantIds.stream().filter(id -> !lockedKpiUsers.contains(id)).count();
                if (missingKpi > 0) {
                    blockers.add("存在 " + missingKpi + " 人未配置并锁定KPI");
                }
            }
        }

        if (targetStatus == PeriodStatus.ARCHIVED) {
            long finalScores = scoreMapper.selectCount(
                    new LambdaQueryWrapper<com.yimeng.okr.entity.PerformanceScore>()
                            .eq(com.yimeng.okr.entity.PerformanceScore::getPeriodId, periodId)
                            .eq(com.yimeng.okr.entity.PerformanceScore::getType, ScoreType.FINAL));
            if (finalScores == 0) {
                blockers.add("暂无最终评分");
                return;
            }

            long unconfirmedScores = scoreMapper.selectCount(
                    new LambdaQueryWrapper<com.yimeng.okr.entity.PerformanceScore>()
                            .eq(com.yimeng.okr.entity.PerformanceScore::getPeriodId, periodId)
                            .eq(com.yimeng.okr.entity.PerformanceScore::getType, ScoreType.FINAL)
                            .isNull(com.yimeng.okr.entity.PerformanceScore::getConfirmedAt));
            if (unconfirmedScores > 0) {
                blockers.add("存在 " + unconfirmedScores + " 条未确认最终评分");
            }

            long unpublishedScores = scoreMapper.selectCount(
                    new LambdaQueryWrapper<PerformanceScore>()
                            .eq(PerformanceScore::getPeriodId, periodId)
                            .eq(PerformanceScore::getType, ScoreType.FINAL)
                            .ne(PerformanceScore::getStatus, "PUBLISHED"));
            if (unpublishedScores > 0) {
                blockers.add("存在 " + unpublishedScores + " 条未发布最终评分");
            }

            List<Long> participantIds = getParticipantIds(periodId, true);
            if (!participantIds.isEmpty()) {
                Set<Long> selfScoreUsers = userIdsFromScores(scoreMapper.selectList(
                        new LambdaQueryWrapper<PerformanceScore>()
                                .eq(PerformanceScore::getPeriodId, periodId)
                                .eq(PerformanceScore::getType, ScoreType.SELF)
                                .in(PerformanceScore::getUserId, participantIds)));
                long missingSelf = participantIds.stream().filter(id -> !selfScoreUsers.contains(id)).count();
                if (missingSelf > 0) {
                    blockers.add("存在 " + missingSelf + " 人未完成自评");
                }

                Set<Long> superiorScoreUsers = userIdsFromScores(scoreMapper.selectList(
                        new LambdaQueryWrapper<PerformanceScore>()
                                .eq(PerformanceScore::getPeriodId, periodId)
                                .eq(PerformanceScore::getType, ScoreType.SUPERIOR)
                                .in(PerformanceScore::getUserId, participantIds)));
                long missingSuperior = participantIds.stream().filter(id -> !superiorScoreUsers.contains(id)).count();
                if (missingSuperior > 0) {
                    blockers.add("存在 " + missingSuperior + " 人未完成上级评分");
                }

                Set<Long> publishedFinalUsers = userIdsFromScores(scoreMapper.selectList(
                        new LambdaQueryWrapper<PerformanceScore>()
                                .eq(PerformanceScore::getPeriodId, periodId)
                                .eq(PerformanceScore::getType, ScoreType.FINAL)
                                .eq(PerformanceScore::getStatus, "PUBLISHED")
                                .in(PerformanceScore::getUserId, participantIds)));
                long missingPublishedFinal = participantIds.stream().filter(id -> !publishedFinalUsers.contains(id)).count();
                if (missingPublishedFinal > 0) {
                    blockers.add("存在 " + missingPublishedFinal + " 人最终评分未发布");
                }

                Set<Long> feedbackUsers = userIdsFromFeedback(feedbackMapper.selectList(
                        new LambdaQueryWrapper<PerformanceFeedback>()
                                .eq(PerformanceFeedback::getPeriodId, periodId)
                                .eq(PerformanceFeedback::getStatus, "CLOSED")
                                .in(PerformanceFeedback::getUserId, participantIds)));
                long missingFeedback = participantIds.stream().filter(id -> !feedbackUsers.contains(id)).count();
                if (missingFeedback > 0) {
                    blockers.add("存在 " + missingFeedback + " 人未完成面谈反馈");
                }
            }
        }
    }

    private List<Long> getParticipantIds(Long periodId, boolean includeProcessData) {
        List<PeriodParticipant> participants = participantMapper.selectList(
                new LambdaQueryWrapper<PeriodParticipant>().eq(PeriodParticipant::getPeriodId, periodId));
        if (participants != null && !participants.isEmpty()) {
            return participants.stream()
                    .filter(p -> "ACTIVE".equals(p.getStatus()))
                    .map(PeriodParticipant::getUserId)
                    .filter(Objects::nonNull)
                    .toList();
        }

        Set<Long> ids = new HashSet<>();
        addAll(ids, userIdsFromObjectives(objectiveMapper.selectList(
                new LambdaQueryWrapper<OkrObjective>().eq(OkrObjective::getPeriodId, periodId))));
        addAll(ids, userIdsFromKpiConfigs(kpiConfigMapper.selectList(
                new LambdaQueryWrapper<KpiConfig>().eq(KpiConfig::getPeriodId, periodId))));
        if (includeProcessData) {
            addAll(ids, userIdsFromScores(scoreMapper.selectList(
                    new LambdaQueryWrapper<PerformanceScore>().eq(PerformanceScore::getPeriodId, periodId))));
            addAll(ids, userIdsFromFeedback(feedbackMapper.selectList(
                    new LambdaQueryWrapper<PerformanceFeedback>().eq(PerformanceFeedback::getPeriodId, periodId))));
        }
        return ids.stream().toList();
    }

    private void addAll(Set<Long> target, Set<Long> source) {
        target.addAll(source);
    }

    private Set<Long> userIdsFromObjectives(List<OkrObjective> objectives) {
        Set<Long> ids = new HashSet<>();
        if (objectives != null) {
            objectives.stream().map(OkrObjective::getUserId).filter(Objects::nonNull).forEach(ids::add);
        }
        return ids;
    }

    private Set<Long> userIdsFromKpiConfigs(List<KpiConfig> configs) {
        Set<Long> ids = new HashSet<>();
        if (configs != null) {
            configs.stream().map(KpiConfig::getUserId).filter(Objects::nonNull).forEach(ids::add);
        }
        return ids;
    }

    private Set<Long> userIdsFromScores(List<PerformanceScore> scores) {
        Set<Long> ids = new HashSet<>();
        if (scores != null) {
            scores.stream().map(PerformanceScore::getUserId).filter(Objects::nonNull).forEach(ids::add);
        }
        return ids;
    }

    private Set<Long> userIdsFromFeedback(List<PerformanceFeedback> feedbacks) {
        Set<Long> ids = new HashSet<>();
        if (feedbacks != null) {
            feedbacks.stream().map(PerformanceFeedback::getUserId).filter(Objects::nonNull).forEach(ids::add);
        }
        return ids;
    }

    private void notifyPeriodTransition(AssessmentPeriod period, PeriodStatus target) {
        String title;
        String content;
        switch (target) {
            case OKR_ALIGN -> {
                title = "考核周期已发布";
                content = "「" + period.getName() + "」已发布，请进入 OKR 对齐阶段，制定目标并完成对齐";
            }
            case EXECUTING -> {
                title = "OKR 已锁定，开始执行";
                content = "「" + period.getName() + "」OKR 已锁定，进入执行追踪阶段，请定期更新 KR 进度";
            }
            case ASSESSING -> {
                title = "考评阶段已开始";
                content = "「" + period.getName() + "」进入考评阶段，请前往「我的绩效」完成自评";
            }
            case ARCHIVED -> {
                title = "考核周期已归档";
                content = "「" + period.getName() + "」已完成并归档，可在历史绩效中查看结果";
            }
            default -> { return; }
        }
        List<SysUser> users = userMapper.selectList(null);
        for (SysUser u : users) {
            publisher.publishEvent(new NotificationEvent(
                    this, u.getId(), "PERIOD_" + target.name(),
                    title, content, "period", period.getId()));
        }
    }

    public void delete(Long id) {
        AssessmentPeriod period = getById(id);
        if (period.getStatus() != PeriodStatus.DRAFT) {
            throw new BusinessException("仅可删除待发布状态的周期");
        }
        periodMapper.deleteById(id);
    }

    @Transactional
    public PeriodParticipant exemptParticipant(Long periodId, Long userId, Long operatorId, String reason) {
        if (reason == null || reason.trim().isEmpty()) {
            throw new BusinessException("豁免原因不能为空");
        }
        PeriodParticipant participant = participantMapper.selectOne(
                new LambdaQueryWrapper<PeriodParticipant>()
                        .eq(PeriodParticipant::getPeriodId, periodId)
                        .eq(PeriodParticipant::getUserId, userId));
        if (participant == null) {
            participant = new PeriodParticipant();
            participant.setPeriodId(periodId);
            participant.setUserId(userId);
            participant.setStatus("EXEMPT");
            participant.setReason(reason.trim());
            participant.setOperatedBy(operatorId);
            participant.setExemptedAt(java.time.LocalDateTime.now());
            participantMapper.insert(participant);
        } else {
            participant.setStatus("EXEMPT");
            participant.setReason(reason.trim());
            participant.setOperatedBy(operatorId);
            participant.setExemptedAt(java.time.LocalDateTime.now());
            participantMapper.updateById(participant);
        }
        return participant;
    }

    public List<PeriodParticipant> listParticipants(Long periodId) {
        return participantMapper.selectList(
                new LambdaQueryWrapper<PeriodParticipant>()
                        .eq(PeriodParticipant::getPeriodId, periodId)
                        .orderByAsc(PeriodParticipant::getUserId));
    }

    @Transactional
    public PeriodParticipant addParticipant(Long periodId, Long userId) {
        SysUser user = userMapper.selectById(userId);
        if (user == null) {
            throw new BusinessException(404, "用户不存在");
        }
        if ("DISABLED".equals(user.getStatus()) || "LEFT".equals(user.getStatus())) {
            throw new BusinessException("禁用或离职用户不能加入考核周期");
        }
        PeriodParticipant existing = participantMapper.selectOne(
                new LambdaQueryWrapper<PeriodParticipant>()
                        .eq(PeriodParticipant::getPeriodId, periodId)
                        .eq(PeriodParticipant::getUserId, userId));
        if (existing != null) {
            existing.setStatus("ACTIVE");
            existing.setReason(null);
            existing.setExemptedAt(null);
            participantMapper.updateById(existing);
            return existing;
        }
        PeriodParticipant participant = new PeriodParticipant();
        participant.setPeriodId(periodId);
        participant.setUserId(userId);
        participant.setStatus("ACTIVE");
        participantMapper.insert(participant);
        return participant;
    }

    @Transactional
    public PeriodParticipant activateParticipant(Long periodId, Long userId) {
        PeriodParticipant participant = participantMapper.selectOne(
                new LambdaQueryWrapper<PeriodParticipant>()
                        .eq(PeriodParticipant::getPeriodId, periodId)
                        .eq(PeriodParticipant::getUserId, userId));
        if (participant == null) {
            throw new BusinessException(404, "周期参与人不存在");
        }
        participant.setStatus("ACTIVE");
        participant.setReason(null);
        participant.setOperatedBy(null);
        participant.setExemptedAt(null);
        participantMapper.updateById(participant);
        return participant;
    }

    public AssessmentPeriod getActivePeriod() {
        LocalDate today = LocalDate.now();
        return periodMapper.selectOne(
                new LambdaQueryWrapper<AssessmentPeriod>()
                        .le(AssessmentPeriod::getStartDate, today)
                        .ge(AssessmentPeriod::getEndDate, today)
                        .ne(AssessmentPeriod::getStatus, PeriodStatus.ARCHIVED)
                        .ne(AssessmentPeriod::getStatus, PeriodStatus.DRAFT)
                        .orderByDesc(AssessmentPeriod::getCreatedAt)
                        .last("LIMIT 1"));
    }

    @Transactional
    public List<AssessmentPeriod> batchCreate(Map<String, Object> body) {
        String namePrefix = body.getOrDefault("namePrefix", "周期").toString();
        String typeStr = body.get("periodType").toString();
        PeriodType type = PeriodType.valueOf(typeStr);
        String startDateStr = body.get("startDate").toString();
        LocalDate start = LocalDate.parse(startDateStr);

        List<AssessmentPeriod> result = new ArrayList<>();
        LocalDate cursor = start;
        for (int i = 0; i < 6; i++) {
            AssessmentPeriod p = new AssessmentPeriod();
            p.setName(namePrefix + (i + 1));
            p.setPeriodType(type);
            p.setStartDate(cursor);
            p.setEndDate(type.calculateEndDate(cursor));
            p.setStatus(PeriodStatus.DRAFT);
            periodMapper.insert(p);
            result.add(p);
            cursor = p.getEndDate().plusDays(1);
        }
        return result;
    }

    @Transactional
    public void checkAndAutoAdvance() {
        LocalDate today = LocalDate.now();
        List<AssessmentPeriod> executing = periodMapper.selectList(
                new LambdaQueryWrapper<AssessmentPeriod>()
                        .eq(AssessmentPeriod::getStatus, PeriodStatus.EXECUTING));
        for (AssessmentPeriod p : executing) {
            if (p.getEndDate() != null && p.getEndDate().isBefore(today)) {
                p.setStatus(PeriodStatus.ASSESSING);
                periodMapper.updateById(p);
                log.info("Auto transition period {} to ASSESSING", p.getId());
                notifyPeriodTransition(p, PeriodStatus.ASSESSING);
            }
        }
    }

    private boolean isValidTransition(PeriodStatus from, PeriodStatus to) {
        return switch (from) {
            case DRAFT -> to == PeriodStatus.OKR_ALIGN;
            case OKR_ALIGN -> to == PeriodStatus.EXECUTING;
            case EXECUTING -> to == PeriodStatus.ASSESSING || to == PeriodStatus.OKR_ALIGN || to == PeriodStatus.ARCHIVED;
            case ASSESSING -> to == PeriodStatus.ARCHIVED;
            case ARCHIVED -> false;
        };
    }

    private void createParticipantSnapshot(Long periodId) {
        long existing = participantMapper.selectCount(
                new LambdaQueryWrapper<PeriodParticipant>().eq(PeriodParticipant::getPeriodId, periodId));
        if (existing > 0) return;
        List<SysUser> users = userMapper.selectList(
                new LambdaQueryWrapper<SysUser>()
                        .isNotNull(SysUser::getDepartmentId)
                        .and(w -> w.isNull(SysUser::getStatus).or().eq(SysUser::getStatus, "ACTIVE"))
                        .isNull(SysUser::getLeftAt));
        if (users == null) return;
        for (SysUser user : users) {
            if ("DISABLED".equals(user.getStatus()) || "LEFT".equals(user.getStatus()) || user.getLeftAt() != null) {
                continue;
            }
            PeriodParticipant participant = new PeriodParticipant();
            participant.setPeriodId(periodId);
            participant.setUserId(user.getId());
            participant.setStatus("ACTIVE");
            participantMapper.insert(participant);
        }
    }

    private void snapshotPeriod(Long periodId) {
        List<OkrObjective> objectives = objectiveMapper.selectList(
                new LambdaQueryWrapper<OkrObjective>().eq(OkrObjective::getPeriodId, periodId));
        for (OkrObjective objective : objectives) {
            List<KrKeyResult> krs = krMapper.selectList(
                    new LambdaQueryWrapper<KrKeyResult>().eq(KrKeyResult::getObjectiveId, objective.getId()));
            objective.setSnapshotJson(toJson(Map.of(
                    "objective", objective,
                    "keyResults", krs
            )));
            objectiveMapper.updateById(objective);
        }

        List<KpiConfig> configs = kpiConfigMapper.selectList(
                new LambdaQueryWrapper<KpiConfig>().eq(KpiConfig::getPeriodId, periodId));
        for (KpiConfig config : configs) {
            config.setSnapshotJson(toJson(config));
            kpiConfigMapper.updateById(config);
        }
    }

    private String toJson(Object value) {
        try {
            return objectMapper.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            throw new BusinessException("归档快照生成失败");
        }
    }
}
