package com.yimeng.okr.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yimeng.okr.dto.OkrDetailDto;
import com.yimeng.okr.dto.ScoringDto;
import com.yimeng.okr.dto.UserInfo;
import com.yimeng.okr.entity.*;
import com.yimeng.okr.enums.Grade;
import com.yimeng.okr.enums.ScoreType;
import com.yimeng.okr.event.NotificationEvent;
import com.yimeng.okr.exception.BusinessException;
import com.yimeng.okr.mapper.*;
import com.yimeng.okr.entity.PerformanceScoreItem;
import java.time.LocalDateTime;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.util.List;
import java.util.Locale;
import java.util.Set;

@Slf4j
@Service
@RequiredArgsConstructor
public class PerformanceService {

    private static final String STATUS_SELF_SUBMITTED = "SELF_SUBMITTED";
    private static final String STATUS_SUPERIOR_SUBMITTED = "SUPERIOR_SUBMITTED";
    private static final String STATUS_PENDING_PUBLISH = "PENDING_PUBLISH";
    private static final String STATUS_PUBLISHED = "PUBLISHED";

    private final PerformanceScoreMapper scoreMapper;
    private final PerformanceFeedbackMapper feedbackMapper;
    private final AssessmentPeriodMapper periodMapper;
    private final SysUserMapper userMapper;
    private final PerformanceScoreItemMapper scoreItemMapper;
    private final AuditCorrectionMapper auditCorrectionMapper;
    private final OkrService okrService;
    private final AuthorizationService authorizationService;
    private final ApplicationEventPublisher publisher;
    private final ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();

    // Self evaluation

    @Transactional
    public PerformanceScore submitSelfScore(Long periodId, Long userId, Double okrScore, Double kpiScore,
                                            Double bonus, Double penalty, String commentsJson) {
        AssessmentPeriod period = checkPeriodStatus(periodId);
        validateScoreParts(okrScore, kpiScore, bonus, penalty);
        SysUser actor = userMapper.selectById(userId);
        ensureActiveUser(actor, "自评人");

        PerformanceScore existing = findExisting(periodId, userId, ScoreType.SELF);
        if (existing != null) {
            throw new BusinessException("已提交自评，不可重复提交");
        }

        PerformanceScore score = new PerformanceScore();
        score.setPeriodId(periodId);
        score.setUserId(userId);
        score.setEvaluatorId(userId);
        score.setOkrScore(okrScore);
        score.setKpiScore(kpiScore);
        score.setBonus(bonus != null ? bonus : 0);
        score.setPenalty(penalty != null ? penalty : 0);
        score.setTotal(calculateTotal(okrScore, kpiScore, bonus, penalty));
        score.setGrade(Grade.fromScore(score.getTotal()));
        score.setType(ScoreType.SELF);
        score.setStatus(STATUS_SELF_SUBMITTED);
        score.setCommentsJson(commentsJson);
        scoreMapper.insert(score);
        writeScoreItems(score.getId(), commentsJson);
        return score;
    }

    // Superior scoring

