using Microsoft.Extensions.Logging;
using QYZH.InteractiveMagazine.Common.Helpers;
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,
    BaseRepository<UserPet> userPetRepository,
    IPointsService pointsService,
    IPetService petService,
    ILogger<UserAnswerTaskService> logger)
    : BaseRepository<JournalPageTaskUserAnswer>, IUserAnswerTaskService
{
    /// <summary>
    /// ============================================
    /// 方法思路：
    /// 1. 首先验证 UserJournal 表，获取用户绑定的杂志列表
    ///    - 如果用户没有绑定任何杂志，直接返回空列表
    /// 2. 批量预加载策略：为避免N+1查询问题，先按期刊维度批量加载所有关联数据
    ///    - 期刊基本信息
    ///    - 一级栏目（ParentId == 0）
    ///    - 所有任务（按期刊ID）
    ///    - 所有页面（按期刊ID）
    /// 3. 查询用户在这些期刊下的作答记录
    /// 4. 跨页题目处理核心：
    ///    - 跨页题目定义：JournalPageTask.Id != GroupId（同一个GroupId下有多个任务）
    ///    - 进度计算：按GroupId去重，已完成条件是该GroupId下所有任务都完成（Status=1）
    ///    - 积分计算：按GroupId去重，每个GroupId只计算一次积分（取主任务的积分）
    /// 5. 状态判定逻辑：
    ///    - 未开始：无任何作答记录
    ///    - 进行中：有作答记录但未全部完成
    ///    - 已完成：所有GroupId都完成
    /// 6. 用户未作答任何任务时：
    ///    - 仍返回绑定杂志的进度信息
    ///    - 进度、成长值、积分为0
    ///    - 连续打卡、剩余篇数正常计算
    /// ============================================
    /// </summary>
    /// <param name="userId">用户ID</param>
    /// <returns>用户所有期刊的学习进度列表</returns>
    public async Task<List<JournalProgressOutput>> GetJournalProgressAsync(long userId)
    {
        #region 1. 验证 UserJournal 表，获取用户绑定的杂志

        // ============================================
        // 第一步：先验证 UserJournal 表数据
        // 获取用户绑定的所有杂志 ID
        // 如果没有绑定的杂志，直接返回空列表，不进行后续查询
        // ============================================
        var boundJournalIds = await userJournalRepository.Context.Queryable<UserJournal>()
            .Where(uj => uj.UserId == userId)
            .Select(uj => uj.JournalId)
            .ToListAsync();

        if (boundJournalIds == null || boundJournalIds.Count == 0)
        {
            return new List<JournalProgressOutput>();
        }

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

        #endregion

        #region 2. 批量预加载关联数据

        // ============================================
        // 第二步：批量预加载关联实体（按期刊维度全量加载，避免N+1查询）
        // - 期刊基本信息
        // - 一级栏目（ParentId == 0 表示一级目录）
        // - 所有任务
        // - 所有页面（用于计算剩余篇数）
        // ============================================
        var journals = await journalRepository.Context.Queryable<Journal>()
            .Where(j => journalIds.Contains(j.Id))
            .ToListAsync();

        var allCatalogs = await catalogRepository.Context.Queryable<JournalCatalog>()
            .Where(c => journalIds.Contains(c.JournalId) && c.ParentId == 0)
            .ToListAsync();

        var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
            .Where(t => journalIds.Contains(t.JournalId))
            .ToListAsync();

        var allPages = await pageRepository.Context.Queryable<JournalPage>()
            .Where(p => journalIds.Contains(p.JournalId))
            .ToListAsync();

        #endregion

        #region 3. 查询用户作答记录（仅绑定的杂志）

        // ============================================
        // 第三步：查询用户在这些绑定杂志下的作答记录
        // 只查询绑定杂志的作答记录，减少数据量
        // ============================================
        var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
            .Where(a => a.UserId == userId && journalIds.Contains(a.JournalId))
            .ToListAsync();

        #endregion

        #region 4. 查询连续打卡天数

        var consecutiveDays = await CalculateConsecutiveDaysAsync(userId);

        #endregion

        #region 5. 提取用户已作答/已完成任务ID集合

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

        // 用户已完成的任务ID集合
        // 【已完成判断逻辑】
        // Status: 0=未批阅，1=已批阅      
        var userCompletedTaskIds = userAnswers
            .Where(a => a.Status == (int)UserAnswerStatusEnum.Complete)
            .Select(a => a.JournalPageTaskId)
            .ToHashSet();

        #endregion

        #region 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 totalGroupIds = journalTasks.Select(t => t.GroupId).Distinct().ToList();
            var totalTaskCount = totalGroupIds.Count;

            // 【跨页题目-已完成判断】已完成的 GroupId 列表
            // 关键：必须该 GroupId 下所有任务都完成，才算该 GroupId 完成
            var completedGroupIds = journalTasks
                .GroupBy(t => t.GroupId)
                .Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
                .Select(g => g.Key)
                .ToList();

            var completedTaskCount = completedGroupIds.Count;

            // ============================================
            // 【积分和成长值计算】
            // 1. totalGrowthPoints：从已完成作答记录中，按 JournalPageTaskGroupId 查询主任务（Id=GroupId）的 GrowthPoint
            // 2. totalPoints：统计该期刊下已完成任务的 Points 总数
            // ============================================
            // 该期刊下用户已完成的作答记录
            // 【已完成判断逻辑】
            // Status: 0=未批阅，1=已批阅           
            var completedAnswers = userAnswers
                .Where(a => a.JournalId == journal.Id && a.Status == (int)UserAnswerStatusEnum.Complete)
                .ToList();

            // 按 JournalPageTaskGroupId 去重，获取已完成的主任务 ID 列表
            var completedGroupIdsSet = completedAnswers.Select(a => a.JournalPageTaskGroupId).Distinct().ToList();

            // 从主任务表查询这些 GroupId 对应的 GrowthPoint 和 Points
            var mainTasks = allTasks
                .Where(t => completedGroupIdsSet.Contains(t.GroupId) && t.Id == t.GroupId)
                .ToList();

            // 累计成长值（从主任务的 GrowthPoint 累加）
            var totalGrowthPoints = mainTasks.Sum(t => t.GrowthPoint);

            // 累计积分（从已完成任务的 Points 累加，每个 GroupId 只计算一次）
            var totalPoints = mainTasks.Sum(t => t.Points);

            // 【进度百分比计算】按篇数统计
            // 只计算有题目的页面，进度 = 已答篇数 / 有题目的总篇数 × 100
            var pagesWithTasks = journalTasks.Select(t => t.JournalPageId).Distinct().ToHashSet();
            var totalPagesWithTasks = pagesWithTasks.Count;
            var answeredPagesWithTasks = pagesWithTasks.Count(userAnsweredPageIds.Contains);
            var progress = totalPagesWithTasks > 0
                ? Math.Round((double)answeredPagesWithTasks / totalPagesWithTasks * 100, 2)
                : 0;

            // 剩余篇数：有题目的页面总数 - 已作答的有题目页面数
            var remainingPages = totalPagesWithTasks - answeredPagesWithTasks;

            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.CheckInDate.Date == yesterday)
            .FirstAsync();

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

        // 昨天有打卡记录，检查今天是否已打卡
        var todayRecord = await checkInRecordRepository.Context.Queryable<CheckInRecord>()
            .Where(r => r.UserId == userId && 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 0. 验证用户是否绑定该杂志

        // ============================================
        // 第一步：验证 UserJournal 表，确认用户是否绑定了该杂志
        // 如果用户没有绑定该杂志，直接返回空列表
        // ============================================
        var isBound = await userJournalRepository.Context.Queryable<UserJournal>()
            .Where(uj => uj.UserId == userId && uj.JournalId == journalId)
            .AnyAsync();

        if (!isBound)
        {
            return new List<CatalogListOutput>();
        }

        #endregion

        #region 1. 查询期刊
        // 1. 查询期刊
        var journal = await journalRepository.Context.Queryable<Journal>()
            .Where(j => j.Id == journalId)
            .FirstAsync();

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

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

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

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

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

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

        // 用户已完成的任务ID集合
        // 【已完成判断逻辑】        
        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).OrderBy(c => c.Sort).ToList();
        var result = new List<CatalogListOutput>();

        foreach (var firstCatalog in firstLevelCatalogs)
        {
            // ============================================
            // 文章篇数计算逻辑：
            // - 篇数 = 有题目的页面数（有 JournalPageTask 的页面）
            // - 有二级栏目：累加所有二级栏目下有题目的页面数
            // - 没有二级栏目：统计一级栏目下直接有题目的页面数
            // ============================================
            int articleCount = 0;
            var children = new List<SecondCatalogOutput>();

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

            if (secondLevelCatalogs.Count > 0)
            {
                // 有二级单元：按二级单元分组统计
                foreach (var secondCatalog in secondLevelCatalogs)
                {
                    // 二级目录下的所有页面（JournalPage.JournalCatalogId = 二级目录ID）
                    var secondLevelPages = allPages
                        .Where(p => p.JournalCatalogId == secondCatalog.Id)
                        .ToList();

                    var secondLevelPageIds = secondLevelPages.Select(p => p.Id).ToHashSet();

                    // 二级目录下有题目的页面（有 JournalPageTask 的页面）
                    var pagesWithTasks = allTasks
                        .Where(t => secondLevelPageIds.Contains(t.JournalPageId))
                        .Select(t => t.JournalPageId)
                        .Distinct()
                        .ToHashSet();

                    // 【关键】如果二级目录下没有有题目的页面，则跳过，不统计到 children
                    if (pagesWithTasks.Count == 0)
                    {
                        continue;
                    }

                    // 累加该二级栏目下有题目的页面数（文章篇数）
                    articleCount += pagesWithTasks.Count;

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

                    // 判定状态（基于 AnswerStatus >= 1 判断已完成，按页面维度统计）
                    // 【已完成判断逻辑】
                    // AnswerStatus: 0=未答题，1=已答题，2=正确，3=半对，4=错误
                    // 只要 AnswerStatus >= 1（已答题）说明该页面已完成
                    string status;

                    // 总页面数（有题目的页面）
                    var totalPageCount = pagesWithTasks.Count;
                    // 已完成页面数：该页面有答题记录且 AnswerStatus >= 1
                    var completedPageCount = pagesWithTasks
                        .Count(pageId => userAnswers.Any(a => a.JournalPageId == pageId && a.Status == (int)UserAnswerStatusEnum.Complete));
                    // 已作答页面数：该页面有答题记录即可
                    var answeredPageCount = pagesWithTasks
                        .Count(pageId => userAnswers.Any(a => a.JournalPageId == pageId));

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

                    // 计算该二级目录的总成长值和总积分（跨页题目按GroupId去重）
                    var distinctSecondLevelTasks = secondLevelTasks
                        .GroupBy(t => t.GroupId)
                        .Select(g => g.First())
                        .ToList();
                    var totalGrowthPoints = distinctSecondLevelTasks.Sum(t => t.GrowthPoint);
                    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
            {
                // 没有二级单元：统计一级栏目下直接有题目的页面数
                var firstLevelPages = allPages
                    .Where(p => p.JournalCatalogId == firstCatalog.Id)
                    .ToList();
                var firstLevelPageIds = firstLevelPages.Select(p => p.Id).ToHashSet();
                var pagesWithTasks = allTasks
                    .Where(t => firstLevelPageIds.Contains(t.JournalPageId))
                    .Select(t => t.JournalPageId)
                    .Distinct()
                    .ToHashSet();
                articleCount = pagesWithTasks.Count;
                // Children保持为空集合，不添加任何子项
            }

            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)
            .FirstAsync();

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

        #region 2. 验证用户是否绑定该杂志

        // ============================================
        // 验证 UserJournal 表，确认用户是否绑定了该杂志
        // 如果用户没有绑定该杂志，直接返回 null
        // ============================================
        var isBound = await userJournalRepository.Context.Queryable<UserJournal>()
            .Where(uj => uj.UserId == userId && uj.JournalId == firstCatalog.JournalId)
            .AnyAsync();

        if (!isBound)
        {
            return null;
        }

        #endregion

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

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

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

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

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

        // 用户已完成的任务ID集合
        // 【已完成判断逻辑】
        // AnswerStatus: 0=未答题，1=已答题，2=正确，3=半对，4=错误
        // 只要 AnswerStatus >= 1（已答题）说明作业已完成
        var userCompletedTaskIds = userAnswers
            .Where(a => a.Status == (int)UserAnswerStatusEnum.Complete)
            .Select(a => a.JournalPageTaskId)
            .ToHashSet();
        #endregion

        #region 5. 获取一级栏目下所有二级目录
        // 获取该一级目录下所有二级目录（不区分Type）
        var secondLevelCatalogs = allCatalogs
            .Where(c => c.ParentId == firstCatalog.Id)
            .OrderBy(c => c.Sort)
            .ToList();

        // 文章篇数 = 有题目的页面数（有 JournalPageTask 的页面）
        int articleCount = 0;
        // 总任务数（按 GroupId 去重）
        int totalTaskCount = 0;
        // 构建子目录详情列表
        var subCatalogs = new List<SubCatalogDetailOutput>();

        foreach (var secondCatalog in secondLevelCatalogs)
        {
            // 二级目录下的所有页面
            var secondLevelPages = allPages
                .Where(p => p.JournalCatalogId == secondCatalog.Id)
                .OrderBy(p => p.PageNum)
                .ToList();

            var secondLevelPageIds = secondLevelPages.Select(p => p.Id).ToHashSet();

            // 二级目录下有题目的页面（有 JournalPageTask 的页面）
            var pagesWithTasks = allTasks
                .Where(t => secondLevelPageIds.Contains(t.JournalPageId))
                .Select(t => t.JournalPageId)
                .Distinct()
                .ToHashSet();

            // 如果二级目录下没有有题目的页面，则跳过
            if (pagesWithTasks.Count == 0)
            {
                continue;
            }

            // 累加文章篇数
            articleCount += pagesWithTasks.Count;

            // 该文章下的所有任务
            var articleTasks = allTasks.Where(t => secondLevelPageIds.Contains(t.JournalPageId)).ToList();
            // taskCount = GroupId 去重数（跨页题目只算一个）
            var taskCount = articleTasks.Select(t => t.GroupId).Distinct().Count();
            // GroupId 去重数（用于状态判断）
            var totalGroupCount = taskCount;

            // 累加总任务数
            totalTaskCount += taskCount;

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

            // 状态格式：1/2 进行中、2/2 已完成、0/2 未开始（按 GroupId 去重统计）
            // 只要 AnswerStatus >= 1（已答题）说明该 GroupId 已完成
            string status;
            if (totalGroupCount == 0)
            {
                status = "0/0 未开始";
            }
            else
            {
                // 已完成 GroupId 数（该 GroupId 下所有任务都 AnswerStatus >= 1 才算完成）
                var completedGroupCount = articleTasks
                    .GroupBy(t => t.GroupId)
                    .Where(g => g.All(t => userCompletedTaskIds.Contains(t.Id)))
                    .Count();
                // 已作答 GroupId 数（只要有一个任务作答就算该 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 = $"{totalGroupCount}/{totalGroupCount} 已完成";
                else
                    status = $"{completedGroupCount}/{totalGroupCount} 进行中";
            }

            subCatalogs.Add(new SubCatalogDetailOutput
            {
                CatalogId = secondCatalog.Id,
                SecondLevelCatalogName = secondCatalog.Name ?? string.Empty,
                StartPage = startPage,
                EndPage = endPage,
                TaskCount = taskCount,
                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)
            .FirstAsync();

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

        #endregion

        #region 2. 验证用户是否绑定该杂志

        // ============================================
        // 验证 UserJournal 表，确认用户是否绑定了该杂志
        // 如果用户没有绑定该杂志，直接返回 null
        // ============================================
        var isBound = await userJournalRepository.Context.Queryable<UserJournal>()
            .Where(uj => uj.UserId == userId && uj.JournalId == catalog.JournalId)
            .AnyAsync();

        if (!isBound)
        {
            return null;
        }

        #endregion

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

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

        #endregion

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

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

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

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

        #endregion

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

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

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

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

        // 用户已作答的任务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();

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

        #endregion

        #region 6. 按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,
                TotalPoints = mainTask.Points,
                AnswerMinutes = groupAnswerMinutes,
                Status = taskStatus
            });
        }

        #endregion

        #region 7. 返回结果

        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（JournalPageTaskGroupId）</param>
    /// <param name="userId">用户ID</param>
    /// <returns>跨页题目详情</returns>
    public async Task<CrossPageTaskOutput> GetCrossPageTaskAsync(long groupId, long userId)
    {
        #region 0. 查询任务并验证用户绑定

        // 1. 查询该 GroupId 下的所有任务
        var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
            .Where(t => t.GroupId == groupId)
            .OrderBy(t => t.Id)
            .ToListAsync();

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

        // ============================================
        // 验证 UserJournal 表，确认用户是否绑定了该杂志
        // 如果用户没有绑定该杂志，直接返回 null
        // ============================================
        var journalId = allTasks[0].JournalId;
        var isBound = await userJournalRepository.Context.Queryable<UserJournal>()
            .Where(uj => uj.UserId == userId && uj.JournalId == journalId)
            .AnyAsync();

        if (!isBound)
        {
            return null;
        }

        #endregion

        #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)
            .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)
                .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)
                    .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)
                .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))
                .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 = DomainHelper.OssFullUrl(userAnswer.PageAnswerUrl);
                }
                else if (!string.IsNullOrEmpty(relatedTask.TaskUrl))
                {
                    // 未答题或无 PageAnswerUrl：使用任务的 TaskUrl 补齐（加上域名）
                    imageUrl = DomainHelper.OssFullUrl(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)
                .FirstAsync();

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

            if (userAnswer != null && !string.IsNullOrEmpty(userAnswer.PageAnswerUrl))
            {
                // 已答题，使用 PageAnswerUrl（加上域名）
                images.Add(DomainHelper.OssFullUrl(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(DomainHelper.OssFullUrl(userAnswer.PageAnswerDotUrl));
                }
            }
        }
        else
        {
            // 非跨页题目：取当前任务答题记录的 PageAnswerDotUrl（加上域名）
            // 仅在已答题且 PageAnswerDotUrl 有值时返回
            if (userAnswers != null && userAnswers.Count > 0)
            {
                var userAnswer = userAnswers.FirstOrDefault();
                if (userAnswer != null && !string.IsNullOrEmpty(userAnswer.PageAnswerDotUrl))
                {
                    pageAnswerDotUrls.Add(DomainHelper.OssFullUrl(userAnswer.PageAnswerDotUrl));
                }
            }
        }

        #endregion

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

        // ============================================
        // 【是否可领取积分判断】
        // 条件：任务已完成且未领取过积分
        // - Status = 1（已批阅）
        // - AnswerStatus >= 1（已答题：正确/半对/错误）
        // 跨页题目：所有任务都满足条件才能领取
        // 普通题目：当前任务满足条件即可领取
        // 【关键】子题目入口（mainTask == null）无法领取积分
        // ============================================
        bool canClaimPoints = false;

        // 【关键】如果主任务不存在（子题目入口），无法领取积分
        if (mainTask == null)
        {
            // 子题目无法单独领取积分，CanClaimPoints 为 false
        }
        else
        {
            // 查询是否已领取过积分（用主任务的 GroupId 作为 RelatedId）
            var existingRecord = await answerRepository.Context.Queryable<PointsRecord>()
                .Where(r => r.UserId == userId)
                .Where(r => r.ChangeType == PointsChangeTypeEnum.TaskReward.ToString() && r.RelatedId == mainTask.GroupId)
                .AnyAsync();

            if (!existingRecord)
            {
                // 未领取过积分，检查任务是否完成
                // 条件：Status=1 
                canClaimPoints = relatedTasks.All(t => userAnswers.Any(a => a.JournalPageTaskId == t.Id && a.Status == (int)UserAnswerStatusEnum.Complete));
            }
        }

        #endregion

        #region 7. 获取任务名称

        // ============================================
        // 【获取任务名称】
        // 跨页题目：取主任务（Id == GroupId）的 Task 字段
        // 普通题目：取当前任务的 Task 字段
        // 子题目入口：查询主任务的 Task 字段
        // ============================================
        string taskName;
        if (mainTask != null)
        {
            // 主任务入口，直接使用 mainTask
            taskName = mainTask.Task ?? string.Empty;
        }
        else
        {
            // 子题目入口：查询主任务（Id == GroupId）
            var realMainTask = allTasks.FirstOrDefault(t => t.Id == groupId);
            if (realMainTask != null)
            {
                taskName = realMainTask.Task ?? string.Empty;
            }
            else
            {
                // 通过 GroupId 查询主任务（适用于子任务入口的情况）
                var mainTaskByGroupId = await taskRepository.Context.Queryable<JournalPageTask>()
                    .Where(t => t.Id == t.GroupId && t.GroupId == groupId)
                    .FirstAsync();
                taskName = mainTaskByGroupId?.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 查询任务信息，判断是否跨页题目
    /// 2. 查询用户答题记录，校验主任务（JournalPageTaskId == GroupId）才能领取
    /// 3. 完成校验：Status=1 且 AnswerStatus>=1，跨页题目需所有任务都完成
    /// 4. 重复领取校验：通过 PointsRecord 表的 RelatedId + ChangeType 判断
    /// 5. 计算积分、成长值、得分（均取主任务数据）
    /// 6. 调用积分服务增加积分
    /// ============================================
    /// </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. 查询任务与答题记录
        // ============================================
        // 1. 查询任务与答题记录
        // ============================================
        var relatedTasks = await taskRepository.Context.Queryable<JournalPageTask>()
            .Where(t => t.GroupId == groupId)
            .ToListAsync();

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

        // 主任务：Id == GroupId
        var mainTask = relatedTasks.FirstOrDefault(t => t.Id == groupId);
        if (mainTask == null)
        {
            throw new BusinessException("跨页题目需要完成所有任务才能领取积分", ResultCode.BAD_REQUEST);
        }

        // 查询用户答题记录
        var userAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
            .Where(a => a.JournalPageTaskGroupId == groupId && a.UserId == userId)
            .ToListAsync();

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

        #region 2. 完成校验
        // ============================================
        // 2. 完成校验
        // 主任务校验 + 跨页题目所有任务校验
        // 完成条件：Status=1
        // ============================================
        // 校验主任务
        var mainTaskAnswer = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == groupId);
        if (mainTaskAnswer == null || mainTaskAnswer.Status != (int)UserAnswerStatusEnum.Complete)
        {
            throw new BusinessException("请先完成答题再领取积分", ResultCode.BAD_REQUEST);
        }

        // 跨页题目：校验所有任务
        if (relatedTasks.Count > 1)
        {
            foreach (var task in relatedTasks)
            {
                var answer = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == task.Id);
                if (answer == null || answer.Status != (int)UserAnswerStatusEnum.Complete)
                {
                    throw new BusinessException("跨页题目需要完成所有任务才能领取积分", ResultCode.BAD_REQUEST);
                }
            }
        }
        #endregion

        #region 3. 重复领取校验与积分计算
        // ============================================
        // 3. 重复领取校验与积分计算
        // ============================================
        // 重复领取校验
        var existingRecord = await answerRepository.Context.Queryable<PointsRecord>()
            .Where(r => r.UserId == userId)
            .Where(r => r.ChangeType == nameof(PointsChangeTypeEnum.TaskReward) && r.RelatedId == groupId)
            .FirstAsync();

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

        // 积分、成长值、得分（均取主任务数据）
        int totalPoints = mainTaskAnswer.Points;
        int totalGrowthPoints = mainTaskAnswer.GrowthPoints;
        float score = mainTaskAnswer.Score;

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

        #region 4. 事务执行领取操作
        // ============================================
        // 4. 事务执行领取操作
        //    - 手动更新 Users.Points（积分累加）
        //    - 插入积分流水记录
        //    - 喂养宠物（如有活跃宠物，更新宠物成长值）
        // ============================================
        // 查询用户信息（获取当前积分）
        var user = await usersRepository.GetByIdAsync(userId);
        if (user == null)
        {
            throw new BusinessException("用户不存在", ResultCode.NOT_FOUND);
        }

        // 查询用户活跃宠物
        var pet = await userPetRepository.Context.Queryable<UserPet>()
            .Where(p => p.UserId == userId && p.Status == (int)UserPetStatusEnum.Active)
            .FirstAsync();

        var result = new ClaimTaskPointsOutput();

        await answerRepository.UseTranAsync(async () =>
        {
            // 4a. 手动更新 Users.Points（积分累加）
            var newPointsBalance = user.Points + totalPoints;
            // 4b. 手动更新 Users.GrowthPoints（成长值累加）
            var newGrowthPointsBalance = user.GrowthPoints + totalGrowthPoints;
            await answerRepository.Context.Updateable<Users>()
                .SetColumns(u => u.Points == newPointsBalance)
                .SetColumns(u => u.GrowthPoints == newGrowthPointsBalance)
                .SetColumns(u => u.UpdatedBy == userId.ToString())
                .SetColumns(u => u.UpdatedAt == DateTime.Now)
                .Where(u => u.Id == userId)
                .ExecuteCommandAsync();

            // 4b. 插入积分流水记录
            var pointsRecord = new PointsRecord
            {
                UserId = userId,
                ChangeAmount = totalPoints,
                BalanceAfter = newPointsBalance,
                ChangeType = PointsChangeTypeEnum.TaskReward.ToString(),
                RelatedId = groupId,
                Description = $"完成任务奖励，GroupId：{mainTask.GroupId}",
                Type = PointsFlowTypeEnum.Income,
                Status = (int)PointsRecordStatusEnum.Success,
                IsDeleted = false,
                CreatedBy = "系统",
                CreatedAt = DateTime.Now,
                UpdatedBy = "系统",
                UpdatedAt = DateTime.Now
            };
            await answerRepository.Context.Insertable(pointsRecord).ExecuteCommandAsync();

            // 4c. 如果有活跃宠物，喂养宠物（同一事务内，更新宠物成长值）
            if (pet != null && totalGrowthPoints > 0)
            {
                await petService.FeedPetInTranAsync(userId, new Models.Dto.Pet.FeedPetInput
                {
                    PetId = pet.Id,
                    GrowthPoints = totalGrowthPoints
                });
            }

            // ============================================
            // 【任务编号计算】
            // 从 JournalPageTask.No 字段按 - 分割，取索引为 2 的部分
            // 例如："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]}";
                }
            }

            result.TotalGrowthPoints = totalGrowthPoints;
            result.TotalPoints = totalPoints;
            result.Score = score;
            result.TaskNo = taskNo;
        });

        return result;
        #endregion
    }

    /// <summary>
    /// ============================================
    /// 方法思路：批量领取任务积分（一键领取）
    /// 1. 遍历每个传入的 GroupId，做校验，筛选出可领取的任务
    /// 2. 在事务中执行所有写操作：
    ///    - 更新 Users 表 Points（积分服务内部处理）
    ///    - 更新 Users 表 GrowthPoints（累加）
    ///    - 喂养宠物（如有活跃宠物）
    /// 3. 返回结果包含：
    ///    - 成功数量和失败数量
    ///    - 成功详情列表（每个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);
        }
       
        #endregion

        #region 2. 批量查询所有数据（一次性查询，避免N+1）
        // ============================================
        // 2. 批量查询所有数据
        //    - 所有 GroupId 对应的任务（JournalPageTask）
        //    - 所有 GroupId 对应的用户答题记录（JournalPageTaskUserAnswer）
        //    - 已领取的积分记录（用于重复领取校验）
        // ============================================

        // 2.1 批量查询所有相关任务（按 GroupId 分组）
        var allTasks = await taskRepository.Context.Queryable<JournalPageTask>()
            .Where(t => input.GroupIds.Contains(t.GroupId))
            .ToListAsync();
        var tasksByGroupId = allTasks.GroupBy(t => t.GroupId).ToDictionary(g => g.Key, g => g.ToList());

        // 2.2 批量查询所有用户答题记录（按 JournalPageTaskGroupId 分组）
        var allUserAnswers = await answerRepository.Context.Queryable<JournalPageTaskUserAnswer>()
            .Where(a => input.GroupIds.Contains(a.JournalPageTaskGroupId) && a.UserId == userId)
            .ToListAsync();
        var answersByGroupId = allUserAnswers.GroupBy(a => a.JournalPageTaskGroupId).ToDictionary(g => g.Key, g => g.ToList());

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

        #region 3. 内存中筛选可领取的任务
        // ============================================
        // 3. 在内存中逐个校验每个 GroupId，筛选可领取的任务
        //    所有数据已在步骤2中一次性查询完成，这里只做内存筛选
        // ============================================
        var claimableTasks = new List<(long GroupId, int Points, int GrowthPoints, float Score, string TaskNo)>();

        foreach (var groupId in input.GroupIds)
        {
            try
            {
                // 3.1 从内存中获取相关任务
                if (!tasksByGroupId.TryGetValue(groupId, out var relatedTasks) || relatedTasks.Count == 0)
                {
                    result.FailItems.Add(new BatchClaimFailItem
                    {
                        GroupId = groupId,
                        TaskNo = string.Empty,
                        Message = "任务不存在"
                    });
                    continue;
                }

                // 3.2 判断是否跨页题目并找主任务
                bool isCrossPage = relatedTasks.Count > 1;
                var mainTask = relatedTasks.FirstOrDefault(t => t.Id == groupId);
                
                // ============================================
                // 【任务编号计算】
                // 从 JournalPageTask.No 字段按 - 分割，取索引为 2 的部分，前面加"任务"
                // 如果找不到主任务，尝试从子任务中计算
                // ============================================
                string taskNo = string.Empty;
                var taskForNo = mainTask ?? relatedTasks.FirstOrDefault();
                if (taskForNo != null && !string.IsNullOrEmpty(taskForNo.No))
                {
                    var parts = taskForNo.No.Split('-');
                    if (parts.Length >= 3)
                    {
                        taskNo = $"任务{parts[2]}";
                    }
                }

                if (mainTask == null)
                {
                    result.FailItems.Add(new BatchClaimFailItem
                    {
                        GroupId = groupId,
                        TaskNo = taskNo,
                        Message = "子题目无法单独领取积分，请从主任务入口领取"
                    });
                    continue;
                }

                // 3.3 从内存中获取用户答题记录
                if (!answersByGroupId.TryGetValue(groupId, out var userAnswers) || userAnswers.Count == 0)
                {
                    result.FailItems.Add(new BatchClaimFailItem
                    {
                        GroupId = groupId,
                        TaskNo = taskNo,
                        Message = "请先完成答题再领取积分"
                    });
                    continue;
                }

                // 3.4 完成校验
                var mainTaskAnswer = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == groupId);
                if (mainTaskAnswer == null || mainTaskAnswer.Status != (int)UserAnswerStatusEnum.Complete)
                {
                    result.FailItems.Add(new BatchClaimFailItem
                    {
                        GroupId = groupId,
                        TaskNo = taskNo,
                        Message = "请先完成答题再领取积分"
                    });
                    continue;
                }

                // 跨页题目：校验所有任务
                if (isCrossPage)
                {
                    bool allCompleted = relatedTasks.All(task => 
                    {
                        var answer = userAnswers.FirstOrDefault(a => a.JournalPageTaskId == task.Id);
                        return answer != null && answer.Status == (int)UserAnswerStatusEnum.Complete;
                    });
                    if (!allCompleted)
                    {
                        result.FailItems.Add(new BatchClaimFailItem
                        {
                            GroupId = groupId,
                            TaskNo = taskNo,
                            Message = "跨页题目需要完成所有任务才能领取积分"
                        });
                        continue;
                    }
                }

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

                // 3.6 计算积分、成长值、得分
                int points = mainTaskAnswer.Points;
                int growthPoints = mainTaskAnswer.GrowthPoints;
                float score = mainTaskAnswer.Score;

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

                // 添加到可领取列表
                claimableTasks.Add((groupId, points, growthPoints, score, taskNo));
            }
            catch (Exception ex)
            {
                // 异常情况下，尝试从已查询的数据中计算 taskNo
                string taskNo = string.Empty;
                if (tasksByGroupId.TryGetValue(groupId, out var tasks) && tasks.Count > 0)
                {
                    var task = tasks.FirstOrDefault(t => t.Id == groupId) ?? tasks.FirstOrDefault();
                    if (task != null && !string.IsNullOrEmpty(task.No))
                    {
                        var parts = task.No.Split('-');
                        if (parts.Length >= 3)
                        {
                            taskNo = $"任务{parts[2]}";
                        }
                    }
                }
                result.FailItems.Add(new BatchClaimFailItem
                {
                    GroupId = groupId,
                    TaskNo = taskNo,
                    Message = ex.Message
                });
            }
        }
        #endregion

        #region 4. 事务执行所有领取操作
        // ============================================
        // 4. 事务执行所有领取操作
        //    - 手动更新 Users.Points（积分累加）
        //    - 插入积分流水记录
        //    - 喂养宠物（如有活跃宠物，更新宠物成长值）
        // ============================================
        if (claimableTasks.Count > 0)
        {
            // 查询用户信息（获取当前积分）
            var user = await usersRepository.GetByIdAsync(userId);
            if (user == null)
            {
                throw new BusinessException("用户不存在", ResultCode.NOT_FOUND);
            }

            // 查询用户活跃宠物
            var pet = await userPetRepository.Context.Queryable<UserPet>()
                .Where(p => p.UserId == userId && p.Status == (int)UserPetStatusEnum.Active)
                .FirstAsync();

            int totalGrowthPoints = 0;
            int totalPointsAmount = 0;

            await answerRepository.UseTranAsync(async () =>
            {
                foreach (var task in claimableTasks)
                {
                    totalPointsAmount += task.Points;
                    totalGrowthPoints += task.GrowthPoints;

                    // 添加成功详情
                    result.SuccessItems.Add(new BatchClaimSuccessItem
                    {
                        TaskNo = task.TaskNo,
                        Points = task.Points,
                        GrowthPoints = task.GrowthPoints,
                        Score = task.Score
                    });
                }

                // 4a. 手动更新 Users.Points（积分累加）
                var newPointsBalance = user.Points + totalPointsAmount;
                // 4b. 手动更新 Users.GrowthPoints（成长值累加）
                var newGrowthPointsBalance = user.GrowthPoints + totalGrowthPoints;

                await answerRepository.Context.Updateable<Users>()
                    .SetColumns(u => u.Points == newPointsBalance)
                    .SetColumns(u => u.GrowthPoints == newGrowthPointsBalance)
                    .SetColumns(u => u.UpdatedBy == userId.ToString())
                    .SetColumns(u => u.UpdatedAt == DateTime.Now)
                    .Where(u => u.Id == userId)
                    .ExecuteCommandAsync();

                // 4b. 批量插入积分流水记录
                var records = new List<PointsRecord>();
                foreach (var task in claimableTasks)
                {
                    records.Add(new PointsRecord
                    {
                        UserId = userId,
                        ChangeAmount = task.Points,
                        BalanceAfter = newPointsBalance,
                        ChangeType = PointsChangeTypeEnum.TaskReward.ToString(),
                        RelatedId = task.GroupId,
                        Description = $"完成任务奖励，GroupId：{task.GroupId}",
                        Type = PointsFlowTypeEnum.Income,
                        Status = (int)PointsRecordStatusEnum.Success,
                        IsDeleted = false,
                        CreatedBy = "系统",
                        CreatedAt = DateTime.Now,
                        UpdatedBy = "系统",
                        UpdatedAt = DateTime.Now
                    });
                }
                await answerRepository.Context.Insertable(records).ExecuteCommandAsync();

                // 4c. 喂养宠物（同一事务内，更新宠物成长值）
                if (pet != null && totalGrowthPoints > 0)
                {
                    await petService.FeedPetInTranAsync(userId, new Models.Dto.Pet.FeedPetInput
                    {
                        PetId = pet.Id,
                        GrowthPoints = totalGrowthPoints
                    });
                }
            });

            result.SuccessCount = claimableTasks.Count;
        }
        #endregion

        #region 5. 组装返回结果
        result.FailCount = result.FailItems.Count;

        return result;
        #endregion
    }
}
