package com.lesingle.modules.biz.contest.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lesingle.common.enums.ErrorCode;
import com.lesingle.common.enums.WorkStatus;
import com.lesingle.common.enums.RegistrationStatus;
import com.lesingle.common.exception.BusinessException;
import com.lesingle.common.result.PageResult;
import com.lesingle.modules.biz.contest.dto.QueryWorkDto;
import com.lesingle.modules.biz.contest.dto.SubmitWorkDto;
import com.lesingle.modules.biz.contest.entity.BizContest;
import com.lesingle.modules.biz.contest.entity.BizContestRegistration;
import com.lesingle.modules.biz.contest.entity.BizContestWork;
import com.lesingle.modules.biz.contest.entity.BizContestWorkAttachment;
import com.lesingle.modules.biz.contest.mapper.ContestMapper;
import com.lesingle.modules.biz.contest.mapper.ContestRegistrationMapper;
import com.lesingle.modules.biz.contest.mapper.ContestWorkAttachmentMapper;
import com.lesingle.modules.biz.contest.mapper.ContestWorkMapper;
import com.lesingle.modules.biz.contest.service.IContestWorkService;
import com.lesingle.modules.biz.review.entity.BizContestJudge;
import com.lesingle.modules.biz.review.entity.BizContestReviewRule;
import com.lesingle.modules.biz.review.entity.BizContestWorkJudgeAssignment;
import com.lesingle.modules.biz.review.entity.BizContestWorkScore;
import com.lesingle.modules.biz.review.mapper.ContestJudgeMapper;
import com.lesingle.modules.biz.review.mapper.ContestReviewRuleMapper;
import com.lesingle.modules.biz.review.mapper.ContestWorkJudgeAssignmentMapper;
import com.lesingle.modules.biz.review.mapper.ContestWorkScoreMapper;
import com.lesingle.modules.biz.review.util.ContestFinalScoreCalculator;
import com.lesingle.modules.sys.entity.SysUser;
import com.lesingle.modules.sys.mapper.SysUserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class ContestWorkServiceImpl extends ServiceImpl<ContestWorkMapper, BizContestWork>
        implements IContestWorkService {

    private final ContestWorkMapper contestWorkMapper;
    private final ContestWorkAttachmentMapper contestWorkAttachmentMapper;
    private final ContestRegistrationMapper contestRegistrationMapper;
    private final ContestMapper contestMapper;
    private final ContestWorkJudgeAssignmentMapper assignmentMapper;
    private final SysUserMapper sysUserMapper;
    private final ContestWorkScoreMapper contestWorkScoreMapper;
    private final ContestReviewRuleMapper contestReviewRuleMapper;
    private final ContestJudgeMapper contestJudgeMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> submitWork(SubmitWorkDto dto, Long tenantId, Long submitterId) {
        log.info("开始提交作品，报名ID：{}，提交者：{}", dto.getRegistrationId(), submitterId);

        // 验证报名存在且已通过
        BizContestRegistration registration = contestRegistrationMapper.selectById(dto.getRegistrationId());
        if (registration == null) {
            throw BusinessException.of(ErrorCode.NOT_FOUND, "报名记录不存在");
        }
        if (!RegistrationStatus.PASSED.getValue().equals(registration.getRegistrationState())) {
            throw BusinessException.of(ErrorCode.BAD_REQUEST, "报名未通过审核，无法提交作品");
        }

        // 校验报名归属：只有报名者本人才能提交作品
        if (registration.getUserId() != null && !registration.getUserId().equals(submitterId)) {
            log.warn("越权提交尝试：报名用户ID={}，提交者ID={}，报名ID={}", registration.getUserId(), submitterId, dto.getRegistrationId());
            throw BusinessException.of(ErrorCode.FORBIDDEN, "无权替他人提交作品");
        }

        Long contestId = registration.getContestId();

        // 查询活动提交规则
        BizContest contest = contestMapper.selectById(contestId);
        String submitRule = contest != null ? contest.getSubmitRule() : "once";

        // 计算版本号
        int version = 1;
        if ("resubmit".equals(submitRule)) {
            // 将旧版本标记为非最新
            LambdaUpdateWrapper<BizContestWork> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(BizContestWork::getRegistrationId, dto.getRegistrationId())
                    .eq(BizContestWork::getIsLatest, true)
                    .set(BizContestWork::getIsLatest, false);
            update(updateWrapper);

            // 查询当前最大版本号
            LambdaQueryWrapper<BizContestWork> versionWrapper = new LambdaQueryWrapper<>();
            versionWrapper.eq(BizContestWork::getRegistrationId, dto.getRegistrationId());
            versionWrapper.orderByDesc(BizContestWork::getVersion);
            versionWrapper.last("LIMIT 1");
            BizContestWork latestWork = getOne(versionWrapper, false);
            if (latestWork != null) {
                version = latestWork.getVersion() + 1;
            }
        } else {
            // once 规则：检查是否已有作品
            LambdaQueryWrapper<BizContestWork> existWrapper = new LambdaQueryWrapper<>();
            existWrapper.eq(BizContestWork::getRegistrationId, dto.getRegistrationId());
            long existCount = count(existWrapper);
            if (existCount > 0) {
                throw BusinessException.of(ErrorCode.CONFLICT, "该报名已提交作品，不允许重复提交");
            }
        }

        // 生成作品编号
        String workNo = nextContestWorkNo(contestId);

        // 创建作品
        BizContestWork work = new BizContestWork();
        work.setTenantId(tenantId);
        work.setContestId(contestId);
        work.setRegistrationId(dto.getRegistrationId());
        work.setWorkNo(workNo);
        work.setTitle(dto.getTitle());
        work.setDescription(dto.getDescription());
        work.setFiles(dto.getFiles());
        work.setVersion(version);
        work.setIsLatest(true);
        work.setStatus(WorkStatus.SUBMITTED.getValue());
        work.setSubmitTime(LocalDateTime.now());
        work.setSubmitterUserId(submitterId);
        work.setSubmitterAccountNo(registration.getAccountNo());
        work.setPreviewUrl(dto.getPreviewUrl());
        work.setPreviewUrls(dto.getPreviewUrls());
        work.setAiModelMeta(dto.getAiModelMeta());

        save(work);
        log.info("作品提交成功，ID：{}，编号：{}", work.getId(), workNo);

        // 保存附件
        if (dto.getAttachments() != null && !dto.getAttachments().isEmpty()) {
            for (SubmitWorkDto.AttachmentItem item : dto.getAttachments()) {
                BizContestWorkAttachment attachment = new BizContestWorkAttachment();
                attachment.setTenantId(tenantId);
                attachment.setContestId(contestId);
                attachment.setWorkId(work.getId());
                attachment.setFileName(item.getFileName());
                attachment.setFileUrl(item.getFileUrl());
                attachment.setFileType(item.getFileType());
                attachment.setSize(item.getSize());
                attachment.setCreator(submitterId != null ? submitterId.intValue() : null);
                attachment.setCreateTime(LocalDateTime.now());
                contestWorkAttachmentMapper.insert(attachment);
            }
            log.info("作品附件保存成功，数量：{}", dto.getAttachments().size());
        }

        return workToMap(work);
    }

    @Override
    public PageResult<Map<String, Object>> findAll(QueryWorkDto dto, Long tenantId, boolean isSuperTenant) {
        log.info("查询作品列表，活动ID：{}，页码：{}", dto.getContestId(), dto.getPage());

        LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();

        if (dto.getContestId() != null) {
            wrapper.eq(BizContestWork::getContestId, dto.getContestId());
        }
        if (StringUtils.hasText(dto.getStatus())) {
            wrapper.eq(BizContestWork::getStatus, dto.getStatus());
        }
        if (StringUtils.hasText(dto.getTitle())) {
            wrapper.like(BizContestWork::getTitle, dto.getTitle());
        }
        if (StringUtils.hasText(dto.getWorkNo())) {
            wrapper.eq(BizContestWork::getWorkNo, dto.getWorkNo());
        }
        if (dto.getRegistrationId() != null) {
            wrapper.eq(BizContestWork::getRegistrationId, dto.getRegistrationId());
        }
        if (dto.getTenantId() != null) {
            wrapper.eq(BizContestWork::getTenantId, dto.getTenantId());
        } else if (!isSuperTenant && tenantId != null) {
            wrapper.eq(BizContestWork::getTenantId, tenantId);
        }
        // username 筛选：对应报名表的 account_no
        if (StringUtils.hasText(dto.getUsername())) {
            LambdaQueryWrapper<BizContestRegistration> userRegWrapper = new LambdaQueryWrapper<>();
            if (dto.getContestId() != null) {
                userRegWrapper.eq(BizContestRegistration::getContestId, dto.getContestId());
            }
            if (dto.getTenantId() != null) {
                userRegWrapper.eq(BizContestRegistration::getTenantId, dto.getTenantId());
            } else if (!isSuperTenant && tenantId != null) {
                userRegWrapper.eq(BizContestRegistration::getTenantId, tenantId);
            }
            userRegWrapper.eq(BizContestRegistration::getValidState, 1);
            userRegWrapper.like(BizContestRegistration::getAccountNo, dto.getUsername());
            List<BizContestRegistration> userRegs = contestRegistrationMapper.selectList(userRegWrapper);
            Set<Long> userRegIds = userRegs.stream()
                    .map(BizContestRegistration::getId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());
            if (!userRegIds.isEmpty()) {
                wrapper.in(BizContestWork::getRegistrationId, userRegIds);
            } else {
                // 没有匹配的报名记录，返回空结果
                wrapper.eq(BizContestWork::getId, -1L);
            }
        }

        // name 筛选：对应报名表的 account_name
        if (StringUtils.hasText(dto.getName())) {
            LambdaQueryWrapper<BizContestRegistration> nameRegWrapper = new LambdaQueryWrapper<>();
            if (dto.getContestId() != null) {
                nameRegWrapper.eq(BizContestRegistration::getContestId, dto.getContestId());
            }
            if (dto.getTenantId() != null) {
                nameRegWrapper.eq(BizContestRegistration::getTenantId, dto.getTenantId());
            } else if (!isSuperTenant && tenantId != null) {
                nameRegWrapper.eq(BizContestRegistration::getTenantId, tenantId);
            }
            nameRegWrapper.eq(BizContestRegistration::getValidState, 1);
            nameRegWrapper.like(BizContestRegistration::getAccountName, dto.getName());
            List<BizContestRegistration> nameRegs = contestRegistrationMapper.selectList(nameRegWrapper);
            Set<Long> nameRegIds = nameRegs.stream()
                    .map(BizContestRegistration::getId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());
            if (!nameRegIds.isEmpty()) {
                wrapper.in(BizContestWork::getRegistrationId, nameRegIds);
            } else {
                wrapper.eq(BizContestWork::getId, -1L);
            }
        }

        Set<Long> keywordRegistrationIds = Collections.emptySet();
        if (StringUtils.hasText(dto.getKeyword())) {
            String keyword = dto.getKeyword();

            // keyword 命中口径：作品编号（work_no）/作者姓名（account_name）/报名账号（account_no）/队伍名称（team_name）
            LambdaQueryWrapper<BizContestRegistration> regWrapper = new LambdaQueryWrapper<>();
            if (dto.getContestId() != null) {
                regWrapper.eq(BizContestRegistration::getContestId, dto.getContestId());
            }
            regWrapper.eq(BizContestRegistration::getValidState, 1);

            if (dto.getTenantId() != null) {
                regWrapper.eq(BizContestRegistration::getTenantId, dto.getTenantId());
            } else if (!isSuperTenant && tenantId != null) {
                regWrapper.eq(BizContestRegistration::getTenantId, tenantId);
            }

            regWrapper.and(w -> w.like(BizContestRegistration::getAccountNo, keyword)
                    .or().like(BizContestRegistration::getAccountName, keyword)
                    .or().like(BizContestRegistration::getTeamName, keyword));

            List<BizContestRegistration> regs = contestRegistrationMapper.selectList(regWrapper);
            keywordRegistrationIds = regs.stream()
                    .map(BizContestRegistration::getId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());

            if (!keywordRegistrationIds.isEmpty()) {
                // 使用 apply 拼接 IN 条件，避免当前 MyBatis-Plus LambdaQueryWrapper 的
                // or().in() 链式泛型推断导致的编译失败。
                String registrationIdIn = keywordRegistrationIds.stream()
                        .map(String::valueOf)
                        .collect(Collectors.joining(","));
                wrapper.and(w -> w.like(BizContestWork::getWorkNo, keyword)
                        .or().apply("registration_id IN (" + registrationIdIn + ")"));
            } else {
                wrapper.and(w -> w.like(BizContestWork::getWorkNo, keyword));
            }
        }
        if (StringUtils.hasText(dto.getSubmitStartTime())) {
            wrapper.ge(BizContestWork::getSubmitTime, parseDateTime(dto.getSubmitStartTime(), true));
        }
        if (StringUtils.hasText(dto.getSubmitEndTime())) {
            wrapper.le(BizContestWork::getSubmitTime, parseDateTime(dto.getSubmitEndTime(), false));
        }

        // assignStatus 筛选：基于分配表判断已分配/未分配
        if (StringUtils.hasText(dto.getAssignStatus()) && dto.getContestId() != null) {
            LambdaQueryWrapper<BizContestWorkJudgeAssignment> assignQueryWrapper = new LambdaQueryWrapper<>();
            assignQueryWrapper.eq(BizContestWorkJudgeAssignment::getContestId, dto.getContestId());
            assignQueryWrapper.select(BizContestWorkJudgeAssignment::getWorkId);
            assignQueryWrapper.groupBy(BizContestWorkJudgeAssignment::getWorkId);
            List<BizContestWorkJudgeAssignment> assignedRecords = assignmentMapper.selectList(assignQueryWrapper);
            Set<Long> assignedWorkIds = assignedRecords.stream()
                    .map(BizContestWorkJudgeAssignment::getWorkId)
                    .collect(Collectors.toSet());

            if ("assigned".equals(dto.getAssignStatus())) {
                if (assignedWorkIds.isEmpty()) {
                    wrapper.eq(BizContestWork::getId, -1L);
                } else {
                    wrapper.in(BizContestWork::getId, assignedWorkIds);
                }
            } else if ("unassigned".equals(dto.getAssignStatus())) {
                if (!assignedWorkIds.isEmpty()) {
                    wrapper.notIn(BizContestWork::getId, assignedWorkIds);
                }
            }
        }

        // 默认只查最新版本
        wrapper.eq(BizContestWork::getIsLatest, true);
        wrapper.eq(BizContestWork::getValidState, 1);
        wrapper.orderByDesc(BizContestWork::getSubmitTime);

        Page<BizContestWork> page = new Page<>(dto.getPage(), dto.getPageSize());
        Page<BizContestWork> result = contestWorkMapper.selectPage(page, wrapper);

        // 批量查询报名/活动信息
        Set<Long> registrationIds = result.getRecords().stream()
                .map(BizContestWork::getRegistrationId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        Set<Long> contestIds = result.getRecords().stream()
                .map(BizContestWork::getContestId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        Map<Long, BizContestRegistration> registrationMap = new HashMap<>();
        if (!registrationIds.isEmpty()) {
            List<BizContestRegistration> registrations = contestRegistrationMapper.selectBatchIds(registrationIds);
            registrationMap = registrations.stream()
                    .collect(Collectors.toMap(BizContestRegistration::getId, r -> r));
        }

        Map<Long, BizContest> contestMap = new HashMap<>();
        if (!contestIds.isEmpty()) {
            List<BizContest> contests = contestMapper.selectBatchIds(contestIds);
            contestMap = contests.stream()
                    .collect(Collectors.toMap(BizContest::getId, c -> c));
        }

        // 批量查询分配信息
        Set<Long> workIds = result.getRecords().stream()
                .map(BizContestWork::getId)
                .collect(Collectors.toSet());

        Map<Long, List<BizContestWorkJudgeAssignment>> assignmentMap = new HashMap<>();
        Map<Long, String> judgeNameMap = new HashMap<>();
        if (!workIds.isEmpty()) {
            LambdaQueryWrapper<BizContestWorkJudgeAssignment> assignWrapper = new LambdaQueryWrapper<>();
            assignWrapper.in(BizContestWorkJudgeAssignment::getWorkId, workIds);
            if (dto.getContestId() != null) {
                assignWrapper.eq(BizContestWorkJudgeAssignment::getContestId, dto.getContestId());
            }
            List<BizContestWorkJudgeAssignment> allAssignments = assignmentMapper.selectList(assignWrapper);
            assignmentMap = allAssignments.stream()
                    .collect(Collectors.groupingBy(BizContestWorkJudgeAssignment::getWorkId));

            // 批量查询评委用户信息
            Set<Long> judgeIds = allAssignments.stream()
                    .map(BizContestWorkJudgeAssignment::getJudgeId)
                    .collect(Collectors.toSet());
            if (!judgeIds.isEmpty()) {
                List<SysUser> judgeUsers = sysUserMapper.selectBatchIds(judgeIds);
                for (SysUser u : judgeUsers) {
                    judgeNameMap.put(u.getId(), u.getNickname());
                }
            }
        }

        Map<Long, BizContestRegistration> finalRegistrationMap = registrationMap;
        Map<Long, BizContest> finalContestMap = contestMap;
        Map<Long, List<BizContestWorkJudgeAssignment>> finalAssignmentMap = assignmentMap;
        Map<Long, String> finalJudgeNameMap = judgeNameMap;

        // 批量评分：列表「评委评分」与详情抽屉一致；作品表 final_score 未落库时按评审规则从评分表回算
        Map<Long, List<BizContestWorkScore>> scoresByWorkId = new HashMap<>();
        if (!workIds.isEmpty()) {
            LambdaQueryWrapper<BizContestWorkScore> scoreWrapper = new LambdaQueryWrapper<>();
            scoreWrapper.in(BizContestWorkScore::getWorkId, workIds);
            scoreWrapper.eq(BizContestWorkScore::getValidState, 1);
            List<BizContestWorkScore> allPageScores = contestWorkScoreMapper.selectList(scoreWrapper);
            scoresByWorkId = allPageScores.stream()
                    .collect(Collectors.groupingBy(BizContestWorkScore::getWorkId));
        }

        Map<Long, String> contestCalculationRuleCache = new HashMap<>();
        Map<Long, Map<Long, BigDecimal>> contestWeightMapCache = new HashMap<>();

        // 批量预加载评审规则，避免 N+1 查询
        Set<Long> ruleIds = contestIds.stream()
                .map(cid -> finalContestMap.get(cid))
                .filter(c -> c != null && c.getReviewRuleId() != null)
                .map(BizContest::getReviewRuleId)
                .collect(Collectors.toSet());
        Map<Long, BizContestReviewRule> ruleMap = new HashMap<>();
        if (!ruleIds.isEmpty()) {
            LambdaQueryWrapper<BizContestReviewRule> ruleWrapper = new LambdaQueryWrapper<>();
            ruleWrapper.in(BizContestReviewRule::getId, ruleIds);
            contestReviewRuleMapper.selectList(ruleWrapper)
                    .forEach(r -> ruleMap.put(r.getId(), r));
        }

        // 批量预加载所有相关活动的评委权重（contestIds 为空时不能拼 IN ()，否则 SQL 报错 500）
        List<BizContestJudge> allJudges = Collections.emptyList();
        if (!contestIds.isEmpty()) {
            LambdaQueryWrapper<BizContestJudge> allJudgesWrapper = new LambdaQueryWrapper<>();
            allJudgesWrapper.in(BizContestJudge::getContestId, contestIds);
            allJudgesWrapper.eq(BizContestJudge::getValidState, 1);
            allJudges = contestJudgeMapper.selectList(allJudgesWrapper);
        }
        Map<Long, List<BizContestJudge>> judgesByContestId = allJudges.stream()
                .collect(Collectors.groupingBy(BizContestJudge::getContestId));

        for (Long cid : contestIds) {
            BizContest c = finalContestMap.get(cid);
            if (c == null) {
                continue;
            }
            String calculationRule = "average";
            if (c.getReviewRuleId() != null) {
                BizContestReviewRule rule = ruleMap.get(c.getReviewRuleId());
                if (rule != null && StringUtils.hasText(rule.getCalculationRule())) {
                    calculationRule = rule.getCalculationRule();
                }
            }
            contestCalculationRuleCache.put(cid, calculationRule);

            List<BizContestJudge> judges = judgesByContestId.getOrDefault(cid, Collections.emptyList());
            Map<Long, BigDecimal> weightMap = new HashMap<>();
            for (BizContestJudge j : judges) {
                weightMap.put(j.getJudgeId(), j.getWeight() != null ? j.getWeight() : BigDecimal.ONE);
            }
            contestWeightMapCache.put(cid, weightMap);
        }

        Map<Long, List<BizContestWorkScore>> finalScoresByWorkId = scoresByWorkId;
        Map<Long, String> finalContestCalculationRuleCache = contestCalculationRuleCache;
        Map<Long, Map<Long, BigDecimal>> finalContestWeightMapCache = contestWeightMapCache;

        List<Map<String, Object>> voList = result.getRecords().stream()
                .map(work -> {
                    Map<String, Object> map = workToMap(work);

                    BizContest contest = finalContestMap.get(work.getContestId());
                    if (contest != null) {
                        Map<String, Object> contestVo = new LinkedHashMap<>();
                        contestVo.put("id", contest.getId());
                        contestVo.put("contestName", contest.getContestName());
                        map.put("contest", contestVo);
                    }

                    BizContestRegistration reg = finalRegistrationMap.get(work.getRegistrationId());
                    if (reg != null) {
                        Map<String, Object> userVo = new LinkedHashMap<>();
                        userVo.put("id", reg.getUserId());
                        userVo.put("username", reg.getAccountNo());
                        userVo.put("nickname", reg.getAccountName());

                        Map<String, Object> regVo = new LinkedHashMap<>();
                        regVo.put("id", reg.getId());
                        regVo.put("user", userVo);

                        if (StringUtils.hasText(reg.getTeamName()) || reg.getTeamId() != null) {
                            Map<String, Object> teamVo = new LinkedHashMap<>();
                            teamVo.put("teamName", reg.getTeamName());
                            regVo.put("team", teamVo);
                        }

                        map.put("registration", regVo);

                        // 兼容旧字段：保留扁平账号信息
                        map.put("accountNo", reg.getAccountNo());
                        map.put("accountName", reg.getAccountName());
                        map.put("userId", reg.getUserId());
                    }

                    // 分配信息
                    List<BizContestWorkJudgeAssignment> workAssignments = finalAssignmentMap.getOrDefault(work.getId(), Collections.emptyList());
                    List<Map<String, Object>> assignmentVoList = workAssignments.stream().map(a -> {
                        Map<String, Object> assignVo = new LinkedHashMap<>();
                        assignVo.put("id", a.getId());
                        assignVo.put("judgeId", a.getJudgeId());
                        assignVo.put("status", a.getStatus());
                        assignVo.put("assignmentTime", a.getAssignmentTime());
                        Map<String, Object> judgeVo = new LinkedHashMap<>();
                        judgeVo.put("id", a.getJudgeId());
                        judgeVo.put("nickname", finalJudgeNameMap.getOrDefault(a.getJudgeId(), ""));
                        assignVo.put("judge", judgeVo);
                        return assignVo;
                    }).collect(Collectors.toList());
                    map.put("assignments", assignmentVoList);

                    // _count 用于分配状态判断
                    Map<String, Object> countVo = new LinkedHashMap<>();
                    countVo.put("assignments", workAssignments.size());
                    map.put("_count", countVo);

                    int totalJudgesCount = workAssignments.size();
                    long reviewedCount = workAssignments.stream()
                            .filter(a -> "completed".equals(a.getStatus()))
                            .count();
                    map.put("totalJudgesCount", totalJudgesCount);
                    map.put("reviewedCount", reviewedCount);

                    BigDecimal displayFinal = work.getFinalScore();
                    if (displayFinal == null) {
                        List<BizContestWorkScore> scores = finalScoresByWorkId.getOrDefault(work.getId(), Collections.emptyList());
                        if (!scores.isEmpty()) {
                            String rule = finalContestCalculationRuleCache.getOrDefault(work.getContestId(), "average");
                            Map<Long, BigDecimal> wm = finalContestWeightMapCache.getOrDefault(work.getContestId(), Collections.emptyMap());
                            displayFinal = ContestFinalScoreCalculator.compute(scores, rule, wm);
                        }
                    }
                    if (displayFinal != null) {
                        map.put("finalScore", displayFinal);
                        map.put("averageScore", displayFinal);
                    }

                    return map;
                })
                .collect(Collectors.toList());

        return PageResult.from(result, voList);
    }

    @Override
    public Map<String, Object> getStats(Long contestId, Long tenantId, boolean isSuperTenant) {
        log.info("获取作品统计，活动ID：{}", contestId);

        // 租户过滤
        boolean needTenantFilter = !isSuperTenant && tenantId != null;

        LambdaQueryWrapper<BizContestWork> baseWrapper = new LambdaQueryWrapper<>();
        if (contestId != null) {
            baseWrapper.eq(BizContestWork::getContestId, contestId);
        }
        baseWrapper.eq(BizContestWork::getIsLatest, true);
        baseWrapper.eq(BizContestWork::getValidState, 1);
        if (needTenantFilter) {
            baseWrapper.eq(BizContestWork::getTenantId, tenantId);
        }
        long total = count(baseWrapper);

        LambdaQueryWrapper<BizContestWork> submittedWrapper = new LambdaQueryWrapper<>();
        if (contestId != null) {
            submittedWrapper.eq(BizContestWork::getContestId, contestId);
        }
        submittedWrapper.eq(BizContestWork::getIsLatest, true);
        submittedWrapper.eq(BizContestWork::getValidState, 1);
        if (needTenantFilter) {
            submittedWrapper.eq(BizContestWork::getTenantId, tenantId);
        }
        submittedWrapper.eq(BizContestWork::getStatus, WorkStatus.SUBMITTED.getValue());
        long submitted = count(submittedWrapper);

        LambdaQueryWrapper<BizContestWork> reviewingWrapper = new LambdaQueryWrapper<>();
        if (contestId != null) {
            reviewingWrapper.eq(BizContestWork::getContestId, contestId);
        }
        reviewingWrapper.eq(BizContestWork::getIsLatest, true);
        reviewingWrapper.eq(BizContestWork::getValidState, 1);
        if (needTenantFilter) {
            reviewingWrapper.eq(BizContestWork::getTenantId, tenantId);
        }
        reviewingWrapper.eq(BizContestWork::getStatus, WorkStatus.REVIEWING.getValue());
        long reviewing = count(reviewingWrapper);

        LambdaQueryWrapper<BizContestWork> reviewedWrapper = new LambdaQueryWrapper<>();
        if (contestId != null) {
            reviewedWrapper.eq(BizContestWork::getContestId, contestId);
        }
        reviewedWrapper.eq(BizContestWork::getIsLatest, true);
        reviewedWrapper.eq(BizContestWork::getValidState, 1);
        if (needTenantFilter) {
            reviewedWrapper.eq(BizContestWork::getTenantId, tenantId);
        }
        reviewedWrapper.in(BizContestWork::getStatus, Arrays.asList(WorkStatus.ACCEPTED.getValue(), WorkStatus.AWARDED.getValue()));
        long reviewed = count(reviewedWrapper);

        Map<String, Object> stats = new LinkedHashMap<>();
        stats.put("total", total);
        stats.put("submitted", submitted);
        stats.put("reviewing", reviewing);
        stats.put("reviewed", reviewed);
        return stats;
    }

    @Override
    public Map<String, Object> findDetail(Long id) {
        log.info("查询作品详情，ID：{}", id);

        BizContestWork work = getById(id);
        if (work == null) {
            throw BusinessException.of(ErrorCode.NOT_FOUND, "作品不存在");
        }

        Map<String, Object> result = workToMap(work);

        // 查询报名信息
        if (work.getRegistrationId() != null) {
            BizContestRegistration registration = contestRegistrationMapper.selectById(work.getRegistrationId());
            if (registration != null) {
                Map<String, Object> regInfo = new LinkedHashMap<>();
                regInfo.put("id", registration.getId());
                regInfo.put("userId", registration.getUserId());
                regInfo.put("accountNo", registration.getAccountNo());
                regInfo.put("accountName", registration.getAccountName());
                regInfo.put("registrationType", registration.getRegistrationType());
                regInfo.put("participantType", registration.getParticipantType());
                result.put("registration", regInfo);
            }
        }

        // 查询附件
        LambdaQueryWrapper<BizContestWorkAttachment> attWrapper = new LambdaQueryWrapper<>();
        attWrapper.eq(BizContestWorkAttachment::getWorkId, id);
        List<BizContestWorkAttachment> attachments = contestWorkAttachmentMapper.selectList(attWrapper);
        result.put("attachments", attachments);

        return result;
    }

    @Override
    public List<Map<String, Object>> getWorkVersions(Long registrationId) {
        log.info("查询作品版本历史，报名ID：{}", registrationId);

        LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(BizContestWork::getRegistrationId, registrationId);
        wrapper.orderByDesc(BizContestWork::getVersion);

        List<BizContestWork> works = list(wrapper);
        return works.stream()
                .map(this::workToMap)
                .collect(Collectors.toList());
    }

    @Override
    public PageResult<Map<String, Object>> getGuidedWorks(Long contestId, String workNo, String playerName,
                                                           String accountNo, Long page, Long pageSize, Long userId) {
        log.info("查询指导作品，活动ID：{}，教师用户ID：{}", contestId, userId);

        // 简化实现：查询当前教师指导的报名ID列表，再查对应作品
        // 完整实现需要关联 t_biz_contest_registration_teacher 表
        LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();
        if (contestId != null) {
            wrapper.eq(BizContestWork::getContestId, contestId);
        }
        wrapper.eq(BizContestWork::getIsLatest, true);

        if (StringUtils.hasText(workNo)) {
            wrapper.eq(BizContestWork::getWorkNo, workNo);
        }

        wrapper.orderByDesc(BizContestWork::getSubmitTime);

        Page<BizContestWork> pageObj = new Page<>(page != null ? page : 1L, pageSize != null ? pageSize : 10L);
        Page<BizContestWork> result = contestWorkMapper.selectPage(pageObj, wrapper);

        List<Map<String, Object>> voList = result.getRecords().stream()
                .map(this::workToMap)
                .collect(Collectors.toList());

        return PageResult.from(result, voList);
    }

    @Override
    public void removeWork(Long id) {
        log.info("删除作品，ID：{}", id);
        removeById(id);
        log.info("作品删除成功，ID：{}", id);
    }

    // ====== 私有辅助方法 ======

    /**
     * 解析时间参数，支持 "yyyy-MM-dd" 和 "yyyy-MM-ddTHH:mm:ss" 两种格式。
     * isStart=true 时纯日期补 00:00:00，isStart=false 时纯日期补 23:59:59。
     */
    private LocalDateTime parseDateTime(String value, boolean isStart) {
        if (value.contains("T")) {
            return LocalDateTime.parse(value, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        }
        LocalDate date = LocalDate.parse(value, DateTimeFormatter.ISO_LOCAL_DATE);
        return isStart ? date.atStartOfDay() : date.atTime(23, 59, 59);
    }

    @Override
    public synchronized String nextContestWorkNo(Long contestId) {
        // 使用 MAX(work_no) 查询避免并发编号重复
        LambdaQueryWrapper<BizContestWork> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(BizContestWork::getContestId, contestId);
        wrapper.likeRight(BizContestWork::getWorkNo, "W" + contestId + "-");
        wrapper.orderByDesc(BizContestWork::getWorkNo);
        wrapper.last("LIMIT 1");
        BizContestWork lastWork = getOne(wrapper, false);

        int nextSeq = 1;
        if (lastWork != null && lastWork.getWorkNo() != null) {
            try {
                String no = lastWork.getWorkNo();
                int dashIndex = no.lastIndexOf("-");
                if (dashIndex > 0) {
                    nextSeq = Integer.parseInt(no.substring(dashIndex + 1)) + 1;
                }
            } catch (NumberFormatException e) {
                log.warn("解析作品编号失败：{}，将使用默认序号", lastWork.getWorkNo());
                // 降级：使用 count + 1
                LambdaQueryWrapper<BizContestWork> countWrapper = new LambdaQueryWrapper<>();
                countWrapper.eq(BizContestWork::getContestId, contestId);
                nextSeq = (int) count(countWrapper) + 1;
            }
        }
        return "W" + contestId + "-" + nextSeq;
    }

    private Map<String, Object> workToMap(BizContestWork entity) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("id", entity.getId());
        map.put("tenantId", entity.getTenantId());
        map.put("contestId", entity.getContestId());
        map.put("registrationId", entity.getRegistrationId());
        map.put("workNo", entity.getWorkNo());
        map.put("title", entity.getTitle());
        map.put("description", entity.getDescription());
        map.put("files", entity.getFiles());
        map.put("version", entity.getVersion());
        map.put("isLatest", entity.getIsLatest());
        map.put("status", entity.getStatus());
        map.put("violationReason", entity.getViolationReason());
        map.put("violationMarkTime", entity.getViolationMarkTime());
        map.put("violationJudgeId", entity.getViolationJudgeId());
        map.put("submitTime", entity.getSubmitTime());
        map.put("submitterUserId", entity.getSubmitterUserId());
        map.put("submitterAccountNo", entity.getSubmitterAccountNo());
        map.put("submitSource", entity.getSubmitSource());
        map.put("previewUrl", entity.getPreviewUrl());
        map.put("previewUrls", entity.getPreviewUrls());
        map.put("aiModelMeta", entity.getAiModelMeta());
        map.put("userWorkId", entity.getUserWorkId());
        map.put("finalScore", entity.getFinalScore());
        map.put("rank", entity.getRank());
        map.put("awardLevel", entity.getAwardLevel());
        map.put("awardName", entity.getAwardName());
        map.put("certificateUrl", entity.getCertificateUrl());
        map.put("createTime", entity.getCreateTime());
        return map;
    }
}