    @Transactional
    public PerformanceScore submitSuperiorScore(Long periodId, Long userId, Long evaluatorId,
                                                 Double okrScore, Double kpiScore,
                                                 Double bonus, Double penalty, String commentsJson) {
        AssessmentPeriod period = checkPeriodStatus(periodId);
        validateScoreParts(okrScore, kpiScore, bonus, penalty);

        // Permission: evaluator must be the direct superior
        SysUser targetUser = userMapper.selectById(userId);
        if (targetUser == null) throw new BusinessException(404, "用户不存在");
        ensureActiveUser(targetUser, "被评分人");
        SysUser evaluator = userMapper.selectById(evaluatorId);
        ensureActiveUser(evaluator, "评分人");
        if (!evaluatorId.equals(targetUser.getSuperiorId())) {
            throw new BusinessException("你无权对此用户评分");
        }

        PerformanceScore selfScore = findExisting(periodId, userId, ScoreType.SELF);
        if (selfScore == null) {
            throw new BusinessException("员工尚未提交自评，暂不能进行上级评分");
        }

        PerformanceScore existing = findExisting(periodId, userId, ScoreType.SUPERIOR);
        if (existing != null) {
            throw new BusinessException("已提交上级评分，不可重复评分");
        }

        double total = calculateTotal(okrScore, kpiScore, bonus, penalty);
        Grade grade = Grade.fromScore(total);

        PerformanceScore score = new PerformanceScore();
        score.setPeriodId(periodId);
        score.setUserId(userId);
        score.setEvaluatorId(evaluatorId);
        score.setOkrScore(okrScore);
        score.setKpiScore(kpiScore);
        score.setBonus(bonus != null ? bonus : 0);
        score.setPenalty(penalty != null ? penalty : 0);
        score.setTotal(total);
        score.setGrade(grade);
        score.setType(ScoreType.SUPERIOR);
        score.setStatus(STATUS_SUPERIOR_SUBMITTED);
        score.setCommentsJson(commentsJson);
        scoreMapper.insert(score);
        writeScoreItems(score.getId(), commentsJson);

        // Also create a FINAL record
        PerformanceScore finalScore = new PerformanceScore();
        finalScore.setPeriodId(periodId);
        finalScore.setUserId(userId);
        finalScore.setEvaluatorId(evaluatorId);
        finalScore.setOkrScore(okrScore);
        finalScore.setKpiScore(kpiScore);
        finalScore.setBonus(bonus != null ? bonus : 0);
        finalScore.setPenalty(penalty != null ? penalty : 0);
        finalScore.setTotal(total);
        finalScore.setGrade(grade);
        finalScore.setType(ScoreType.FINAL);
        finalScore.setStatus(STATUS_PENDING_PUBLISH);
        finalScore.setCommentsJson(commentsJson);
        scoreMapper.insert(finalScore);
        writeScoreItems(finalScore.getId(), commentsJson);

        return score;
    }

    // Queries with OKR records (same-screen display)

    public ScoringDto getScoringPage(Long periodId, Long userId, Long actorId) {
        authorizationService.requireCanViewUserData(actorId, userId);
        AssessmentPeriod period = periodMapper.selectById(periodId);
        if (period == null) throw new BusinessException(404, "考核周期不存在");
        SysUser user = userMapper.selectById(userId);
        if (user == null) throw new BusinessException(404, "用户不存在");

        ScoringDto dto = new ScoringDto();

        ScoringDto.PeriodInfo periodInfo = new ScoringDto.PeriodInfo();
        periodInfo.setId(period.getId());
        periodInfo.setName(period.getName());
        periodInfo.setStartDate(period.getStartDate().toString());
        periodInfo.setEndDate(period.getEndDate().toString());
        periodInfo.setStatus(period.getStatus().name());
        dto.setPeriod(periodInfo);

        dto.setTargetUser(UserInfo.from(user));

        // Core: include full OKR records
        OkrDetailDto myOkr = okrService.getMyOkr(periodId, userId);
        dto.setOkrRecords(myOkr != null ? List.of(myOkr) : List.of());

        dto.setSelfScore(findExisting(periodId, userId, ScoreType.SELF));
        dto.setSuperiorScore(findExisting(periodId, userId, ScoreType.SUPERIOR));
        dto.setFinalScore(findExisting(periodId, userId, ScoreType.FINAL));

        if (dto.getSelfScore() != null) {
            dto.setSelfItems(queryScoreItems(dto.getSelfScore().getId()));
        }
        if (dto.getSuperiorScore() != null) {
            dto.setSuperiorItems(queryScoreItems(dto.getSuperiorScore().getId()));
        }
        if (dto.getFinalScore() != null) {
            dto.setFinalItems(queryScoreItems(dto.getFinalScore().getId()));
        }

        dto.setFeedback(feedbackMapper.selectOne(
                new LambdaQueryWrapper<PerformanceFeedback>()
                        .eq(PerformanceFeedback::getPeriodId, periodId)
                        .eq(PerformanceFeedback::getUserId, userId)));

        // Privacy masking: restrict sensitive fields by viewer role
        applyPrivacyMask(dto, actorId, userId, user);

        return dto;
    }

