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

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.competition.common.enums.ErrorCode;
import com.competition.common.exception.BusinessException;
import com.competition.common.result.PageResult;
import com.competition.common.util.SecurityUtil;
import com.competition.modules.biz.contest.dto.CreateContestDto;
import com.competition.modules.biz.contest.dto.QueryContestDto;
import com.competition.modules.biz.contest.entity.BizContest;
import com.competition.modules.biz.contest.entity.BizContestAttachment;
import com.competition.modules.biz.contest.entity.BizContestRegistration;
import com.competition.modules.biz.contest.entity.BizContestWork;
import com.competition.modules.biz.contest.mapper.ContestAttachmentMapper;
import com.competition.modules.biz.contest.mapper.ContestMapper;
import com.competition.modules.biz.contest.mapper.ContestRegistrationMapper;
import com.competition.modules.biz.contest.mapper.ContestWorkMapper;
import com.competition.modules.biz.contest.service.IContestService;
import com.competition.modules.biz.review.entity.BizContestWorkJudgeAssignment;
import com.competition.modules.biz.review.mapper.ContestWorkJudgeAssignmentMapper;
import com.competition.modules.sys.entity.SysTenant;
import com.competition.modules.sys.mapper.SysTenantMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

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

@Slf4j
@Service
@RequiredArgsConstructor
public class ContestServiceImpl extends ServiceImpl<ContestMapper, BizContest> implements IContestService {

    private final ContestMapper contestMapper;
    private final ContestAttachmentMapper contestAttachmentMapper;
    private final ContestRegistrationMapper contestRegistrationMapper;
    private final ContestWorkMapper contestWorkMapper;
    private final ContestWorkJudgeAssignmentMapper contestWorkJudgeAssignmentMapper;
    private final SysTenantMapper sysTenantMapper;

    // 支持两种日期格式：ISO 格式 (T 分隔) 和空格分隔格式
    private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    private static final DateTimeFormatter SPACE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 解析日期时间字符串，兼容 ISO 格式和空格分隔格式
     */
    private LocalDateTime parseDateTime(String dateTime) {
        if (!StringUtils.hasText(dateTime)) {
            return null;
        }
        // 尝试 ISO 格式 (yyyy-MM-ddTHH:mm:ss)
        try {
            return LocalDateTime.parse(dateTime, ISO_FORMATTER);
        } catch (Exception e) {
            // 尝试空格分隔格式 (yyyy-MM-dd HH:mm:ss)
            try {
                return LocalDateTime.parse(dateTime, SPACE_FORMATTER);
            } catch (Exception ex) {
                log.warn("日期格式解析失败：{}", dateTime, ex);
                return null;
            }
        }
    }

