package com.lesingle.modules.biz.review.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.lesingle.common.enums.PublishStatus;
import com.lesingle.common.enums.RegistrationStatus;
import com.lesingle.common.enums.WorkStatus;
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.mapper.ContestMapper;
import com.lesingle.modules.biz.contest.mapper.ContestRegistrationMapper;
import com.lesingle.modules.biz.contest.mapper.ContestWorkMapper;
import com.lesingle.modules.biz.review.mapper.AnalyticsMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 租户端数据统计（运营概览 / 评审分析）
 */
@Service
@RequiredArgsConstructor
public class AnalyticsService {

    private final ContestMapper contestMapper;
    private final ContestRegistrationMapper contestRegistrationMapper;
    private final ContestWorkMapper contestWorkMapper;
    private final AnalyticsMapper analyticsMapper;

    /**
     * 运营概览
     *
     * @param timeRange month / quarter / year / all（空或 all 表示不限时间）
     */
    public Map<String, Object> getOverview(Long tenantId, boolean isSuperTenant, Long contestId, String timeRange) {
        List<Long> contestIds = resolveVisibleContestIds(tenantId, isSuperTenant, contestId);
        LocalDateTime[] range = resolveTimeRange(timeRange);

        Map<String, Object> result = new LinkedHashMap<>();
        if (contestIds.isEmpty()) {
            result.put("summary", emptySummary());
            result.put("funnel", emptyFunnel());
            result.put("monthlyTrend", Collections.emptyList());
            result.put("contestComparison", Collections.emptyList());
            return result;
        }

        // --- summary ---
        long totalContests = contestIds.size();

        LambdaQueryWrapper<BizContestRegistration> regBase = new LambdaQueryWrapper<BizContestRegistration>()
                .in(BizContestRegistration::getContestId, contestIds);
        if (!isSuperTenant && tenantId != null) {
            regBase.eq(BizContestRegistration::getTenantId, tenantId);
        }
        applyRegistrationTimeRange(regBase, range);

        long totalRegistrations = contestRegistrationMapper.selectCount(regBase);

        LambdaQueryWrapper<BizContestRegistration> regPassed = new LambdaQueryWrapper<BizContestRegistration>()
                .in(BizContestRegistration::getContestId, contestIds)
                .eq(BizContestRegistration::getRegistrationState, RegistrationStatus.PASSED.getValue());
        if (!isSuperTenant && tenantId != null) {
            regPassed.eq(BizContestRegistration::getTenantId, tenantId);
        }
        applyRegistrationTimeRange(regPassed, range);
        long passedRegistrations = contestRegistrationMapper.selectCount(regPassed);

        LambdaQueryWrapper<BizContestWork> workBase = workBaseWrapper(contestIds, tenantId, isSuperTenant);
        applyWorkSubmitTimeRange(workBase, range);
        long totalWorks = contestWorkMapper.selectCount(workBase);

        LambdaQueryWrapper<BizContestWork> workReviewed = workBaseWrapper(contestIds, tenantId, isSuperTenant);
        workReviewed.in(BizContestWork::getStatus, WorkStatus.ACCEPTED.getValue(), WorkStatus.AWARDED.getValue());
        applyWorkSubmitTimeRange(workReviewed, range);
        long reviewedWorks = contestWorkMapper.selectCount(workReviewed);

        LambdaQueryWrapper<BizContestWork> workAwarded = workBaseWrapper(contestIds, tenantId, isSuperTenant);
        workAwarded.isNotNull(BizContestWork::getAwardLevel).ne(BizContestWork::getAwardLevel, "none");
        applyWorkSubmitTimeRange(workAwarded, range);
        long awardedWorks = contestWorkMapper.selectCount(workAwarded);

        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("totalContests", totalContests);
        summary.put("totalRegistrations", totalRegistrations);
        summary.put("passedRegistrations", passedRegistrations);
        summary.put("totalWorks", totalWorks);
        summary.put("reviewedWorks", reviewedWorks);
        summary.put("awardedWorks", awardedWorks);
        result.put("summary", summary);

        // --- funnel（与 summary 同一口径）---
        Map<String, Object> funnel = new LinkedHashMap<>();
        funnel.put("registered", totalRegistrations);
        funnel.put("passed", passedRegistrations);
        funnel.put("submitted", totalWorks);
        funnel.put("reviewed", reviewedWorks);
        funnel.put("awarded", awardedWorks);
        result.put("funnel", funnel);

        // --- 月度趋势（最近 6 个自然月窗口由 SQL 限定）---
        result.put("monthlyTrend", buildMonthlyTrend(contestIds, tenantId, isSuperTenant));

        // --- 活动对比 ---
        result.put("contestComparison", buildContestComparison(contestIds, tenantId, isSuperTenant, range));

        return result;
    }