    /**
     * 按查看者角色裁剪敏感字段，防止绩效数据过度暴露。
     * - 本人 / HR / 超管：可查看全部
     * - 直属上级：可查看总分，但屏蔽自评详细内容
     * - 其他（防御性）：仅返回最终总分
     */
    private void applyPrivacyMask(ScoringDto dto, Long actorId, Long targetUserId, SysUser targetUser) {
        boolean isSelf = actorId.equals(targetUserId);
        boolean isHrOrAbove = authorizationService.isHrOrAbove(actorId);
        boolean isSuperior = targetUser.getSuperiorId() != null
                && targetUser.getSuperiorId().equals(actorId);

        if (isSelf && dto.getFinalScore() != null && !isPublished(dto.getFinalScore())) {
            // 本人查看未发布的终评：隐藏终评内容
            dto.setFinalScore(null);
            dto.setFinalItems(null);
        }
        if (isSelf || isHrOrAbove) {
            return; // 本人和HR/超管：不额外裁剪
        }

        if (isSuperior) {
            // 直属上级：屏蔽自评详细内容
            if (dto.getSelfScore() != null) {
                dto.getSelfScore().setCommentsJson(null);
                dto.setSelfItems(null);
            }
            return;
        }

        // 防御性裁剪：仅保留最终评分的总分和等级
        clearScoreFields(dto.getSelfScore());
        dto.setSelfItems(null);
        clearScoreFields(dto.getSuperiorScore());
        dto.setSuperiorItems(null);
        if (dto.getFinalScore() != null) {
            dto.getFinalScore().setCommentsJson(null);
        }
        dto.setFinalItems(null);
        dto.setFeedback(null);
    }

    private void clearScoreFields(PerformanceScore score) {
        if (score != null) {
            score.setCommentsJson(null);
            score.setOkrScore(null);
            score.setKpiScore(null);
        }
    }

    public List<ScoringDto> getSubordinateScores(Long periodId, Long superiorId) {
        List<SysUser> subs = userMapper.selectList(
                new LambdaQueryWrapper<SysUser>().eq(SysUser::getSuperiorId, superiorId));
        return subs.stream().map(sub -> getScoringPage(periodId, sub.getId(), superiorId)).toList();
    }

    public ScoringDto getMyResult(Long periodId, Long userId) {
        return getScoringPage(periodId, userId, userId);
    }

    @Transactional
    public PerformanceScore correctFinalScore(Long scoreId, Long operatorId, Double okrScore, Double kpiScore,
                                              Double bonus, Double penalty, String commentsJson, String reason) {
        if (!authorizationService.isHrOrAbove(operatorId)) {
            throw new BusinessException(403, "无权限，需要HR管理员或以上权限");
        }
        if (reason == null || reason.trim().isEmpty()) {
            throw new BusinessException("修正原因不能为空");
        }
        validateScoreParts(okrScore, kpiScore, bonus, penalty);
        PerformanceScore score = scoreMapper.selectById(scoreId);
        if (score == null) throw new BusinessException(404, "绩效结果不存在");
        if (score.getType() != ScoreType.FINAL) {
            throw new BusinessException("仅最终绩效结果允许审计修正");
        }
        AuditCorrection audit = new AuditCorrection();
        audit.setPerformanceId(scoreId);
        audit.setCorrectionType("FINAL_SCORE");
        audit.setOldValuesJson(toJson(score));

        score.setOkrScore(okrScore);
        score.setKpiScore(kpiScore);
        score.setBonus(bonus != null ? bonus : 0);
        score.setPenalty(penalty != null ? penalty : 0);
        score.setTotal(calculateTotal(okrScore, kpiScore, bonus, penalty));
        score.setGrade(Grade.fromScore(score.getTotal()));
        score.setCommentsJson(commentsJson);
        score.setStatus(STATUS_PENDING_PUBLISH);
        score.setPublishedAt(null);
        score.setConfirmedAt(null);

        audit.setNewValuesJson(toJson(score));
        audit.setReason(reason.trim());
        audit.setOperatedBy(operatorId);
        auditCorrectionMapper.insert(audit);
        scoreMapper.updateById(score);
        return score;
    }

