using Microsoft.Extensions.Logging;
using QYZH.InteractiveMagazine.IService;
using QYZH.InteractiveMagazine.Models.Common;
using QYZH.InteractiveMagazine.Models.Dto;
using QYZH.InteractiveMagazine.Models.Dto.Points;
using QYZH.InteractiveMagazine.Models.Dto.UserAnswerTaskService;
using QYZH.InteractiveMagazine.Models.Entity;
using QYZH.InteractiveMagazine.Models.Enum;
using QYZH.InteractiveMagazine.Repository;

namespace QYZH.InteractiveMagazine.Service;

/// <summary>
/// 用户作答任务服务实现
/// </summary>
public class UserAnswerTaskService(
    BaseRepository<JournalPageTaskUserAnswer> answerRepository,
    BaseRepository<Journal> journalRepository,
    BaseRepository<JournalCatalog> catalogRepository,
    BaseRepository<JournalPage> pageRepository,
    BaseRepository<JournalPageTask> taskRepository,    
    BaseRepository<UserJournal> userJournalRepository,
    BaseRepository<Users> usersRepository,
    BaseRepository<CheckInRecord> checkInRecordRepository,
    IPointsService pointsService,
    ILogger<UserAnswerTaskService> logger)
    : BaseRepository<JournalPageTaskUserAnswer>, IUserAnswerTaskService
{
    /// <summary>
    /// ============================================
    /// 方法思路：
    /// 1. 批量预加载策略：为避免N+1查询问题，先按期刊维度批量加载所有关联数据
    /// 2. 期刊来源：
    ///    - 用户绑定的杂志（UserJournal表）
    ///    - 用户有作答记录的杂志
    ///    合并去重后返回
    /// 3. 跨页题目处理核心：
    ///    - 跨页题目定义：JournalPageTask.Id != GroupId（同一个GroupId下有多个任务）
    ///    - 进度计算：按GroupId去重，已完成条件是该GroupId下所有任务都完成（Status=1）
    ///    - 积分计算：按GroupId去重，每个GroupId只计算一次积分（取第一个任务的积分）
    /// 4. 状态判定逻辑：
    ///    - 未开始：无任何作答记录
    ///    - 进行中：有作答记录但未全部完成
    ///    - 已完成：所有GroupId都完成
    /// 5. 用户未作答任何任务时：
    ///    - 仍返回绑定杂志的进度信息
    ///    - 进度、成长值、积分为0
    ///    - 连续打卡、剩余篇数正常计算
    /// ============================================
    /// </summary>
    /// <param name="userId">用户ID</param>
    /// <returns>用户所有期刊的学习进度列表</returns>
    public async Task<List<JournalProgressOutput>> GetJournalProgressAsync(long userId)
    {
        // ============================================
        // 跨页题目处理核心逻辑说明：
        // 1. 跨页题目定义：JournalPageTask.Id != GroupId
        //    - Id 是任务的唯一标识
        //    - GroupId 是跨页题目的分组标识，同一个跨页题目的所有任务共享同一个GroupId
        // 2. 进度计算：按 GroupId 去重
        //    - 已完成条件：该 GroupId 下所有任务都完成（Status=1）
        // 3. 积分计算：按 GroupId 去重，每个 GroupId 只计算一次积分
        // 4. 状态判定：
        //    - 未开始：无任何 GroupId 被作答
        //    - 进行中：有 GroupId 被作答但未全部完成
        //    - 已完成：所有 GroupId 都完成
        // ============================================

        #region 1. 查询用户作答记录
        // 1. 查询用户的所有作答记录
        var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
            .Where(a => a.UserId == userId && !a.IsDeleted)
            .ToListAsync();
        #endregion

        #region 2. 收集期刊ID（绑定+作答去重）
        // ============================================
        // 2. 收集所有需要展示的期刊ID
        //    - 用户绑定的杂志（UserJournal表）
        //    - 用户有作答记录的杂志
        //    合并去重
        // ============================================
        var boundJournalIds = await userJournalRepository.Context.Queryable<UserJournal>()
            .Where(uj => uj.UserId == userId && !uj.IsDeleted)
            .Select(uj => uj.JournalId)
            .ToListAsync();

        var answeredJournalIds = userAnswers.Select(a => a.JournalId).Distinct().ToList();

        var journalIds = boundJournalIds
            .Concat(answeredJournalIds)
            .Distinct()
            .ToList();

        if (journalIds.Count == 0)
        {
            return new List<JournalProgressOutput>();
        }
        #endregion

        #region 3. 批量预加载关联数据
        // 3. 批量预加载关联实体（按期刊维度全量加载，避免N+1查询）
        var journals = await journalRepository.Context.Queryable<Journal>()
            .Where(j => journalIds.Contains(j.Id) && !j.IsDeleted)
            .ToListAsync();

        // 加载这些期刊的一级目录（ParentId == 0 表示一级目录）
        var allCatalogs = await catalogRepository.Context.Queryable<JournalCatalog>()
            .Where(c => journalIds.Contains(c.JournalId) && c.ParentId == 0 && !c.IsDeleted)
            .ToListAsync();

        // 加载这些期刊的所有任务
        var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
            .Where(t => journalIds.Contains(t.JournalId) && !t.IsDeleted)
            .ToListAsync();

        // 加载这些期刊的所有页面（用于计算剩余篇数）
        var allPages = await pageRepository.Context.Queryable<JournalPage>()
            .Where(p => journalIds.Contains(p.JournalId) && !p.IsDeleted)
            .ToListAsync();
        #endregion

        #region 4. 查询连续打卡天数
        // 4. 查询用户连续打卡天数
        var consecutiveDays = await CalculateConsecutiveDaysAsync(userId);
        #endregion

        #region 5. 提取用户已作答/已完成任务ID集合
        // 5. 提取用户已作答的任务ID集合（包含进行中和已完成）
        var userAnsweredTaskIds = userAnswers.Select(a => a.JournalPageTaskId).ToHashSet();
        var userAnsweredPageIds = userAnswers.Select(a => a.JournalPageId).ToHashSet();

        // 用户已完成的任务ID集合（Status=1表示已完成）
        var userCompletedTaskIds = userAnswers.Where(a => a.Status == (int)UserAnswerStatusEnum.Complete).Select(a => a.JournalPageTaskId).ToHashSet();
        #endregion

        #region 6. 按期刊分组聚合并返回结果
        // 6. 按期刊分组聚合
        var result = new List<JournalProgressOutput>();

        foreach (var journal in journals)
        {
            // 该期刊下所有任务
            var journalTasks = allTasks.Where(t => t.JournalId == journal.Id).ToList();

            // 该期刊的一级栏目数
            var catalogCount = allCatalogs.Count(c => c.JournalId == journal.Id);

            // 【跨页题目-进度计算】总任务数：按 GroupId 去重，跨页题目只算一个
            var totalTaskCount = journalTasks.Select(t => t.GroupId).Distinct().Count();

            // 【跨页题目-已完成判断】已完成任务数：按 GroupId 去重
            // 关键：必须该 GroupId 下所有任务都完成，才算该 GroupId 完成
            var completedTaskCount = journalTasks
                .GroupBy(t => t.GroupId)
                .Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
                .Count();

            // 【跨页题目-积分计算】获取已完成的 GroupId 列表
            var completedGroupIds = journalTasks
                .GroupBy(t => t.GroupId)
                .Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
                .Select(g => g.Key)
                .ToList();

            // 【跨页题目-积分去重】每个已完成的 GroupId 只取第一个任务的积分
            // 原因：同一个跨页题目的多个任务只应该计算一次积分
            var distinctCompletedTasks = journalTasks
                .Where(t => completedGroupIds.Contains(t.GroupId))
                .GroupBy(t => t.GroupId)
                .Select(g => g.First())
                .ToList();

            // 累计已完成的跨页题目的总成长值和总积分
            var totalGrowthPoints = distinctCompletedTasks.Sum(t => t.GrowthPoint ?? 0);
            var totalPoints = distinctCompletedTasks.Sum(t => t.Points);

            // 【跨页题目-进度百分比计算】
            var totalGroupIds = journalTasks.Select(t => t.GroupId).Distinct().Count();
            var completedGroupIdCount = journalTasks
                .GroupBy(t => t.GroupId)
                .Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
                .Count();

            var progress = totalGroupIds > 0
                ? Math.Round((double)completedGroupIdCount / totalGroupIds * 100, 2)
                : 0;

            // 剩余篇数：该期刊总页数 - 用户已作答页数
            var journalPages = allPages.Where(p => p.JournalId == journal.Id).ToList();
            var answeredPageCount = journalPages.Count(p => userAnsweredPageIds.Contains(p.Id));           
            var remainingPages = journalPages.Count - answeredPageCount;

            result.Add(new JournalProgressOutput
            {
                JournalId = journal.Id,
                JournalName = journal.Name ?? string.Empty,
                CatalogCount = catalogCount,
                TotalTaskCount = totalTaskCount,
                CompletedTaskCount = completedTaskCount,
                TotalGrowthPoints = totalGrowthPoints,
                TotalPoints = totalPoints,
                Progress = progress,
                RemainingPages = remainingPages,
                ConsecutiveDays = consecutiveDays
            });
        }

        return result;
        #endregion
    }

    /// <summary>
    /// ============================================
    /// 方法思路：计算用户连续打卡天数
    /// 1. 获取今天和昨天的日期
    /// 2. 查询昨天是否有打卡记录
    /// 3. 如果昨天没有打卡记录，返回0（连续打卡中断）
    /// 4. 如果昨天有打卡记录：
    ///    - 查询今天是否有打卡记录
    ///    - 今天有记录：返回今天的连续天数
    ///    - 今天无记录：返回昨天的连续天数
    /// ============================================
    /// </summary>
    /// <param name="userId">用户ID</param>
    /// <returns>连续打卡天数</returns>
    private async Task<int> CalculateConsecutiveDaysAsync(long userId)
    {
        var today = DateTime.Now.Date;
        var yesterday = today.AddDays(-1);

        // 查询昨天是否有打卡记录
        var yesterdayRecord = await checkInRecordRepository.Context.Queryable<CheckInRecord>()
            .Where(r => r.UserId == userId && !r.IsDeleted && r.CheckInDate.Date == yesterday)
            .FirstAsync();

        // 如果昨天没有打卡记录，连续天数为0（连续打卡中断）
        if (yesterdayRecord == null)
        {
            return 0;
        }

        // 昨天有打卡记录，检查今天是否已打卡
        var todayRecord = await checkInRecordRepository.Context.Queryable<CheckInRecord>()
            .Where(r => r.UserId == userId && !r.IsDeleted && r.CheckInDate.Date == today)
            .FirstAsync();

        // 今天已打卡：返回今天的连续天数；今天未打卡：返回昨天的连续天数
        return todayRecord?.ConsecutiveDays ?? yesterdayRecord.ConsecutiveDays;
    }

    /// <summary>
    /// ============================================
    /// 方法思路：获取目录列表（按一级目录分组展示）
    /// 1. 批量预加载：该期刊下所有目录、页面、任务、用户作答记录
    /// 2. 目录结构处理：
    ///    - 一级目录：ParentId=0 且 Type=Unit
    ///    - 二级目录：ParentId=一级目录Id 且 Type=Unit
    ///    - 文章目录：Type=Page
    /// 3. 【关键】如果一级目录下没有二级栏目（Type=Unit的子目录），
    ///    则该一级目录不参与任何计算，直接跳过
    /// 4. 跨页题目处理：
    ///    - 状态判断：按GroupId去重，已完成条件是该GroupId下所有任务都完成（Status=1）
    ///    - 积分计算：按GroupId去重，每个GroupId只计算一次积分
    ///    - 已作答判断：只要有一个任务作答就算该GroupId已作答（包含进行中和已完成）
    /// 5. 状态判定：
    ///    - 未开始：无任何GroupId被作答
    ///    - 进行中：有GroupId被作答但未全部完成
    ///    - 已完成：所有GroupId都完成
    /// ============================================
    /// </summary>
    /// <param name="journalId">期刊ID</param>
    /// <param name="userId">用户ID</param>
    /// <returns>目录列表（按一级目录分组）</returns>
    public async Task<List<CatalogListOutput>> GetCatalogListAsync(long journalId, long userId)
    {
        #region 1. 查询期刊
        // 1. 查询期刊
        var journal = await journalRepository.Context.Queryable<Journal>()
            .Where(j => j.Id == journalId && !j.IsDeleted)
            .FirstAsync();

        if (journal == null)
        {
            return new List<CatalogListOutput>();
        }
        #endregion

        #region 2. 批量加载关联数据
        // 2. 批量加载关联数据
        var allCatalogs = await catalogRepository.Context.Queryable<JournalCatalog>()
            .Where(c => c.JournalId == journalId && !c.IsDeleted)
            .ToListAsync();

        var allPages = await pageRepository.Context.Queryable<JournalPage>()
            .Where(p => p.JournalId == journalId && !p.IsDeleted)
            .ToListAsync();

        var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
            .Where(t => t.JournalId == journalId && !t.IsDeleted)
            .ToListAsync();

        var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
            .Where(a => a.JournalId == journalId && a.UserId == userId && !a.IsDeleted)
            .ToListAsync();
        #endregion

        #region 3. 用户已作答/已完成任务ID集合
        // 3. 用户已作答的任务ID集合（包含进行中和已完成）
        var userAnsweredTaskIds = userAnswers.Select(a => a.JournalPageTaskId).ToHashSet();

        // 用户已完成的任务ID集合（Status=1表示已完成）
        var userCompletedTaskIds = userAnswers.Where(a => a.Status == (int)UserAnswerStatusEnum.Complete).Select(a => a.JournalPageTaskId).ToHashSet();
        #endregion

        #region 4. 按一级目录分组计算
        // 4. 按一级目录分组
        var firstLevelCatalogs = allCatalogs.Where(c => c.ParentId == 0 && c.Type == JournalCatalogTypeEnum.Unit).OrderBy(c => c.Sort).ToList();
        var result = new List<CatalogListOutput>();

        foreach (var firstCatalog in firstLevelCatalogs)
        {
            // 该一级目录下的 Type=Page 子目录（即文章）
            var pageCatalogs = allCatalogs
                .Where(c => c.ParentId == firstCatalog.Id && c.Type == JournalCatalogTypeEnum.Page)
                .OrderBy(c => c.Sort)
                .ToList();

            var pageCatalogIds = pageCatalogs.Select(c => c.Id).ToHashSet();

            // 获取所有文章下的页面ID
            var allArticlePageIds = allPages
                .Where(p => pageCatalogIds.Contains(p.JournalCatalogId))
                .Select(p => p.Id)
                .ToHashSet();

            // 该一级目录下的二级单元目录（Type=Unit 的子目录）
            var secondLevelCatalogs = allCatalogs
                .Where(c => c.ParentId == firstCatalog.Id && c.Type == JournalCatalogTypeEnum.Unit)
                .OrderBy(c => c.Sort)
                .ToList();

            // ============================================
            // 文章篇数计算逻辑：
            // - 有二级栏目：累加所有二级栏目下的文章数 + 一级栏目下直接的文章数
            // - 没有二级栏目：计算一级栏目下直接的文章数（Type=Page）
            // ============================================
            int articleCount = 0;
            var children = new List<SecondCatalogOutput>();

            if (secondLevelCatalogs.Count > 0)
            {
                // 有二级单元：按二级单元分组
                // 先统计一级栏目下直接的文章数（Type=Page，ParentId=一级栏目Id）
                articleCount = pageCatalogs.Count;

                foreach (var secondCatalog in secondLevelCatalogs)
                {
                    // 二级单元下的 Type=Page 子目录
                    var subPageCatalogs = allCatalogs
                        .Where(c => c.ParentId == secondCatalog.Id && c.Type == JournalCatalogTypeEnum.Page)
                        .ToList();

                    // 累加该二级栏目下的文章数
                    articleCount += subPageCatalogs.Count;

                    var subPageCatalogIds = subPageCatalogs.Select(c => c.Id).ToHashSet();

                    // 二级单元下的所有页面
                    var secondLevelPageIds = allPages
                        .Where(p => subPageCatalogIds.Contains(p.JournalCatalogId))
                        .Select(p => p.Id)
                        .ToHashSet();

                    // 二级单元下的所有任务
                    var secondLevelTasks = allTasks.Where(t => secondLevelPageIds.Contains(t.JournalPageId)).ToList();

                    // 判定状态（Status=0进行中，Status=1已完成，跨页题目按GroupId去重）
                    // 跨页题目必须所有任务都完成，才算该 GroupId 已完成
                    string status;
                    if (secondLevelTasks.Count == 0)
                    {
                        status = "未开始";
                    }
                    else
                    {
                        // 总 GroupId 数（跨页题目只算一个）
                        var totalGroupCount = secondLevelTasks.Select(t => t.GroupId).Distinct().Count();
                        // 已完成 GroupId 数（该 GroupId 下所有任务都完成才算完成）
                        var completedGroupCount = secondLevelTasks
                            .GroupBy(t => t.GroupId)
                            .Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
                            .Count();
                        // 【关键】已作答 GroupId 数：只要有一个任务作答就算该 GroupId 已作答（包含进行中和已完成）
                        // 这个字段用于判断状态：未开始 / 进行中 / 已完成
                        var answeredGroupCount = secondLevelTasks
                            .Where(t => userAnsweredTaskIds.Contains(t.Id))
                            .Select(t => t.GroupId)
                            .Distinct()
                            .Count();

                        // 状态判定逻辑：
                        // - answeredGroupCount == 0：没有任何 GroupId 被作答 → 未开始
                        // - completedGroupCount == totalGroupCount：所有 GroupId 都完成 → 已完成
                        // - 其他：有 GroupId 被作答但未全部完成 → 进行中
                        if (answeredGroupCount == 0)
                            status = "未开始";
                        else if (completedGroupCount == totalGroupCount)
                            status = "已完成";
                        else
                            status = "进行中";
                    }

                    // 计算该二级目录的总成长值和总积分（跨页题目按GroupId去重）
                    var distinctSecondLevelTasks = secondLevelTasks
                        .GroupBy(t => t.GroupId)
                        .Select(g => g.First())
                        .ToList();
                    var totalGrowthPoints = distinctSecondLevelTasks.Sum(t => t.GrowthPoint) ?? 0;
                    var totalPoints = distinctSecondLevelTasks.Sum(t => t.Points);

                    children.Add(new SecondCatalogOutput
                    {
                        CatalogId = secondCatalog.Id,
                        CatalogName = secondCatalog.Name ?? string.Empty,
                        TotalPoints = totalPoints,
                        TotalGrowthPoints = totalGrowthPoints,
                        Status = status
                    });
                }
            }
            else
            {
                // 没有二级单元：只统计一级栏目下直接的文章数，Children为空集合
                articleCount = pageCatalogs.Count;
                // Children保持为空集合，不添加任何子项
            }

            // 计算该一级目录的总成长值和总积分（基于所有文章页面的任务）
            var firstLevelTasks = allTasks.Where(t => allArticlePageIds.Contains(t.JournalPageId)).ToList();

            result.Add(new CatalogListOutput
            {
                CatalogId = firstCatalog.Id,
                CatalogName = firstCatalog.Name ?? string.Empty,
                JournalName = journal.Name ?? string.Empty,
                ArticleCount = articleCount,
                Children = children
            });
        }
        #endregion

        #region 5. 返回结果
        return result;
        #endregion
    }

    /// <summary>
    /// ============================================
    /// 方法思路：获取一级目录概览（含二级目录详情和任务完成状态）
    /// 1. 批量预加载：该期刊下所有目录、页面、任务、用户作答记录
    /// 2. 获取该一级目录下的所有文章目录（Type=Page）
    /// 3. 跨页题目处理：
    ///    - 总任务数：按GroupId去重（跨页题目只算一个）
    ///    - 状态判断：按GroupId去重，已完成条件是该GroupId下所有任务都完成（Status=1）
    ///    - 已作答判断：只要有一个任务作答就算该GroupId已作答
    /// 4. 状态格式："已完成数/总数 已完成/进行中/未开始"
    ///    例如："2/3 进行中" 表示3个GroupId中已完成2个
    /// ============================================
    /// </summary>
    /// <param name="catalogId">一级目录ID</param>
    /// <param name="userId">用户ID</param>
    /// <returns>一级目录概览信息</returns>
    public async Task<CatalogOverviewOutput> GetCatalogOverviewAsync(long catalogId, long userId)
    {
        #region 1. 查询一级目录
        // 1. 查询一级目录
        var firstCatalog = await catalogRepository.Context.Queryable<JournalCatalog>()
            .Where(c => c.Id == catalogId && !c.IsDeleted)
            .FirstAsync();

        if (firstCatalog == null)
        {
            return null;
        }
        #endregion

        #region 2. 批量加载关联数据
        // 2. 批量加载关联数据
        var allCatalogs = await catalogRepository.Context.Queryable<JournalCatalog>()
            .Where(c => c.JournalId == firstCatalog.JournalId && !c.IsDeleted)
            .ToListAsync();

        var allPages = await pageRepository.Context.Queryable<JournalPage>()
            .Where(p => p.JournalId == firstCatalog.JournalId && !p.IsDeleted)
            .ToListAsync();

        var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
            .Where(t => t.JournalId == firstCatalog.JournalId && !t.IsDeleted)
            .ToListAsync();

        var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
            .Where(a => a.JournalId == firstCatalog.JournalId && a.UserId == userId && !a.IsDeleted)
            .ToListAsync();
        #endregion

        #region 3. 用户已作答/已完成任务ID集合
        // 3. 用户已作答的任务ID集合（包含进行中和已完成）
        var userAnsweredTaskIds = userAnswers.Select(a => a.JournalPageTaskId).ToHashSet();

        // 用户已完成的任务ID集合（Status=1表示已完成）
        var userCompletedTaskIds = userAnswers.Where(a => a.Status == (int)UserAnswerStatusEnum.Complete).Select(a => a.JournalPageTaskId).ToHashSet();
        #endregion

        #region 4. 获取一级栏目下所有Type=Page子目录
        // 4. 获取该一级目录下所有 Type=Page 的子目录（即文章/页面目录）
        var pageCatalogs = allCatalogs
            .Where(c => c.ParentId == firstCatalog.Id && c.Type == JournalCatalogTypeEnum.Page)
            .OrderBy(c => c.Sort)
            .ToList();

        // 5. 文章篇数 = Type=Page 的子目录数量
        var articleCount = pageCatalogs.Count;
        #endregion

        #region 5. 总任务数与子目录详情
        // 6. 获取所有文章目录下的页面
        var pageCatalogIds = pageCatalogs.Select(c => c.Id).ToHashSet();
        var allLevelPages = allPages.Where(p => pageCatalogIds.Contains(p.JournalCatalogId)).ToList();
        var allLevelPageIds = allLevelPages.Select(p => p.Id).ToHashSet();

        // 7. 总任务数（按 GroupId 去重，跨页题目只算一个）
        var totalTaskCount = allTasks
            .Where(t => allLevelPageIds.Contains(t.JournalPageId))
            .Select(t => t.GroupId)
            .Distinct()
            .Count();

        // 8. 构建子目录详情列表
        var subCatalogs = new List<SubCatalogDetailOutput>();

        foreach (var pageCatalog in pageCatalogs)
        {
            // 该文章下的所有页面
            var articlePages = allPages
                .Where(p => p.JournalCatalogId == pageCatalog.Id)
                .OrderBy(p => p.PageNum)
                .ToList();

            var articlePageIds = articlePages.Select(p => p.Id).ToHashSet();

            // 该文章下的所有任务
            var articleTasks = allTasks.Where(t => articlePageIds.Contains(t.JournalPageId)).ToList();
            // 总 GroupId 数（按 GroupId 去重，跨页题目只算一个）
            var totalGroupCount = articleTasks.Select(t => t.GroupId).Distinct().Count();

            // 页码范围
            var startPage = articlePages.Count > 0 ? articlePages.Min(p => p.PageNum) : 0;
            var endPage = articlePages.Count > 0 ? articlePages.Max(p => p.PageNum) : 0;

            // 状态格式：1/2 进行中、2/2 已完成、0/2 未开始（跨页题目按GroupId去重）
            // 跨页题目必须所有任务都完成，才算该 GroupId 已完成
            string status;
            if (totalGroupCount == 0)
            {
                status = $"0/0 未开始";
            }
            else
            {
                // 已完成 GroupId 数（该 GroupId 下所有任务都完成才算完成）
                var completedGroupCount = articleTasks
                    .GroupBy(t => t.GroupId)
                    .Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
                    .Count();
                // 已作答 GroupId 数（只要有一个任务作答就算已作答，包含进行中和已完成）
                var answeredGroupCount = articleTasks
                    .Where(t => userAnsweredTaskIds.Contains(t.Id))
                    .Select(t => t.GroupId)
                    .Distinct()
                    .Count();
                
                if (answeredGroupCount == 0)
                    status = $"0/{totalGroupCount} 未开始";
                else if (completedGroupCount == totalGroupCount)
                    status = $"{completedGroupCount}/{totalGroupCount} 已完成";
                else
                    status = $"{completedGroupCount}/{totalGroupCount} 进行中";
            }

            subCatalogs.Add(new SubCatalogDetailOutput
            {
                CatalogId = pageCatalog.Id,
                SecondLevelCatalogName = pageCatalog.Name ?? string.Empty,
                StartPage = startPage,
                EndPage = endPage,
                TaskCount = totalGroupCount,
                Status = status
            });
        }
        #endregion

        #region 6. 返回结果
        return new CatalogOverviewOutput
        {
            CatalogId = firstCatalog.Id,
            FirstLevelCatalogName = firstCatalog.Name ?? string.Empty,
            PageCount = articleCount,
            TotalTaskCount = totalTaskCount,
            SubCatalogs = subCatalogs
        };
        #endregion
    }

    /// <summary>
    /// ============================================
    /// 方法思路：获取用户答题任务列表
    /// 1. 根据二级栏目ID查询栏目基本信息（一级栏目名称、栏目名称、页码范围
    /// 2. 按GroupId分组，每个GroupId对应一个任务项
    /// 3. 每个任务项包含：任务名称、总成长值、总积分、状态
    /// 4. 【跨页题目处理】
    ///    - 是否跨页：Id != GroupId
    ///    - 任务名称：取 Id == GroupId 的主任务的Task字段
    ///    - 成长值/积分：取主任务的，每个GroupId只算一次
    ///    - 状态判断：必须该GroupId下所有任务都完成(Status=1)才算该GroupId完成
    /// ============================================
    /// <param name="catalogId">二级栏目ID/param>
    /// <param name="userId">用户ID</param>
    /// </summary>
    /// <returns>用户答题任务列表信息</returns>
    public async Task<UserAnswerTaskListOutput> GetUserAnswerTaskListAsync(long catalogId, long userId)
    {
        #region 1. 查询二级栏目目录

        // 1. 查询目录（二级栏目）
        var catalog = await catalogRepository.Context.Queryable<JournalCatalog>()
            .Where(c => c.Id == catalogId && !c.IsDeleted)
            .FirstAsync();

        if (catalog == null)
        {
            return null;
        }

        #endregion

        #region 2. 获取一级栏目名称

        // ============================================
        // 【获取一级栏目名称】
        // 通过 ParentId 关联查询一级栏目名称
        // ============================================
        string firstLevelCatalogName = string.Empty;
        if (catalog.ParentId > 0)
        {
            var firstLevelCatalog = await catalogRepository.Context.Queryable<JournalCatalog>()
                .Where(c => c.Id == catalog.ParentId && !c.IsDeleted)
                .FirstAsync();
            firstLevelCatalogName = firstLevelCatalog?.Name ?? string.Empty;
        }

        #endregion

        #region 3. 批量加载关联数据

        // ============================================
        // 2. 批量加载关联数据
        // ============================================
        // 该栏目下的所有页面
        var allPages = await pageRepository.Context.Queryable<JournalPage>()
            .Where(p => p.JournalCatalogId == catalogId && !p.IsDeleted)
            .ToListAsync();

        // 该期刊下所有任务（用于跨页题目关联查询）
        var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
            .Where(t => t.JournalId == catalog.JournalId && !t.IsDeleted)
            .ToListAsync();

        // 该用户在该期刊下所有作答记录
        var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
            .Where(a => a.JournalId == catalog.JournalId && a.UserId == userId && !a.IsDeleted)
            .ToListAsync();

        #endregion

        #region 4. 获取该目录下的页面ID及任务

        // 3. 获取该目录下的所有页面ID
        var pageIds = allPages.Select(p => p.Id).ToHashSet();

        // 4. 该目录下的所有任务（仅用于筛选哪些GroupId属于当前栏目）
        var catalogTasks = allTasks.Where(t => pageIds.Contains(t.JournalPageId)).ToList();

        // 【跨页题目-关键】
        // 跨页题目的任务可能分布在不同页面（甚至不同栏目）下
        // 所以需要从整个期刊的所有任务(allTasks)中按GroupId获取完整的任务组
        // 否则无法正确判断跨页题目是否全部完成
        var catalogGroupIds = catalogTasks.Select(t => t.GroupId).ToHashSet();

        // 5. 用户已作答的任务ID集合（包含进行中和已完成）
        var userAnsweredTaskIds = userAnswers.Select(a => a.JournalPageTaskId).ToHashSet();

        // 用户已完成的任务ID集合（Status=1表示已完成）
        var userCompletedTaskIds = userAnswers.Where(a => a.Status == (int)UserAnswerStatusEnum.Complete).Select(a => a.JournalPageTaskId).ToHashSet();

        // 6. 计算页码范围
        var startPage = allPages.Count > 0 ? allPages.Min(p => p.PageNum) : 0;
        var endPage = allPages.Count > 0 ? allPages.Max(p => p.PageNum) : 0;

        #endregion

        #region 5. 按GroupId分组生成任务列表

        // ============================================
        // 【跨页题目-按GroupId分组生成任务列表】
        // 关键：从整个期刊的所有任务中按GroupId取完整分组
        // 每个GroupId对应一个任务项
        // - 任务名称：取 Id == GroupId 的主任务的Task字段
        // - 成长值/积分：取主任务的，每个GroupId只算一次
        // - 状态：
        //   - 未开始：该GroupId下无任何任务被作答
        //   - 已完成：该GroupId下所有任务都完成(Status=1)
        //   - 进行中：有作答但未全部完成
        // ============================================
        var taskItems = new List<UserAnswerTaskItemOutput>();

        // 从整个期刊的所有任务中，筛选出属于当前栏目的GroupId的完整分组
        var groupTasks = allTasks
            .Where(t => catalogGroupIds.Contains(t.GroupId))
            .GroupBy(t => t.GroupId)
            .ToList();

        foreach (var group in groupTasks)
        {
            var groupId = group.Key;

            // 取主任务（Id == GroupId）
            var mainTask = group.FirstOrDefault(t => t.Id == groupId) ?? group.First();

            // 计算该GroupId下所有任务的答题时间（秒）
            var taskIds = group.Select(t => t.Id).ToHashSet();
            var groupAnswerSeconds = userAnswers
                .Where(a => taskIds.Contains(a.JournalPageTaskId))
                .Sum(a => a.AnswerSeconds);
            var groupAnswerMinutes = Math.Round(groupAnswerSeconds / 60.0, 2);

            // 判断该GroupId是否有作答记录
            bool hasAnswer = group.Any(t => userAnsweredTaskIds.Contains(t.Id));

            // 判断该GroupId是否全部完成
            bool isAllCompleted = group.All(t => userCompletedTaskIds.Contains(t.Id));

            // 计算状态
            string taskStatus;
            if (!hasAnswer)
                taskStatus = "未开始";
            else if (isAllCompleted)
                taskStatus = "已完成";
            else
                taskStatus = "进行中";

            taskItems.Add(new UserAnswerTaskItemOutput
            {
                GroupId = groupId,
                TaskName = mainTask.Task ?? string.Empty,
                TotalGrowthPoints = mainTask.GrowthPoint ?? 0,
                TotalPoints = mainTask.Points,
                AnswerMinutes = groupAnswerMinutes,
                Status = taskStatus
            });
        }

        #endregion

        #region 6. 返回结果

        return new UserAnswerTaskListOutput
        {
            FirstLevelCatalogName = firstLevelCatalogName,
            CatalogName = catalog.Name ?? string.Empty,
            StartPage = startPage,
            EndPage = endPage,
            Tasks = taskItems
        };

        #endregion
    }

    /// <summary>
    /// ============================================
    /// 方法思路：获取跨页题目详情
    /// 1. 根据 groupId 查询该分组下的所有任务
    /// 2. 跨页题目判定：
    ///    - 任务数 > 1 → 跨页题目
    ///    - 任务数 == 1 且 Id != groupId → 跨页题目（子任务进入）
    ///    - 任务数 == 1 且 Id == groupId → 普通题目
    /// 3. 图片数组获取逻辑：
    ///    - 已答题：使用用户上传的 PageAnswerUrl 数组
    ///    - 未答题：使用任务的 TaskUrl 数组补齐
    /// 4. 栏目名称获取：
    ///    - 二级栏目名称：通过页面关联的 JournalCatalogId 查询
    ///    - 一级栏目名称：通过二级栏目的 ParentId 查询
    /// 5. 是否可领取积分判断：
    ///    - 任务必须已完成（Status=1）
    ///    - 跨页题目：所有任务都完成才能领取
    ///    - 普通题目：当前任务完成即可领取
    ///    - 且未领取过积分
    /// ============================================
    /// </summary>
    /// <param name="groupId">任务分组ID（GroupId）</param>
    /// <param name="userId">用户ID</param>
    /// <returns>跨页题目详情</returns>
    public async Task<CrossPageTaskOutput> GetCrossPageTaskAsync(long groupId, long userId)
    {
        // 1. 查询该 GroupId 下的所有任务
        var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
            .Where(t => t.GroupId == groupId && !t.IsDeleted)
            .OrderBy(t => t.Id)
            .ToListAsync();

        if (allTasks == null || allTasks.Count == 0)
        {
            return null;
        }

        #region 1. 跨页题目判定

        // ============================================
        // 【跨页题目判定】
        // 判断标准：
        // - 任务数 > 1 → 跨页题目
        // - 任务数 == 1 且 Id != groupId → 跨页题目（子任务进入）
        // - 任务数 == 1 且 Id == groupId → 普通题目
        // ============================================
        bool isCrossPage = allTasks.Count > 1 || (allTasks.Count == 1 && allTasks[0].Id != groupId);

        // 获取主任务（Id == GroupId），用于跨页题目获取任务名称、积分等
        var mainTask = allTasks.FirstOrDefault(t => t.Id == groupId);
        // 获取任一任务作为基础信息（用于栏目查询等）
        var baseTask = allTasks[0];

        #endregion

        #region 2. 获取相关任务列表

        // ============================================
        // 【获取相关任务列表】
        // 跨页题目：allTasks 即为相关任务列表
        // 非跨页题目：单任务列表
        // ============================================
        List<JournalPageTask> relatedTasks = allTasks;

        #endregion

        #region 3. 获取栏目名称（一级/二级）

        // ============================================
        // 【获取栏目名称】
        // 二级栏目名称：通过页面关联的 JournalCatalogId 查询
        // 一级栏目名称：通过二级栏目的 ParentId 查询
        // ============================================
        var page = await pageRepository.Context.Queryable<JournalPage>()
            .Where(p => p.Id == baseTask.JournalPageId && !p.IsDeleted)
            .FirstAsync();

        string catalogName = string.Empty;
        string firstLevelCatalogName = string.Empty;
        if (page != null)
        {
            // 获取二级栏目
            var catalog = await catalogRepository.Context.Queryable<JournalCatalog>()
                .Where(c => c.Id == page.JournalCatalogId && !c.IsDeleted)
                .FirstAsync();
            catalogName = catalog?.Name ?? string.Empty;

            // 获取一级栏目名称
            if (catalog != null && catalog.ParentId > 0)
            {
                var firstLevelCatalog = await catalogRepository.Context.Queryable<JournalCatalog>()
                    .Where(c => c.Id == catalog.ParentId && !c.IsDeleted)
                    .FirstAsync();
                firstLevelCatalogName = firstLevelCatalog?.Name ?? string.Empty;
            }
        }

        #endregion

        #region 4. 获取图片数组（Images）

        // ============================================
        // 【获取图片数组】
        // 核心逻辑：已答题返回 PageAnswerUrl，未答题返回 TaskUrl 补齐
        // 跨页题目：
        //   - 用户答了部分：已答页用 PageAnswerUrl，未答页用 TaskUrl 补齐
        //   - 用户全部作答：全部用 PageAnswerUrl
        //   - 用户未作答：全部用 TaskUrl
        //   - 按主页面页码（PageNum）升序排列，确保拼图顺序正确
        // 非跨页题目：取单条答题记录的 PageAnswerUrl，未答题返回空集合
        // ============================================
        var images = new List<string>();

        // 统一查询用户答题记录（用于图片获取和后续领取积分判断）
        List<JournalPageTaskUserAnswer> userAnswers;
        if (isCrossPage)
        {
            // 跨页题目：查询该 GroupId 下所有用户的答题记录
            userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
                .Where(a => a.JournalPageTaskGroupId == groupId && a.UserId == userId && !a.IsDeleted)
                .OrderBy(a => a.JournalPageTaskId)
                .ToListAsync();

            // ============================================
            // 【跨页题目图片获取逻辑】
            // 1. 收集相关任务所在页面的页码信息
            // 2. 遍历每个相关任务：已答用 PageAnswerUrl，未答用 TaskUrl 补齐
            // 3. 按页码升序排序后输出，确保拼图顺序正确
            // ============================================
            // 查询相关任务对应的页面信息（用于排序）
            var pageIds = relatedTasks.Select(t => t.JournalPageId).Distinct().ToList();
            var pageInfoList = await pageRepository.Context.Queryable<JournalPage>()
                .Where(p => pageIds.Contains(p.Id) && !p.IsDeleted)
                .ToListAsync();
            var pageInfoDict = pageInfoList.ToDictionary(p => p.Id, p => p.PageNum);

            // 组装图片项：(页码, 图片地址)
            var imageItems = new List<(int PageNum, string Url)>();
            foreach (var relatedTask in relatedTasks)
            {
                var pageNum = pageInfoDict.TryGetValue(relatedTask.JournalPageId, out var num) ? num : 0;
                var userAnswer = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == relatedTask.Id);

                string imageUrl;
                if (userAnswer != null && !string.IsNullOrEmpty(userAnswer.PageAnswerUrl))
                {
                    // 已答题：使用 PageAnswerUrl
                    imageUrl = userAnswer.PageAnswerUrl;
                }
                else if (!string.IsNullOrEmpty(relatedTask.TaskUrl))
                {
                    // 未答题或无 PageAnswerUrl：使用任务的 TaskUrl 补齐
                    imageUrl = relatedTask.TaskUrl;
                }
                else
                {
                    // 无任何图片可用，跳过
                    continue;
                }

                imageItems.Add((pageNum, imageUrl));
            }

            // 按页码升序排序后输出图片地址
            images.AddRange(imageItems.OrderBy(x => x.PageNum).Select(x => x.Url));
        }
        else
        {
            // 非跨页题目：查询单条答题记录
            var userAnswer = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
                .Where(a => a.JournalPageTaskId == mainTask.Id && a.UserId == userId && !a.IsDeleted)
                .FirstAsync();

            // 赋值给 userAnswers（用于后续领取积分判断）
            userAnswers = userAnswer != null ? new List<JournalPageTaskUserAnswer> { userAnswer } : new List<JournalPageTaskUserAnswer>();

            if (userAnswer != null && !string.IsNullOrEmpty(userAnswer.PageAnswerUrl))
            {
                // 已答题，使用 PageAnswerUrl
                images.Add(userAnswer.PageAnswerUrl);
            }
            // 未答题不返回任何图片，images 保持空集合
        }

        #endregion

        #region 5. 获取页面作答点读图片集合（PageAnswerDotUrl）

        // ============================================
        // 【获取页面作答点读图片地址集合】
        // 规则：已答题且 PageAnswerDotUrl 有值才返回；未答题或字段为空则跳过
        // 跨页题目：遍历所有相关任务，收集有答题记录的 PageAnswerDotUrl
        // 非跨页题目：取当前任务答题记录的 PageAnswerDotUrl（仅在有值时返回）
        // ============================================
        var pageAnswerDotUrls = new List<string>();
        if (isCrossPage)
        {
            // 跨页题目：遍历相关任务，收集有答题记录的 PageAnswerDotUrl
            foreach (var relatedTask in relatedTasks)
            {
                var userAnswer = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == relatedTask.Id);
                if (userAnswer != null && !string.IsNullOrEmpty(userAnswer.PageAnswerDotUrl))
                {
                    pageAnswerDotUrls.Add(userAnswer.PageAnswerDotUrl);
                }
            }
        }
        else
        {
            // 非跨页题目：取当前任务答题记录的 PageAnswerDotUrl
            // 仅在已答题且 PageAnswerDotUrl 有值时返回
            if (userAnswers != null && userAnswers.Count > 0)
            {
                var userAnswer = userAnswers.FirstOrDefault();
                if (userAnswer != null && !string.IsNullOrEmpty(userAnswer.PageAnswerDotUrl))
                {
                    pageAnswerDotUrls.Add(userAnswer.PageAnswerDotUrl);
                }
            }
        }

        #endregion

        #region 6. 判断是否可领取积分

        // ============================================
        // 【是否可领取积分判断】
        // 条件：任务已完成（Status=1）且未领取过积分
        // 跨页题目：所有任务都完成才能领取
        // 普通题目：当前任务完成即可领取
        // ============================================
        bool canClaimPoints = false;

        // 查询是否已领取过积分
        var existingRecord = await answerRepository.Context.Queryable<PointsRecord>()
            .Where(r => r.UserId == userId && !r.IsDeleted)
            .Where(r => r.ChangeType == PointsChangeTypeEnum.TaskReward.ToString() && r.RelatedId == groupId)
            .AnyAsync();

        if (!existingRecord)
        {
            // 未领取过积分，检查任务是否完成
            if (isCrossPage)
            {
                // 跨页题目：检查该 GroupId 下所有任务是否都完成（Status=1）
                var allCompleted = relatedTasks.All(t =>
                    userAnswers.Any(a => a.JournalPageTaskId == t.Id && a.Status == (int)UserAnswerStatusEnum.Complete));
                canClaimPoints = allCompleted;
            }
            else
            {
                // 普通题目：检查当前任务是否完成（Status=1）
                var userAnswer = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
                    .Where(a => a.JournalPageTaskId == mainTask.Id && a.UserId == userId && !a.IsDeleted)
                    .FirstAsync();
                canClaimPoints = userAnswer?.Status == (int)UserAnswerStatusEnum.Complete;
            }
        }

        #endregion

        #region 7. 获取任务名称

        // ============================================
        // 【获取任务名称】
        // 跨页题目：取主任务（Id == GroupId）的 Task 字段
        // 普通题目：取当前任务的 Task 字段
        // ============================================
        string taskName;
        if (isCrossPage)
        {
            taskName = mainTask?.Task ?? string.Empty;
        }
        else
        {
            taskName = mainTask?.Task ?? string.Empty;
        }

        #endregion

        #region 8. 返回结果

        return new CrossPageTaskOutput
        {
            FirstLevelCatalogName = firstLevelCatalogName,
            CatalogName = catalogName,
            TaskName = taskName,
            Images = images,
            PageAnswerDotUrl = pageAnswerDotUrls,
            IsCrossPage = isCrossPage,
            CanClaimPoints = canClaimPoints
        };

        #endregion
    }

    /// <summary>
    /// ============================================
    /// 方法思路：领取任务积分
    /// 1. 根据 GroupId 查询任务信息，判断是否跨页题目
    ///    - 普通题目：该 GroupId 下只有1个任务，且 Id == GroupId
    ///    - 跨页题目：该 GroupId 下有多个任务，或 Id != GroupId
    /// 2. 通过 groupId 查询用户答题记录
    /// 3. 跨页题目完成校验：必须该 GroupId 下所有任务都完成（Status=1）
    ///    普通题目校验：只需要该任务完成（Status=1）
    /// 4. 重复领取校验：通过 PointsRecord 表的 RelatedId + ChangeType 判断
    /// 5. 积分计算：
    ///    - 跨页题目：累计该 GroupId 下所有任务的积分
    ///    - 普通题目：取当前任务的积分
    /// 6. 得分计算：从 JournalPageTaskUserAnswer 表获取 Score
    /// ============================================
    /// </summary>
    /// <param name="groupId">任务分组Id（JournalPageTask.GroupId）</param>
    /// <param name="userId">用户ID</param>
    /// <returns>领取积分结果</returns>
    public async Task<ClaimTaskPointsOutput> ClaimTaskPointsAsync(long groupId, long userId)
    {
        #region 1. 根据GroupId查询所有相关任务
        // ============================================
        // 1. 根据 GroupId 查询所有相关任务
        // ============================================
        var relatedTasks = await taskRepository.Context.Queryable<JournalPageTask>()
            .Where(t => t.GroupId == groupId && !t.IsDeleted)
            .ToListAsync();

        if (relatedTasks == null || relatedTasks.Count == 0)
        {
            throw new BusinessException("任务不存在", ResultCode.NOT_FOUND);
        }
        #endregion

        #region 2. 判断是否跨页题目
        // ============================================
        // 2. 判断是否跨页题目
        // 普通题目：该 GroupId 下只有1个任务，且该任务的 Id == GroupId
        // 跨页题目：该 GroupId 下有多个任务，或任务的 Id != GroupId
        // ============================================
        bool isCrossPage;
        JournalPageTask mainTask;
        if (relatedTasks.Count == 1 && relatedTasks[0].Id == groupId)
        {
            // 普通题目：只有1个任务，且 Id == GroupId
            isCrossPage = false;
            mainTask = relatedTasks[0];
        }
        else
        {
            // 跨页题目：有多个任务，或者 Id != GroupId
            isCrossPage = true;
            // 取 Id == GroupId 的任务作为主任务（跨页题目的第一条记录）
            mainTask = relatedTasks.FirstOrDefault(t => t.Id == groupId) ?? relatedTasks[0];
        }
        #endregion

        #region 3. 查询用户答题记录
        // ============================================
        // 3. 查询用户答题记录（统一通过 JournalPageTaskGroupId 查询）
        // ============================================
        var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
            .Where(a => a.JournalPageTaskGroupId == groupId && a.UserId == userId && !a.IsDeleted)
            .ToListAsync();

        if (userAnswers == null || userAnswers.Count == 0)
        {
            throw new BusinessException("请先完成答题再领取积分", ResultCode.BAD_REQUEST);
        }
        #endregion

        #region 4. 跨页题目完成校验
        // ============================================
        // 4. 跨页题目完成校验
        // 跨页题目必须该 GroupId 下所有任务都完成（Status=1）才能领取积分
        // 普通题目只需要该任务完成（Status=1）即可领取
        // ============================================
        if (isCrossPage)
        {
            // 跨页题目：检查该 GroupId 下每个任务是否都有 Status=1 的答题记录
            foreach (var task in relatedTasks)
            {
                var completedAnswer = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == task.Id && a.Status == (int)UserAnswerStatusEnum.Complete);
                if (completedAnswer == null)
                {
                    throw new BusinessException("跨页题目需要完成所有任务才能领取积分", ResultCode.BAD_REQUEST);
                }
            }
        }
        else
        {
            // 普通题目：只需要 Status=1 即可
            if (userAnswers.FirstOrDefault()?.Status != (int)UserAnswerStatusEnum.Complete)
            {
                throw new BusinessException("请先完成答题再领取积分", ResultCode.BAD_REQUEST);
            }
        }
        #endregion

        #region 5. 积分计算
        // ============================================
        // 5. 积分计算
        // 【关键】跨页题目所有计算都只算一次（取 JournalPageTaskId == JournalPageTaskGroupId 的值）
        // 跨页题目：只取主任务（Id == GroupId）的积分，不是累加所有任务的积分
        // 普通题目：取主任务的积分
        // ============================================
        int totalPoints = 0;
        if (isCrossPage)
        {
            // 跨页题目：只取主任务（Id == GroupId）的积分
            totalPoints = mainTask.Points;
        }
        else
        {
            // 普通题目：取主任务的积分
            totalPoints = mainTask.Points;
        }

        if (totalPoints <= 0)
        {
            throw new BusinessException("该任务无积分可领取", ResultCode.BAD_REQUEST);
        }
        #endregion

        #region 6. 得分计算
        // ============================================
        // 6. 得分计算
        // 从 JournalPageTaskUserAnswer 表获取 Score
        // 【关键】Score 取 JournalPageTaskId == JournalPageTaskGroupId 的分数
        // 跨页题目：取主任务（Id == GroupId）的分数，不是累加所有任务的分数
        // 普通题目：取当前任务对应答题记录的分数
        // ============================================
        float totalScore = 0;
        if (isCrossPage)
        {
            // 跨页题目：取 JournalPageTaskId == JournalPageTaskGroupId 的分数
            totalScore = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == groupId)?.Score ?? 0;
        }
        else
        {
            // 普通题目：取当前任务对应答题记录的分数
            totalScore = userAnswers.FirstOrDefault()?.Score ?? 0;
        }
        #endregion

        #region 7. 重复领取校验
        // ============================================
        // 7. 重复领取校验
        // 通过 PointsRecord 表的 RelatedId + ChangeType 判断是否已领取
        // 使用 GroupId 作为 RelatedId
        // ============================================
        var existingRecord = await answerRepository.Context.Queryable<PointsRecord>()
            .Where(r => r.UserId == userId && !r.IsDeleted)
            .Where(r => r.ChangeType == PointsChangeTypeEnum.TaskReward.ToString() && r.RelatedId == groupId)
            .FirstAsync();

        if (existingRecord != null)
        {
            throw new BusinessException("积分已领取，请勿重复领取", ResultCode.BAD_REQUEST);
        }
        #endregion

        #region 8. 添加积分记录并返回结果
        // ============================================
        // 8. 调用积分服务添加积分记录
        // ============================================
        var addPointsResult = await pointsService.AddPointsAsync(new AddPointsInput
        {
            UserId = userId,
            Amount = totalPoints,
            ChangeType = PointsChangeTypeEnum.TaskReward,
            RelatedId = groupId,
            Description = $"完成任务奖励：{mainTask.Task}",
            OperatorName = "系统"
        });

        // ============================================
        // 【任务编号计算】
        // 从 JournalPageTask.No 字段按 - 分割，取索引为 2 的部分
        // No 最多出现索引为 3（即最多4段，0-3）
        // 例如："A-B-C-D" → 分割后 ["A","B","C","D"] → 取索引2 = "C"
        // ============================================
        string taskNo = string.Empty;
        if (!string.IsNullOrEmpty(mainTask.No))
        {
            var parts = mainTask.No.Split('-');
            if (parts.Length >= 3)
            {
                taskNo = parts[2];
            }
        }

        return new ClaimTaskPointsOutput
        {
            RecordId = addPointsResult.RecordId,
            PreviousBalance = addPointsResult.PreviousBalance,
            NewBalance = addPointsResult.NewBalance,
            AddedAmount = addPointsResult.AddedAmount,
            TaskName = mainTask.Task ?? string.Empty,
            TaskNo = !string.IsNullOrWhiteSpace(taskNo) ? "任务："+ taskNo : string.Empty,
            Score = totalScore
        };
        #endregion
    }

    /// <summary>
    /// ============================================
    /// 方法思路：批量领取任务积分（一键领取）
    /// 1. 遍历每个传入的 GroupId：
    ///    a. 根据 GroupId 查询所有相关任务，判断是否跨页题目
    ///       - 普通题目：该 GroupId 下只有1个任务，且 Id == GroupId
    ///       - 跨页题目：该 GroupId 下有多个任务，或 Id != GroupId
    ///    b. 通过 JournalPageTaskGroupId 查询用户答题记录
    ///    c. 跨页题目完成校验：必须该 GroupId 下所有任务都完成（Status=1）
    ///    d. 重复领取校验
    ///    e. 计算积分、成长值、得分：
    ///       - 跨页题目：所有计算都只算一次（第一个任务的积分/成长值/得分）
    ///       - 普通题目：正常计算
    /// 2. 返回结果包含：
    ///    - 成功数量和失败数量
    ///    - 成功详情列表（每个GroupId的名称、积分、成长值、得分）
    ///    - 失败详情列表（每个GroupId的错误原因）
    ///    - 总积分、总成长值、领取后余额
    /// ============================================
    /// </summary>
    /// <param name="input">批量领取输入（GroupId列表）</param>
    /// <param name="userId">用户ID</param>
    /// <returns>批量领取结果</returns>
    public async Task<BatchClaimPointsOutput> BatchClaimPointsAsync(BatchClaimPointsInput input, long userId)
    {
        #region 1. 初始化与参数校验
        var result = new BatchClaimPointsOutput();

        if (input == null || input.GroupIds == null || input.GroupIds.Count == 0)
        {
            throw new BusinessException("任务分组Id列表不能为空", ResultCode.BAD_REQUEST);
        }

        var groupIds = input.GroupIds.Distinct().ToList();
        #endregion

        #region 2. 批量查询已领取的积分记录
        // 1. 批量查询已领取的积分记录（用于重复领取校验）
        var claimedRecords = await answerRepository.Context.Queryable<PointsRecord>()
            .Where(r => groupIds.Contains((long)r.RelatedId!) && r.UserId == userId && !r.IsDeleted)
            .Where(r => r.ChangeType == PointsChangeTypeEnum.TaskReward.ToString())
            .ToListAsync();
        var claimedGroupIds = claimedRecords.Select(r => r.RelatedId!.Value).ToHashSet();
        #endregion

        #region 3. 逐个处理每个 GroupId
        // 2. 逐个处理每个 GroupId
        int totalPoints = 0;
        int totalGrowthPoints = 0;
        int currentBalance = 0;

        foreach (var groupId in groupIds)
        {
            try
            {
                #region 3.1 根据 GroupId 查询所有相关任务
                // ============================================
                // 2.1 根据 GroupId 查询所有相关任务
                // ============================================
                var relatedTasks = await taskRepository.Context.Queryable<JournalPageTask>()
                    .Where(t => t.GroupId == groupId && !t.IsDeleted)
                    .ToListAsync();

                if (relatedTasks == null || relatedTasks.Count == 0)
                {
                    result.FailItems.Add(new BatchClaimFailItem
                    {
                        GroupId = groupId,
                        Message = "任务不存在"
                    });
                    continue;
                }
                #endregion

                #region 3.2 判断是否跨页题目
                // ============================================
                // 2.2 判断是否跨页题目
                // 普通题目：该 GroupId 下只有1个任务，且 Id == GroupId
                // 跨页题目：有多个任务，或 Id != GroupId
                // ============================================
                bool isCrossPage;
                JournalPageTask mainTask;
                if (relatedTasks.Count == 1 && relatedTasks[0].Id == groupId)
                {
                    // 普通题目
                    isCrossPage = false;
                    mainTask = relatedTasks[0];
                }
                else
                {
                    // 跨页题目
                    isCrossPage = true;
                    // 取 Id == GroupId 的任务作为主任务
                    mainTask = relatedTasks.FirstOrDefault(t => t.Id == groupId) ?? relatedTasks[0];
                }
                #endregion

                #region 3.3 查询用户答题记录
                // ============================================
                // 2.3 查询用户答题记录（统一通过 JournalPageTaskGroupId 查询）
                // ============================================
                var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
                    .Where(a => a.JournalPageTaskGroupId == groupId && a.UserId == userId && !a.IsDeleted)
                    .ToListAsync();

                if (userAnswers == null || userAnswers.Count == 0)
                {
                    result.FailItems.Add(new BatchClaimFailItem
                    {
                        GroupId = groupId,
                        Message = "请先完成答题再领取积分"
                    });
                    continue;
                }
                #endregion

                #region 3.4 跨页题目完成校验
                // ============================================
                // 2.4 跨页题目完成校验
                // 跨页题目必须该 GroupId 下所有任务都完成（Status=1）
                // 普通题目只需要该任务完成（Status=1）
                // ============================================
                bool isCompleted = true;  // 完成状态标志
                if (isCrossPage)
                {
                    // 跨页题目：检查该 GroupId 下每个任务是否都有 Status=1 的答题记录
                    foreach (var task in relatedTasks)
                    {
                        var completedAnswer = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == task.Id && a.Status == (int)UserAnswerStatusEnum.Complete);
                        if (completedAnswer == null)
                        {
                            isCompleted = false;
                            break;  // 发现未完成的任务，立即退出循环
                        }
                    }
                    if (!isCompleted)
                    {
                        result.FailItems.Add(new BatchClaimFailItem
                        {
                            GroupId = groupId,
                            Message = "跨页题目需要完成所有任务才能领取积分"
                        });
                        continue;  // 跳出外层循环，处理下一个 groupId
                    }
                }
                else
                {
                    // 普通题目：只需要 Status=1 即可
                    if (userAnswers.FirstOrDefault()?.Status != (int)UserAnswerStatusEnum.Complete)
                    {
                        result.FailItems.Add(new BatchClaimFailItem
                        {
                            GroupId = groupId,
                            Message = "请先完成答题再领取积分"
                        });
                        continue;
                    }
                }
                #endregion

                #region 3.5 重复领取校验
                // ============================================
                // 2.5 重复领取校验
                // ============================================
                if (claimedGroupIds.Contains(groupId))
                {
                    result.FailItems.Add(new BatchClaimFailItem
                    {
                        GroupId = groupId,
                        Message = "积分已领取，请勿重复领取"
                    });
                    continue;
                }
                #endregion

                #region 3.6 计算积分、成长值、得分
                // ============================================
                // 2.6 计算积分、成长值、得分
                // 【关键】跨页题目所有计算都只算一次（取 JournalPageTaskId == JournalPageTaskGroupId 的值）
                // Score 取 JournalPageTaskId == JournalPageTaskGroupId 的分数，不是累加所有任务的分数
                // ============================================
                int points;
                int growthPoints;
                float score;

                if (isCrossPage)
                {
                    // 跨页题目：取 JournalPageTaskId == JournalPageTaskGroupId 的积分、成长值、得分
                    // mainTask 已经是 Id == GroupId 的任务，所以 mainTask.Id == groupId
                    points = mainTask.Points;
                    growthPoints = mainTask.GrowthPoint ?? 0;
                    // 跨页题目：取 JournalPageTaskId == JournalPageTaskGroupId 的分数
                    score = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == groupId)?.Score ?? 0;
                }
                else
                {
                    // 普通题目：正常计算
                    points = mainTask.Points;
                    growthPoints = mainTask.GrowthPoint ?? 0;
                    score = userAnswers.FirstOrDefault()?.Score ?? 0;
                }

                if (points <= 0)
                {
                    result.FailItems.Add(new BatchClaimFailItem
                    {
                        GroupId = groupId,
                        Message = "该任务无积分可领取"
                    });
                    continue;
                }

                // 获取任务名称
                var taskName = mainTask.Task ?? string.Empty;

                // ============================================
                // 【任务编号计算】
                // 从 JournalPageTask.No 字段按 - 分割，取索引为 2 的部分
                // No 最多出现索引为 3（即最多4段，0-3）
                // 例如："A-B-C-D" → 分割后 ["A","B","C","D"] → 取索引2 = "C"
                // ============================================
                string taskNo = string.Empty;
                if (!string.IsNullOrEmpty(mainTask.No))
                {
                    var parts = mainTask.No.Split('-');
                    if (parts.Length >= 3)
                    {
                        taskNo = parts[2];
                    }
                }
                #endregion

                #region 3.7 调用积分服务并记录结果
                // 调用积分服务增加积分
                var addResult = await pointsService.AddPointsAsync(new AddPointsInput
                {
                    UserId = userId,
                    Amount = points,
                    ChangeType = PointsChangeTypeEnum.TaskReward,
                    RelatedId = groupId,
                    Description = $"完成任务奖励：{taskName}",
                    OperatorName = "系统"
                });

                // 累计总积分和总成长值
                totalPoints += points;
                totalGrowthPoints += growthPoints;
                currentBalance = addResult.NewBalance;
                result.SuccessCount++;

                // 添加成功详情到成功列表
                result.SuccessItems.Add(new BatchClaimSuccessItem
                {
                    GroupId = groupId,
                    TaskName = taskName,
                    TaskNo = !string.IsNullOrWhiteSpace(taskNo) ? "任务：" + taskNo : string.Empty,
                    Points = points,
                    GrowthPoints = growthPoints,
                    Score = score
                });
                #endregion
            }
            catch (Exception ex)
            {
                // 捕获异常并添加到失败列表，不中断其他任务的处理
                result.FailItems.Add(new BatchClaimFailItem
                {
                    GroupId = groupId,
                    Message = ex.Message
                });
            }
        }
        #endregion

        #region 4. 组装返回结果
        result.FailCount = result.FailItems.Count;
        result.TotalPoints = totalPoints;
        result.TotalGrowthPoints = totalGrowthPoints;
        result.BalanceAfter = currentBalance;

        return result;
        #endregion
    }
}