    /**
     * 评审分析
     */
    public Map<String, Object> getReviewAnalysis(Long tenantId, boolean isSuperTenant, Long contestId, String timeRange) {
        List<Long> contestIds = resolveVisibleContestIds(tenantId, isSuperTenant, contestId);
        Map<String, Object> result = new LinkedHashMap<>();

        if (contestIds.isEmpty()) {
            result.put("efficiency", emptyEfficiency());
            result.put("judgeWorkload", Collections.emptyList());
            result.put("awardDistribution", Collections.emptyList());
            return result;
        }

        BigDecimal avgDays = analyticsMapper.selectAvgReviewCycleDays(contestIds, tenantId, isSuperTenant);
        long scores30 = analyticsMapper.selectScoreCountLast30Days(contestIds, tenantId, isSuperTenant);
        long pending = analyticsMapper.selectPendingAssignments(contestIds);
        BigDecimal avgStd = analyticsMapper.selectAvgStddevAcrossWorks(contestIds, tenantId, isSuperTenant);

        Map<String, Object> efficiency = new LinkedHashMap<>();
        efficiency.put("avgReviewDays", avgDays == null ? 0.0 : round2(avgDays.doubleValue()));
        efficiency.put("dailyReviewCount", Math.round(scores30 / 30.0 * 100.0) / 100.0);
        efficiency.put("pendingAssignments", pending);
        efficiency.put("avgScoreStddev", avgStd == null ? 0.0 : round2(avgStd.doubleValue()));

        result.put("efficiency", efficiency);

        List<Map<String, Object>> judgeRows = analyticsMapper.selectJudgeWorkload(contestIds, tenantId, isSuperTenant);
        List<Map<String, Object>> judgeWorkload = new ArrayList<>();
        for (Map<String, Object> row : judgeRows) {
            Map<String, Object> item = new LinkedHashMap<>();
            Long judgeId = toLong(row.get("judgeId"));
            item.put("judgeId", judgeId != null ? judgeId : 0L);
            item.put("judgeName", Objects.toString(row.get("judgeName"), "评委"));
            item.put("contestCount", toLong(row.get("contestCount")));
            long assigned = toLong(row.get("assignedCount"));
            long scored = toLong(row.get("scoredCount"));
            item.put("assignedCount", assigned);
            item.put("scoredCount", scored);
            double completion = assigned > 0 ? Math.round(scored * 10000.0 / assigned) / 100.0 : 0;
            item.put("completionRate", completion);
            BigDecimal avgSc = toBigDecimal(row.get("avgScore"));
            BigDecimal stdSc = toBigDecimal(row.get("scoreStddev"));
            item.put("avgScore", avgSc == null ? null : round2(avgSc.doubleValue()));
            item.put("scoreStddev", stdSc == null ? 0.0 : round2(stdSc.doubleValue()));
            judgeWorkload.add(item);
        }
        result.put("judgeWorkload", judgeWorkload);

        result.put("awardDistribution", buildAwardDistribution(contestIds, tenantId, isSuperTenant));

        return result;
    }

    // --- 可见活动 ID（已发布 + 租户授权）---

    private List<Long> resolveVisibleContestIds(Long tenantId, boolean isSuperTenant, Long contestId) {
        LambdaQueryWrapper<BizContest> w = new LambdaQueryWrapper<>();
        w.eq(BizContest::getValidState, 1);
        w.eq(BizContest::getContestState, PublishStatus.PUBLISHED.getValue());
        if (!isSuperTenant && tenantId != null) {
            w.apply("JSON_CONTAINS(contest_tenants, CAST({0} AS JSON))", tenantId);
        }
        if (contestId != null) {
            w.eq(BizContest::getId, contestId);
        }
        return contestMapper.selectList(w).stream().map(BizContest::getId).filter(Objects::nonNull).toList();
    }