    @Transactional
    public PerformanceScore publishFinalScore(Long periodId, Long userId, Long operatorId) {
        authorizationService.requireCanManageUserData(operatorId, userId);
        PerformanceScore score = findExisting(periodId, userId, ScoreType.FINAL);
        if (score == null) {
            throw new BusinessException("暂无最终绩效结果可发布");
        }
        if (isPublished(score)) {
            throw new BusinessException("绩效结果已发布，无需重复发布");
        }
        if (score.getConfirmedAt() != null) {
            throw new BusinessException("绩效结果已确认，不可重复发布");
        }
        score.setStatus(STATUS_PUBLISHED);
        score.setPublishedAt(LocalDateTime.now());
        scoreMapper.updateById(score);

        publisher.publishEvent(new NotificationEvent(
                this, userId, "SCORE_CONFIRM",
                "绩效结果已发布", "你的本周期绩效结果已发布，请查看并确认",
                "score", score.getId()));
        return score;
    }

    @Transactional
    public void confirmScore(Long periodId, Long userId) {
        PerformanceScore score = scoreMapper.selectOne(
                new LambdaQueryWrapper<PerformanceScore>()
                        .eq(PerformanceScore::getPeriodId, periodId)
                        .eq(PerformanceScore::getUserId, userId)
                        .eq(PerformanceScore::getType, ScoreType.FINAL));
        if (score == null) throw new BusinessException("暂无绩效结果可供确认");
        if (!isPublished(score)) throw new BusinessException("绩效结果尚未发布，暂不能确认");
        if (score.getConfirmedAt() != null) throw new BusinessException("已确认过，无需重复确认");
        score.setConfirmedAt(LocalDateTime.now());
        scoreMapper.updateById(score);

        SysUser user = userMapper.selectById(userId);
        if (user != null && user.getSuperiorId() != null) {
            publisher.publishEvent(new NotificationEvent(
                    this, user.getSuperiorId(), "SCORE_CONFIRMED",
                    "评分已确认", user.getRealName() + " 已确认绩效评分",
                    "score", score.getId()));
        }
    }

    // Helpers

    private double calculateTotal(Double okrScore, Double kpiScore, Double bonus, Double penalty) {
        double o = okrScore != null ? okrScore : 0;
        double k = kpiScore != null ? kpiScore : 0;
        double b = bonus != null ? bonus : 0;
        double p = penalty != null ? penalty : 0;
        double raw = o + k + b - p;
        return Math.round(raw * 10.0) / 10.0;
    }

    private void validateScoreParts(Double okrScore, Double kpiScore, Double bonus, Double penalty) {
        requireRange("OKR得分", okrScore, 0, 60);
        requireRange("绩效维度得分", kpiScore, 0, 40);
        requireRange("加分", bonus != null ? bonus : 0, 0, 20);
        requireRange("扣分", penalty != null ? penalty : 0, 0, 20);
        double total = calculateTotal(okrScore, kpiScore, bonus, penalty);
        requireRange("总分", total, 0, 100);
    }

    private void requireRange(String label, Double value, double min, double max) {
        double v = value != null ? value : 0;
        if (v < min || v > max) {
            throw new BusinessException(label + "必须在" + min + "~" + max + "之间");
        }
    }



    public List<ScoringDto> getMyHistory(Long userId) {
        var scores = scoreMapper.selectList(
            new LambdaQueryWrapper<PerformanceScore>()
                .eq(PerformanceScore::getUserId, userId)
                .eq(PerformanceScore::getType, ScoreType.FINAL)
                .in(PerformanceScore::getStatus, java.util.List.of("PUBLISHED", "CONFIRMED"))
                .orderByDesc(PerformanceScore::getCreatedAt)
                .last("LIMIT 10"));
        return scores.stream().map(s -> {
            ScoringDto dto = new ScoringDto();
            ScoringDto.PeriodInfo pi = new ScoringDto.PeriodInfo();
            AssessmentPeriod p = periodMapper.selectById(s.getPeriodId());
            if (p != null) {
                pi.setId(p.getId());
                pi.setName(p.getName());
                pi.setStatus(p.getStatus().name());
            }
            dto.setPeriod(pi);
            dto.setFinalScore(s);
            return dto;
        }).toList();
    }