    @Override
    public BizContest createContest(CreateContestDto dto, Long creatorId) {
        log.info("开始创建赛事，名称：{}", dto.getContestName());

        try {
            BizContest entity = new BizContest();
            mapDtoToEntity(dto, entity);

            // 默认状态
            entity.setContestState("unpublished");
            entity.setStatus("ongoing");
            entity.setResultState("unpublished");
            if (!StringUtils.hasText(entity.getSubmitRule())) {
                entity.setSubmitRule("once");
            }
            entity.setCreator(creatorId != null ? creatorId.intValue() : null);

            // 如果没有设置授权租户，默认添加当前租户
            if (entity.getContestTenants() == null || entity.getContestTenants().isEmpty()) {
                Long currentTenantId = SecurityUtil.getCurrentTenantId();
                if (currentTenantId != null) {
                    entity.setContestTenants(Collections.singletonList(currentTenantId.intValue()));
                }
            }

            save(entity);
            log.info("赛事创建成功，ID：{}, 名称：{}", entity.getId(), entity.getContestName());
            return entity;
        } catch (Exception e) {
            log.error("创建赛事失败，名称：{}", dto.getContestName(), e);
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, "创建赛事失败：" + e.getMessage());
        }
    }

    @Override
    public PageResult<Map<String, Object>> findAll(QueryContestDto dto, Long tenantId, boolean isSuperTenant) {
        log.info("查询赛事列表，页码：{}，每页：{}", dto.getPage(), dto.getPageSize());

        LambdaQueryWrapper<BizContest> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(BizContest::getValidState, 1);

        if (StringUtils.hasText(dto.getContestName())) {
            wrapper.like(BizContest::getContestName, dto.getContestName());
        }
        if (StringUtils.hasText(dto.getContestState())) {
            wrapper.eq(BizContest::getContestState, dto.getContestState());
        }
        if (StringUtils.hasText(dto.getStatus())) {
            wrapper.eq(BizContest::getStatus, dto.getStatus());
        }
        if (StringUtils.hasText(dto.getVisibility())) {
            wrapper.eq(BizContest::getVisibility, dto.getVisibility());
        }
        if (StringUtils.hasText(dto.getContestType())) {
            wrapper.eq(BizContest::getContestType, dto.getContestType());
        }

        // 阶段筛选
        if (StringUtils.hasText(dto.getStage())) {
            LocalDateTime now = LocalDateTime.now();
            switch (dto.getStage()) {
                case "registering":
                    wrapper.le(BizContest::getRegisterStartTime, now)
                           .ge(BizContest::getRegisterEndTime, now);
                    break;
                case "submitting":
                    wrapper.le(BizContest::getSubmitStartTime, now)
                           .ge(BizContest::getSubmitEndTime, now);
                    break;
                case "reviewing":
                    wrapper.le(BizContest::getReviewStartTime, now)
                           .ge(BizContest::getReviewEndTime, now);
                    break;
                default:
                    break;
            }
        }

        // 非超级租户按授权租户过滤
        if (!isSuperTenant && tenantId != null) {
            wrapper.apply("JSON_CONTAINS(contest_tenants, CAST({0} AS JSON))", tenantId);
        }

        wrapper.orderByDesc(BizContest::getCreateTime);

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

        // 批量查询报名数和作品数
        List<Long> contestIds = result.getRecords().stream()
                .map(BizContest::getId).toList();

        Map<Long, Long> registrationCountMap = new HashMap<>();
        Map<Long, Long> workCountMap = new HashMap<>();
        Map<Long, Long> reviewedWorkCountMap = new HashMap<>();
        if (!contestIds.isEmpty()) {
            // 报名数（所有状态）
            contestRegistrationMapper.selectList(
                    new LambdaQueryWrapper<BizContestRegistration>()
                            .in(BizContestRegistration::getContestId, contestIds))
                    .stream()
                    .collect(Collectors.groupingBy(BizContestRegistration::getContestId, Collectors.counting()))
                    .forEach(registrationCountMap::put);

            // 作品：最新有效版本；评审完成数 = 已分配且该作品全部分配记录均为 completed（与评委端、ProgressDetail 一致）
            List<BizContestWork> contestWorks = contestWorkMapper.selectList(
                    new LambdaQueryWrapper<BizContestWork>()
                            .in(BizContestWork::getContestId, contestIds)
                            .eq(BizContestWork::getIsLatest, true)
                            .eq(BizContestWork::getValidState, 1));
            Set<Long> workIdSet = contestWorks.stream().map(BizContestWork::getId).collect(Collectors.toSet());
            Map<Long, List<BizContestWorkJudgeAssignment>> assignByWorkId = new HashMap<>();
            if (!workIdSet.isEmpty()) {
                List<BizContestWorkJudgeAssignment> allAssign = contestWorkJudgeAssignmentMapper.selectList(
                        new LambdaQueryWrapper<BizContestWorkJudgeAssignment>()
                                .in(BizContestWorkJudgeAssignment::getWorkId, workIdSet));
                assignByWorkId = allAssign.stream()
                        .collect(Collectors.groupingBy(BizContestWorkJudgeAssignment::getWorkId));
            }
            for (BizContestWork w : contestWorks) {
                Long cid = w.getContestId();
                workCountMap.merge(cid, 1L, Long::sum);
                List<BizContestWorkJudgeAssignment> assigns = assignByWorkId.getOrDefault(w.getId(), Collections.emptyList());
                if (!assigns.isEmpty() && assigns.stream().allMatch(a -> "completed".equals(a.getStatus()))) {
                    reviewedWorkCountMap.merge(cid, 1L, Long::sum);
                }
            }
        }

        List<Map<String, Object>> voList = result.getRecords().stream()
                .map(entity -> {
                    Map<String, Object> map = entityToMap(entity);
                    Map<String, Object> countMap = new LinkedHashMap<>();
                    long works = workCountMap.getOrDefault(entity.getId(), 0L);
                    long reviewedWorks = reviewedWorkCountMap.getOrDefault(entity.getId(), 0L);
                    countMap.put("registrations", registrationCountMap.getOrDefault(entity.getId(), 0L));
                    countMap.put("works", works);
                    map.put("_count", countMap);
                    map.put("totalWorksCount", works);
                    map.put("reviewedCount", reviewedWorks);
                    return map;
                })
                .collect(Collectors.toList());

        return PageResult.from(result, voList);
    }

    @Override
    public PageResult<Map<String, Object>> getMyContests(QueryContestDto dto, Long userId, Long tenantId) {
        log.info("查询我的赛事，用户ID：{}，租户ID：{}", userId, tenantId);

        LambdaQueryWrapper<BizContest> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(BizContest::getValidState, 1);
        wrapper.eq(BizContest::getContestState, "published");

        if (StringUtils.hasText(dto.getContestName())) {
            wrapper.like(BizContest::getContestName, dto.getContestName());
        }

        // 按租户过滤
        if (tenantId != null) {
            wrapper.apply("JSON_CONTAINS(contest_tenants, CAST({0} AS JSON))", tenantId);
        }

        wrapper.orderByDesc(BizContest::getCreateTime);

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

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

        return PageResult.from(result, voList);
    }

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

        BizContest contest = getById(id);
        if (contest == null) {
            throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
        }

        Map<String, Object> result = entityToMap(contest);

        // 查询附件列表
        LambdaQueryWrapper<BizContestAttachment> attWrapper = new LambdaQueryWrapper<>();
        attWrapper.eq(BizContestAttachment::getContestId, id);
        List<BizContestAttachment> attachments = contestAttachmentMapper.selectList(attWrapper);
        result.put("attachments", attachments);

        // 查询授权租户的详细信息
        List<Integer> tenantIds = contest.getContestTenants();
        if (tenantIds != null && !tenantIds.isEmpty()) {
            List<Map<String, Object>> tenantInfoList = new ArrayList<>();
            for (Integer tenantId : tenantIds) {
                SysTenant tenant = sysTenantMapper.selectById(tenantId.longValue());
                if (tenant != null) {
                    Map<String, Object> tenantInfo = new LinkedHashMap<>();
                    tenantInfo.put("id", tenant.getId());
                    tenantInfo.put("name", tenant.getName());
                    tenantInfo.put("code", tenant.getCode());
                    tenantInfo.put("tenantType", tenant.getTenantType());
                    tenantInfoList.add(tenantInfo);
                }
            }
            result.put("contestTenantInfos", tenantInfoList);
        }

        return result;
    }

    @Override
    public BizContest updateContest(Long id, CreateContestDto dto) {
        log.info("更新赛事，ID：{}", id);

        BizContest entity = getById(id);
        if (entity == null) {
            throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
        }

        mapDtoToEntity(dto, entity);
        updateById(entity);

        log.info("赛事更新成功，ID：{}", id);
        return entity;
    }

    @Override
    public void publishContest(Long id, String contestState) {
        log.info("发布/撤回赛事，ID：{}，状态：{}", id, contestState);

        BizContest entity = getById(id);
        if (entity == null) {
            throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
        }

        entity.setContestState(contestState);
        updateById(entity);
        log.info("赛事状态更新成功，ID：{}，新状态：{}", id, contestState);
    }

    @Override
    public void finishContest(Long id) {
        log.info("结束赛事，ID：{}", id);

        BizContest entity = getById(id);
        if (entity == null) {
            throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
        }

        entity.setStatus("finished");
        updateById(entity);
        log.info("赛事已结束，ID：{}", id);
    }

    @Override
    public void reopenContest(Long id) {
        log.info("重新开启赛事，ID：{}", id);

        BizContest entity = getById(id);
        if (entity == null) {
            throw BusinessException.of(ErrorCode.NOT_FOUND, "赛事不存在");
        }

        entity.setStatus("ongoing");
        updateById(entity);
        log.info("赛事已重新开启，ID：{}", id);
    }

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

    @Override
    public Map<String, Object> getStats(Long tenantId, boolean isSuperTenant) {
        log.info("获取赛事统计，租户ID：{}", tenantId);

        LambdaQueryWrapper<BizContest> baseWrapper = new LambdaQueryWrapper<>();
        baseWrapper.eq(BizContest::getValidState, 1);
        if (!isSuperTenant && tenantId != null) {
            baseWrapper.apply("JSON_CONTAINS(contest_tenants, CAST({0} AS JSON))", tenantId);
        }

        long total = count(baseWrapper);

        LambdaQueryWrapper<BizContest> publishedWrapper = new LambdaQueryWrapper<>();
        publishedWrapper.eq(BizContest::getValidState, 1);
        publishedWrapper.eq(BizContest::getContestState, "published");
        if (!isSuperTenant && tenantId != null) {
            publishedWrapper.apply("JSON_CONTAINS(contest_tenants, CAST({0} AS JSON))", tenantId);
        }
        long published = count(publishedWrapper);

        LambdaQueryWrapper<BizContest> ongoingWrapper = new LambdaQueryWrapper<>();
        ongoingWrapper.eq(BizContest::getValidState, 1);
        ongoingWrapper.eq(BizContest::getStatus, "ongoing");
        if (!isSuperTenant && tenantId != null) {
            ongoingWrapper.apply("JSON_CONTAINS(contest_tenants, CAST({0} AS JSON))", tenantId);
        }
        long ongoing = count(ongoingWrapper);

        LambdaQueryWrapper<BizContest> finishedWrapper = new LambdaQueryWrapper<>();
        finishedWrapper.eq(BizContest::getValidState, 1);
        finishedWrapper.eq(BizContest::getStatus, "finished");
        if (!isSuperTenant && tenantId != null) {
            finishedWrapper.apply("JSON_CONTAINS(contest_tenants, CAST({0} AS JSON))", tenantId);
        }
        long finished = count(finishedWrapper);

        Map<String, Object> stats = new LinkedHashMap<>();
        stats.put("total", total);
        stats.put("published", published);
        stats.put("ongoing", ongoing);
        stats.put("finished", finished);
        return stats;
    }

    @Override
    public Map<String, Object> getDashboard(Long tenantId) {
        log.info("获取仪表盘数据，租户ID：{}", tenantId);

        LambdaQueryWrapper<BizContest> contestWrapper = new LambdaQueryWrapper<>();
        contestWrapper.eq(BizContest::getValidState, 1);
        if (tenantId != null) {
            contestWrapper.apply("JSON_CONTAINS(contest_tenants, CAST({0} AS JSON))", tenantId);
        }
        long totalContests = count(contestWrapper);

        long totalRegistrations = contestRegistrationMapper.selectCount(new LambdaQueryWrapper<BizContestRegistration>());

        long totalWorks = contestWorkMapper.selectCount(new LambdaQueryWrapper<BizContestWork>());

        Map<String, Object> dashboard = new LinkedHashMap<>();
        dashboard.put("totalContests", totalContests);
        dashboard.put("totalRegistrations", totalRegistrations);
        dashboard.put("totalWorks", totalWorks);
        return dashboard;
    }

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

    private void mapDtoToEntity(CreateContestDto dto, BizContest entity) {
        if (StringUtils.hasText(dto.getContestName())) {
            entity.setContestName(dto.getContestName());
        }
        if (StringUtils.hasText(dto.getContestType())) {
            entity.setContestType(dto.getContestType());
        }
        if (StringUtils.hasText(dto.getVisibility())) {
            entity.setVisibility(dto.getVisibility());
        }
        if (dto.getTargetCities() != null) {
            entity.setTargetCities(dto.getTargetCities());
        }
        if (dto.getAgeMin() != null) {
            entity.setAgeMin(dto.getAgeMin());
        }
        if (dto.getAgeMax() != null) {
            entity.setAgeMax(dto.getAgeMax());
        }
        if (StringUtils.hasText(dto.getStartTime())) {
            entity.setStartTime(parseDateTime(dto.getStartTime()));
        }
        if (StringUtils.hasText(dto.getEndTime())) {
            entity.setEndTime(parseDateTime(dto.getEndTime()));
        }
        if (StringUtils.hasText(dto.getAddress())) {
            entity.setAddress(dto.getAddress());
        }
        if (dto.getContent() != null) {
            entity.setContent(dto.getContent());
        }
        if (dto.getContestTenants() != null) {
            entity.setContestTenants(dto.getContestTenants());
        }
        if (StringUtils.hasText(dto.getCoverUrl())) {
            entity.setCoverUrl(dto.getCoverUrl());
        }
        if (StringUtils.hasText(dto.getPosterUrl())) {
            entity.setPosterUrl(dto.getPosterUrl());
        }
        if (StringUtils.hasText(dto.getContactName())) {
            entity.setContactName(dto.getContactName());
        }
        if (StringUtils.hasText(dto.getContactPhone())) {
            entity.setContactPhone(dto.getContactPhone());
        }
        if (StringUtils.hasText(dto.getContactQrcode())) {
            entity.setContactQrcode(dto.getContactQrcode());
        }
        if (dto.getOrganizers() != null) {
            entity.setOrganizers(dto.getOrganizers());
        }
        if (dto.getCoOrganizers() != null) {
            entity.setCoOrganizers(dto.getCoOrganizers());
        }
        if (dto.getSponsors() != null) {
            entity.setSponsors(dto.getSponsors());
        }
        if (StringUtils.hasText(dto.getRegisterStartTime())) {
            entity.setRegisterStartTime(parseDateTime(dto.getRegisterStartTime()));
        }
        if (StringUtils.hasText(dto.getRegisterEndTime())) {
            entity.setRegisterEndTime(parseDateTime(dto.getRegisterEndTime()));
        }
        if (StringUtils.hasText(dto.getRegisterState())) {
            entity.setRegisterState(dto.getRegisterState());
        }
        if (dto.getRequireAudit() != null) {
            entity.setRequireAudit(dto.getRequireAudit());
        }
        if (dto.getAllowedGrades() != null) {
            entity.setAllowedGrades(dto.getAllowedGrades());
        }
        if (dto.getAllowedClasses() != null) {
            entity.setAllowedClasses(dto.getAllowedClasses());
        }
        if (dto.getTeamMinMembers() != null) {
            entity.setTeamMinMembers(dto.getTeamMinMembers());
        }
        if (dto.getTeamMaxMembers() != null) {
            entity.setTeamMaxMembers(dto.getTeamMaxMembers());
        }
        if (StringUtils.hasText(dto.getSubmitRule())) {
            entity.setSubmitRule(dto.getSubmitRule());
        }
        if (StringUtils.hasText(dto.getSubmitStartTime())) {
            entity.setSubmitStartTime(parseDateTime(dto.getSubmitStartTime()));
        }
        if (StringUtils.hasText(dto.getSubmitEndTime())) {
            entity.setSubmitEndTime(parseDateTime(dto.getSubmitEndTime()));
        }
        if (StringUtils.hasText(dto.getWorkType())) {
            entity.setWorkType(dto.getWorkType());
        }
        if (dto.getWorkRequirement() != null) {
            entity.setWorkRequirement(dto.getWorkRequirement());
        }
        if (dto.getReviewRuleId() != null) {
            entity.setReviewRuleId(dto.getReviewRuleId());
        }
        if (StringUtils.hasText(dto.getReviewStartTime())) {
            entity.setReviewStartTime(parseDateTime(dto.getReviewStartTime()));
        }
        if (StringUtils.hasText(dto.getReviewEndTime())) {
            entity.setReviewEndTime(parseDateTime(dto.getReviewEndTime()));
        }
        if (StringUtils.hasText(dto.getResultPublishTime())) {
            entity.setResultPublishTime(parseDateTime(dto.getResultPublishTime()));
        }
    }

    private Map<String, Object> entityToMap(BizContest entity) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("id", entity.getId());
        map.put("contestName", entity.getContestName());
        map.put("contestType", entity.getContestType());
        map.put("contestState", entity.getContestState());
        map.put("status", entity.getStatus());
        map.put("visibility", entity.getVisibility());
        map.put("startTime", entity.getStartTime());
        map.put("endTime", entity.getEndTime());
        map.put("address", entity.getAddress());
        map.put("content", entity.getContent());
        map.put("contestTenants", entity.getContestTenants());
        map.put("coverUrl", entity.getCoverUrl());
        map.put("posterUrl", entity.getPosterUrl());
        map.put("contactName", entity.getContactName());
        map.put("contactPhone", entity.getContactPhone());
        map.put("contactQrcode", entity.getContactQrcode());
        map.put("organizers", entity.getOrganizers());
        map.put("coOrganizers", entity.getCoOrganizers());
        map.put("sponsors", entity.getSponsors());
        map.put("registerStartTime", entity.getRegisterStartTime());
        map.put("registerEndTime", entity.getRegisterEndTime());
        map.put("registerState", entity.getRegisterState());
        map.put("requireAudit", entity.getRequireAudit());
        map.put("allowedGrades", entity.getAllowedGrades());
        map.put("allowedClasses", entity.getAllowedClasses());
        map.put("teamMinMembers", entity.getTeamMinMembers());
        map.put("teamMaxMembers", entity.getTeamMaxMembers());
        map.put("targetCities", entity.getTargetCities());
        map.put("ageMin", entity.getAgeMin());
        map.put("ageMax", entity.getAgeMax());
        map.put("submitRule", entity.getSubmitRule());
        map.put("submitStartTime", entity.getSubmitStartTime());
        map.put("submitEndTime", entity.getSubmitEndTime());
        map.put("workType", entity.getWorkType());
        map.put("workRequirement", entity.getWorkRequirement());
        map.put("reviewRuleId", entity.getReviewRuleId());
        map.put("reviewStartTime", entity.getReviewStartTime());
        map.put("reviewEndTime", entity.getReviewEndTime());
        map.put("resultState", entity.getResultState());
        map.put("resultPublishTime", entity.getResultPublishTime());
        map.put("createTime", entity.getCreateTime());
        map.put("modifyTime", entity.getModifyTime());
        return map;
    }
}