    private static LambdaQueryWrapper<BizContestWork> workBaseWrapper(List<Long> contestIds, Long tenantId, boolean isSuperTenant) {
        LambdaQueryWrapper<BizContestWork> w = new LambdaQueryWrapper<BizContestWork>()
                .in(BizContestWork::getContestId, contestIds)
                .eq(BizContestWork::getIsLatest, true)
                .eq(BizContestWork::getValidState, 1);
        if (!isSuperTenant && tenantId != null) {
            w.eq(BizContestWork::getTenantId, tenantId);
        }
        return w;
    }

    private static void applyRegistrationTimeRange(LambdaQueryWrapper<BizContestRegistration> w, LocalDateTime[] range) {
        if (range != null) {
            w.ge(BizContestRegistration::getRegistrationTime, range[0]);
            w.le(BizContestRegistration::getRegistrationTime, range[1]);
        }
    }

    private static void applyWorkSubmitTimeRange(LambdaQueryWrapper<BizContestWork> w, LocalDateTime[] range) {
        if (range != null) {
            w.ge(BizContestWork::getSubmitTime, range[0]);
            w.le(BizContestWork::getSubmitTime, range[1]);
        }
    }

    /**
     * 时间范围：本月 / 本季 / 本年；all 或空为不限
     */
    private static LocalDateTime[] resolveTimeRange(String timeRange) {
        if (!StringUtils.hasText(timeRange) || "all".equalsIgnoreCase(timeRange.trim())) {
            return null;
        }
        LocalDateTime now = LocalDateTime.now();
        LocalDate today = now.toLocalDate();
        LocalDateTime start;
        switch (timeRange.trim().toLowerCase()) {
            case "month" -> {
                LocalDate first = today.withDayOfMonth(1);
                start = LocalDateTime.of(first, LocalTime.MIN);
            }
            case "quarter" -> {
                int m = today.getMonthValue();
                int qStart = ((m - 1) / 3) * 3 + 1;
                LocalDate first = today.withMonth(qStart).withDayOfMonth(1);
                start = LocalDateTime.of(first, LocalTime.MIN);
            }
            case "year" -> {
                LocalDate first = today.withDayOfYear(1);
                start = LocalDateTime.of(first, LocalTime.MIN);
            }
            default -> {
                return null;
            }
        }
        return new LocalDateTime[]{start, now};
    }

    private List<Map<String, Object>> buildMonthlyTrend(List<Long> contestIds, Long tenantId, boolean isSuperTenant) {
        List<Map<String, Object>> regRows = analyticsMapper.selectMonthlyRegistrationCounts(contestIds, tenantId, isSuperTenant);
        List<Map<String, Object>> workRows = analyticsMapper.selectMonthlyWorkCounts(contestIds, tenantId, isSuperTenant);

        Map<String, Long> regMap = regRows.stream().collect(Collectors.toMap(
                r -> Objects.toString(r.get("ym"), ""),
                r -> toLong(r.get("cnt")),
                Long::sum
        ));
        Map<String, Long> workMap = workRows.stream().collect(Collectors.toMap(
                r -> Objects.toString(r.get("ym"), ""),
                r -> toLong(r.get("cnt")),
                Long::sum
        ));

        YearMonth now = YearMonth.now();
        List<Map<String, Object>> trend = new ArrayList<>();
        for (int i = 5; i >= 0; i--) {
            YearMonth ym = now.minusMonths(i);
            String key = ym.toString();
            Map<String, Object> row = new LinkedHashMap<>();
            row.put("month", key);
            row.put("registrations", regMap.getOrDefault(key, 0L).intValue());
            row.put("works", workMap.getOrDefault(key, 0L).intValue());
            trend.add(row);
        }
        return trend;
    }