    private List<PerformanceScoreItem> queryScoreItems(Long scoreId) {
        return scoreItemMapper.selectList(
            new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<PerformanceScoreItem>()
                .eq(PerformanceScoreItem::getScoreId, scoreId)
                .orderByAsc(PerformanceScoreItem::getSortOrder));
    }

    private void writeScoreItems(Long scoreId, String commentsJson) {
        if (commentsJson == null || commentsJson.isBlank()) return;
        try {
            var node = objectMapper.readTree(commentsJson);
            int sortOrder = 0;
            var krScores = node.get("krScores");
            if (krScores != null && krScores.isArray()) {
                for (var krNode : krScores) {
                    PerformanceScoreItem item = new PerformanceScoreItem();
                    item.setScoreId(scoreId);
                    item.setItemType("KR");
                    item.setReferenceId(krNode.has("krId") ? krNode.get("krId").asLong() : null);
                    item.setName(krNode.has("title") ? krNode.get("title").asText("") : "");
                    item.setMaxScore(1.0);
                    item.setScore(krNode.has("score") ? krNode.get("score").asDouble() : 0);
                    item.setSortOrder(sortOrder++);
                    scoreItemMapper.insert(item);
                }
            }
            var dimensions = node.get("dimensions");
            if (dimensions != null && dimensions.isArray()) {
                for (var dimNode : dimensions) {
                    PerformanceScoreItem item = new PerformanceScoreItem();
                    item.setScoreId(scoreId);
                    item.setItemType("DIMENSION");
                    item.setReferenceId(
                        dimNode.has("dimensionId") ? dimNode.get("dimensionId").asLong() :
                        dimNode.has("id") ? dimNode.get("id").asLong() : null);
                    item.setName(dimNode.has("name") ? dimNode.get("name").asText("") : "");
                    item.setMaxScore(dimNode.has("maxScore") ? dimNode.get("maxScore").asDouble() : 0);
                    item.setScore(dimNode.has("score") ? dimNode.get("score").asDouble() : 0);
                    item.setSortOrder(sortOrder++);
                    scoreItemMapper.insert(item);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("结构化评分明细写入失败(scoreId=" + scoreId + "): " + e.getMessage(), e);
        }
    }

    private String toJson(Object value) {
        try {
            return objectMapper.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            throw new BusinessException("审计数据序列化失败");
        }
    }

    private PerformanceScore findExisting(Long periodId, Long userId, ScoreType type) {
        return scoreMapper.selectOne(
                new LambdaQueryWrapper<PerformanceScore>()
                        .eq(PerformanceScore::getPeriodId, periodId)
                        .eq(PerformanceScore::getUserId, userId)
                        .eq(PerformanceScore::getType, type));
    }

    private boolean isPublished(PerformanceScore score) {
        return STATUS_PUBLISHED.equals(score.getStatus());
    }

    private void ensureActiveUser(SysUser user, String label) {
        if (user == null) {
            throw new BusinessException(404, label + "不存在");
        }
        if (Integer.valueOf(1).equals(user.getDeleted())) {
            throw new BusinessException(label + "已删除，不能继续进行绩效操作");
        }
        String status = user.getStatus();
        if (status != null) {
            String normalized = status.trim().toUpperCase(Locale.ROOT);
            if (Set.of("DISABLED", "LEFT", "INACTIVE").contains(normalized)) {
                throw new BusinessException(label + "当前状态不可用，不能继续进行绩效操作");
            }
        }
        if (user.getDisabledAt() != null || user.getLeftAt() != null) {
            throw new BusinessException(label + "当前状态不可用，不能继续进行绩效操作");
        }
    }

    private AssessmentPeriod checkPeriodStatus(Long periodId) {
        AssessmentPeriod period = periodMapper.selectById(periodId);
        if (period == null) throw new BusinessException(404, "考核周期不存在");
        if (!"ASSESSING".equals(period.getStatus().name())) {
            throw new BusinessException("当前周期不在考评阶段");
        }
        return period;
    }
}