    private List<Map<String, Object>> buildContestComparison(List<Long> contestIds, Long tenantId, boolean isSuperTenant,
                                                             LocalDateTime[] range) {
        List<Map<String, Object>> list = new ArrayList<>();
        for (Long cid : contestIds) {
            BizContest c = contestMapper.selectById(cid);
            if (c == null) {
                continue;
            }
            Map<String, Object> raw = analyticsMapper.selectContestComparisonRow(cid, tenantId, isSuperTenant,
                    range != null ? range[0] : null, range != null ? range[1] : null);
            if (raw == null) {
                continue;
            }
            long registrations = toLong(raw.get("registrations"));
            long passedRegs = toLong(raw.get("passedRegs"));
            long workCount = toLong(raw.get("workCount"));
            long reviewedCount = toLong(raw.get("reviewedCount"));
            long awardedCount = toLong(raw.get("awardedCount"));

            double passRate = registrations > 0 ? round2(passedRegs * 100.0 / registrations) : 0;
            double submitRate = passedRegs > 0 ? round2(workCount * 100.0 / passedRegs) : 0;
            double reviewRate = workCount > 0 ? round2(reviewedCount * 100.0 / workCount) : 0;
            double awardRate = workCount > 0 ? round2(awardedCount * 100.0 / workCount) : 0;

            Map<String, Object> row = new LinkedHashMap<>();
            row.put("contestId", cid);
            row.put("contestName", c.getContestName());
            row.put("registrations", (int) registrations);
            row.put("passRate", passRate);
            row.put("submitRate", submitRate);
            row.put("reviewRate", reviewRate);
            row.put("awardRate", awardRate);
            BigDecimal avg = toBigDecimal(raw.get("avgScore"));
            row.put("avgScore", avg != null ? round2(avg.doubleValue()) : null);
            list.add(row);
        }
        return list;
    }

    private List<Map<String, Object>> buildAwardDistribution(List<Long> contestIds, Long tenantId, boolean isSuperTenant) {
        List<Map<String, Object>> rows = analyticsMapper.selectAwardDistribution(contestIds, tenantId, isSuperTenant);
        Map<String, Long> merged = new LinkedHashMap<>();
        for (Map<String, Object> r : rows) {
            String level = Objects.toString(r.get("awardLevel"), "");
            String rawName = r.get("awardNameRaw") != null ? Objects.toString(r.get("awardNameRaw"), "") : "";
            String label = awardDisplayName(level, rawName);
            long cnt = toLong(r.get("cnt"));
            merged.merge(label, cnt, Long::sum);
        }
        long total = merged.values().stream().mapToLong(Long::longValue).sum();
        List<Map<String, Object>> out = new ArrayList<>();
        for (Map.Entry<String, Long> e : merged.entrySet()) {
            Map<String, Object> item = new LinkedHashMap<>();
            item.put("awardName", e.getKey());
            item.put("count", e.getValue().intValue());
            double pct = total > 0 ? Math.round(e.getValue() * 10000.0 / total) / 100.0 : 0;
            item.put("percentage", pct);
            out.add(item);
        }
        return out;
    }

    private static String awardDisplayName(String level, String awardName) {
        if (StringUtils.hasText(awardName)) {
            return awardName.trim();
        }
        if (!StringUtils.hasText(level)) {
            return "其他";
        }
        return switch (level) {
            case "first" -> "一等奖";
            case "second" -> "二等奖";
            case "third" -> "三等奖";
            case "excellent" -> "优秀奖";
            default -> level;
        };
    }

    private static Map<String, Object> emptySummary() {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("totalContests", 0);
        m.put("totalRegistrations", 0);
        m.put("passedRegistrations", 0);
        m.put("totalWorks", 0);
        m.put("reviewedWorks", 0);
        m.put("awardedWorks", 0);
        return m;
    }

    private static Map<String, Object> emptyFunnel() {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("registered", 0);
        m.put("passed", 0);
        m.put("submitted", 0);
        m.put("reviewed", 0);
        m.put("awarded", 0);
        return m;
    }

    private static Map<String, Object> emptyEfficiency() {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("avgReviewDays", 0);
        m.put("dailyReviewCount", 0);
        m.put("pendingAssignments", 0);
        m.put("avgScoreStddev", 0);
        return m;
    }

    private static long toLong(Object o) {
        if (o == null) {
            return 0L;
        }
        if (o instanceof Number n) {
            return n.longValue();
        }
        try {
            return Long.parseLong(o.toString());
        } catch (NumberFormatException e) {
            return 0L;
        }
    }

    private static BigDecimal toBigDecimal(Object o) {
        if (o == null) {
            return null;
        }
        if (o instanceof BigDecimal b) {
            return b;
        }
        if (o instanceof Number n) {
            return BigDecimal.valueOf(n.doubleValue());
        }
        try {
            return new BigDecimal(o.toString());
        } catch (Exception e) {
            return null;
        }
    }

    private static double round2(double v) {
        return Math.round(v * 100.0) / 100.0;
    }
}
