using QYZH.InteractiveMagazine.Infrastructure.OSS;
using QYZH.InteractiveMagazine.Models.Entity;
using QYZH.InteractiveMagazine.Models.Enum;
using SqlSugar;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace QYZH.InteractiveMagazine.WorkService.Consumers;

/// <summary>
/// 期刊任务接收消费者
/// </summary>
public class JournalTaskReceiveConsumer(
    ILogger<JournalTaskReceiveConsumer> logger,
    IConfiguration configuration,
    IServiceScopeFactory scopeFactory,
    IHttpClientFactory httpClientFactory,
    OssService ossService) : IQueueConsumer
{
    private const int DefaultAiScoreMaxRetryCount = 3;
    private const int DefaultAiScoreRetryDelayMilliseconds = 1000;
    private const long DefaultMaxImageBytes = 10 * 1024 * 1024;

    public string Exchange => "ex.journal";

    public string QueueName => "mq.journal.task.receive";

    public string RoutingKey => "rk.journal.task.receive";

    public async Task HandleAsync(byte[] body, CancellationToken cancellationToken = default)
    {
        var message = Encoding.UTF8.GetString(body);
        logger.LogInformation("收到期刊任务消息: {Message}", message);

        using var scope = scopeFactory.CreateScope();
        var client = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
        var data = JsonSerializer.Deserialize<QuestionData>(message, new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        }) ?? throw new InvalidOperationException("期刊任务消息内容为空");

        if (data.Questions == null || data.Questions.Length == 0)
        {
            logger.LogWarning("期刊任务消息没有题目，UserId: {UserId}, JournalId: {JournalId}, PageId: {PageId}", data.UserId, data.JournalId, data.PageId);
            return;
        }

        var taskIds = data.Questions.Select(q => q.Id).Distinct().ToList();
        var tasks = await client.Queryable<JournalPageTask>()
            .Where(t => taskIds.Contains(t.Id) && !t.IsDeleted)
            .ToListAsync(cancellationToken);
        var taskMap = tasks.ToDictionary(t => t.Id);
        var referenceAnswers = await client.Queryable<JournalPageTaskAnswer>()
            .Where(a => taskIds.Contains(a.JournalPageTaskId) && !a.IsDeleted)
            .ToListAsync(cancellationToken);
        var referenceAnswerMap = referenceAnswers
            .GroupBy(a => a.JournalPageTaskId)
            .ToDictionary(g => g.Key, g => g.ToList());

        var page = await client.Queryable<JournalPage>()
            .Where(p => p.Id == data.PageId && !p.IsDeleted)
            .FirstAsync(cancellationToken);

        var answerEntities = new List<JournalPageTaskUserAnswer>();
        foreach (var question in data.Questions)
        {
            if (!taskMap.TryGetValue(question.Id, out var task))
            {
                logger.LogWarning("未找到期刊任务，TaskId: {TaskId}, UserId: {UserId}", question.Id, data.UserId);
                continue;
            }

            if (!task.NeedAiProcess)
            {
                logger.LogInformation("期刊任务配置为人工批改，跳过AI评分，TaskId: {TaskId}, UserId: {UserId}", task.Id, data.UserId);
                continue;
            }

            referenceAnswerMap.TryGetValue(task.Id, out var taskReferenceAnswers);
            var scoreResult = await ScoreQuestionAsync(task, question, taskReferenceAnswers ?? [], cancellationToken);
            answerEntities.Add(BuildAnswerEntity(data, question, task, page, scoreResult));
        }

        if (answerEntities.Count == 0)
        {
            logger.LogWarning("期刊任务消息没有可入库的答题记录，UserId: {UserId}, JournalId: {JournalId}, PageId: {PageId}", data.UserId, data.JournalId, data.PageId);
            return;
        }

        client.Ado.BeginTran();
        try
        {
            foreach (var answer in answerEntities)
            {
                var existing = await client.Queryable<JournalPageTaskUserAnswer>()
                    .Where(a => a.UserId == answer.UserId && a.JournalPageTaskId == answer.JournalPageTaskId && !a.IsDeleted)
                    .FirstAsync(cancellationToken);

                if (existing == null)
                {
                    await client.Insertable(answer).ExecuteCommandAsync(cancellationToken);
                    continue;
                }

                await client.Insertable(BuildAnswerSnapshot(existing)).ExecuteCommandAsync(cancellationToken);

                answer.Id = existing.Id;
                answer.CreatedBy = existing.CreatedBy;
                answer.CreatedAt = existing.CreatedAt;
                answer.UpdatedBy = answer.UserId.ToString();
                answer.UpdatedAt = DateTime.Now;

                await client.Updateable(answer)
                    .IgnoreColumns(a => new { a.CreatedBy, a.CreatedAt })
                    .Where(a => a.Id == existing.Id)
                    .ExecuteCommandAsync(cancellationToken);
            }

            client.Ado.CommitTran();
        }
        catch
        {
            client.Ado.RollbackTran();
            throw;
        }

        logger.LogInformation("期刊任务答题记录保存完成，UserId: {UserId}, JournalId: {JournalId}, PageId: {PageId}, Count: {Count}",
            data.UserId, data.JournalId, data.PageId, answerEntities.Count);
    }

    public Task OnErrorAsync(byte[] body, Exception exception)
    {
        var message = Encoding.UTF8.GetString(body);
        logger.LogError(exception, "处理期刊任务消息失败: {Message}", message);
        return Task.CompletedTask;
    }

    private async Task<JournalAnswerScoreResult> ScoreQuestionAsync(
        JournalPageTask task,
        Question question,
        List<JournalPageTaskAnswer> referenceAnswers,
        CancellationToken cancellationToken)
    {
        var apiKey = configuration["AiChat:ApiKey"];
        var baseUrl = configuration["AiChat:BaseUrl"];
        var model = configuration["AiChat:Model"];
        var timeoutSeconds = configuration.GetValue<int>("AiChat:TimeoutSeconds");
        var maxTokens = configuration.GetValue<int>("AiChat:MaxTokens");
        var temperature = configuration.GetValue<double>("AiChat:Temperature");

        if (string.IsNullOrWhiteSpace(apiKey) || string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(model))
        {
            throw new InvalidOperationException("AI聊天服务配置不完整，请检查 AiChat 配置节点");
        }

        var answerImages = await BuildAnswerImageContentsAsync(question, cancellationToken);
        if (answerImages.Count == 0)
        {
            throw new InvalidOperationException($"题目 {question.Id} 缺少答案图片");
        }

        var referenceAnswerImages = await BuildReferenceAnswerImageContentsAsync(task.Id, referenceAnswers, cancellationToken);

        var content = new List<object>
        {
            new
            {
                type = "text",
                text = BuildScorePrompt(task, question, answerImages.Count, referenceAnswers, referenceAnswerImages.Count)
            }
        };

        foreach (var answerImage in answerImages)
        {
            content.Add(new
            {
                type = "image_url",
                image_url = new { url = answerImage.DataUrl }
            });
        }

        if (referenceAnswerImages.Count > 0)
        {
            content.Add(new
            {
                type = "text",
                text = $"以下为参考答案图片，共 {referenceAnswerImages.Count} 张。参考答案不是必有，评分时以题目Prompt和学生答案为主。"
            });

            foreach (var referenceAnswerImage in referenceAnswerImages)
            {
                content.Add(new
                {
                    type = "image_url",
                    image_url = new { url = referenceAnswerImage.DataUrl }
                });
            }
        }

        var requestBody = new
        {
            model,
            messages = new object[]
            {
                new
                {
                    role = "system",
                    content = "你是专业的学生作答评分助手。必须只返回合法 JSON，不要返回 Markdown、解释或代码块。"
                },
                new
                {
                    role = "user",
                    content
                }
            },
            max_tokens = maxTokens > 0 ? maxTokens : 2000,
            temperature = temperature > 0 ? temperature : 0.2,
            stream = false,
            response_format = new { type = "json_object" }
        };

        var requestJson = JsonSerializer.Serialize(requestBody, new JsonSerializerOptions
        {
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
        });

        var responseContent = await SendAiScoreRequestWithRetryAsync(
            task.Id,
            $"{baseUrl.TrimEnd('/')}/chat/completions",
            apiKey,
            requestJson,
            timeoutSeconds > 0 ? timeoutSeconds : 300,
            cancellationToken);
        var resultJson = ExtractAssistantContent(responseContent);
        var scoreResult = ParseScoreResult(resultJson);
        scoreResult.Result = TrimResult(scoreResult.Result);
        return scoreResult;
    }

    private static string BuildScorePrompt(
        JournalPageTask task,
        Question question,
        int answerImageCount,
        List<JournalPageTaskAnswer> referenceAnswers,
        int referenceAnswerImageCount)
    {
        var referenceAnswerTexts = referenceAnswers
            .Select(a => a.Answer?.Trim())
            .Where(a => !string.IsNullOrWhiteSpace(a))
            .Distinct()
            .ToList();

        var prompt = new StringBuilder();
        prompt.AppendLine("参考答案不是必有；若无参考答案，以题目 Prompt 和学生答案为准评分。");
        prompt.AppendLine($"参考答案图片数量：{referenceAnswerImageCount}");
        if (referenceAnswerTexts.Count > 0)
        {
            prompt.AppendLine("参考答案文本：");
            for (var i = 0; i < referenceAnswerTexts.Count; i++)
            {
                prompt.AppendLine($"{i + 1}. {referenceAnswerTexts[i]}");
            }
        }
        else
        {
            prompt.AppendLine("参考答案文本：无");
        }
        prompt.AppendLine();
        prompt.AppendLine("请根据题目评分 Prompt 和学生答案图片进行评分。");
        prompt.AppendLine();
        prompt.AppendLine($"题目内容：{task.Task}");
        prompt.AppendLine("评分Prompt：");
        prompt.AppendLine(task.Prompt);
        prompt.AppendLine();
        prompt.AppendLine("题目配置：");
        prompt.AppendLine($"- 成长值上限：{task.GrowthPoint}");
        prompt.AppendLine($"- 积分上限：{task.Points}");
        prompt.AppendLine($"- 理解力上限：{task.Comprehension}");
        prompt.AppendLine($"- 判断力上限：{task.Judgment}");
        prompt.AppendLine($"- 表达力上限：{task.Expression}");
        prompt.AppendLine($"- 说服力上限：{task.Persuasiveness}");
        prompt.AppendLine();
        prompt.AppendLine($"学生答案图片数量：{answerImageCount}");
        prompt.AppendLine();
        prompt.AppendLine("只返回如下 JSON 字段：");
        prompt.AppendLine("{");
        prompt.AppendLine("  \"Score\": 0,");
        prompt.AppendLine("  \"GrowthPoint\": 0,");
        prompt.AppendLine("  \"Points\": 0,");
        prompt.AppendLine("  \"Comprehension\": 0,");
        prompt.AppendLine("  \"Judgment\": 0,");
        prompt.AppendLine("  \"Expression\": 0,");
        prompt.AppendLine("  \"Persuasiveness\": 0,");
        prompt.AppendLine("  \"Result\": \"50字内的中文评语\"");
        prompt.AppendLine("}");
        return prompt.ToString();
    }

    private async Task<List<AnswerImageContent>> BuildAnswerImageContentsAsync(Question question, CancellationToken cancellationToken)
    {
        var imageUrls = question.AnswerUrl?.Where(url => !string.IsNullOrWhiteSpace(url)).Distinct().ToList() ?? [];
        var maxImageBytes = configuration.GetValue<long>("AiChat:MaxImageBytes");
        if (maxImageBytes <= 0)
        {
            maxImageBytes = DefaultMaxImageBytes;
        }

        var result = new List<AnswerImageContent>();
        foreach (var imageUrl in imageUrls)
        {
            cancellationToken.ThrowIfCancellationRequested();

            await using var imageStream = ossService.GetObjectStream(imageUrl);
            if (imageStream == null)
            {
                throw new InvalidOperationException($"答案图片读取失败，TaskId: {question.Id}, Url: {imageUrl}");
            }

            using var memoryStream = new MemoryStream();
            await imageStream.CopyToAsync(memoryStream, cancellationToken);
            if (memoryStream.Length == 0)
            {
                throw new InvalidOperationException($"答案图片内容为空，TaskId: {question.Id}, Url: {imageUrl}");
            }

            if (memoryStream.Length > maxImageBytes)
            {
                throw new InvalidOperationException($"答案图片超过大小限制，TaskId: {question.Id}, Url: {imageUrl}, Size: {memoryStream.Length}");
            }

            var imageBytes = memoryStream.ToArray();
            var mimeType = GetImageMimeType(imageUrl, imageBytes);
            result.Add(new AnswerImageContent($"data:{mimeType};base64,{Convert.ToBase64String(imageBytes)}"));
        }

        return result;
    }

    private async Task<List<AnswerImageContent>> BuildReferenceAnswerImageContentsAsync(
        long taskId,
        List<JournalPageTaskAnswer> referenceAnswers,
        CancellationToken cancellationToken)
    {
        var imageUrls = referenceAnswers
            .Select(a => a.AnswerUrL)
            .Where(url => !string.IsNullOrWhiteSpace(url))
            .Distinct()
            .Select(url => url!)
            .ToList();
        var maxImageBytes = configuration.GetValue<long>("AiChat:MaxImageBytes");
        if (maxImageBytes <= 0)
        {
            maxImageBytes = DefaultMaxImageBytes;
        }

        var result = new List<AnswerImageContent>();
        foreach (var imageUrl in imageUrls)
        {
            cancellationToken.ThrowIfCancellationRequested();

            try
            {
                await using var imageStream = ossService.GetObjectStream(imageUrl);
                if (imageStream == null)
                {
                    throw new InvalidOperationException($"参考答案图片读取失败，TaskId: {taskId}, Url: {imageUrl}");
                }

                using var memoryStream = new MemoryStream();
                await imageStream.CopyToAsync(memoryStream, cancellationToken);
                if (memoryStream.Length == 0)
                {
                    throw new InvalidOperationException($"参考答案图片内容为空，TaskId: {taskId}, Url: {imageUrl}");
                }

                if (memoryStream.Length > maxImageBytes)
                {
                    throw new InvalidOperationException($"参考答案图片超过大小限制，TaskId: {taskId}, Url: {imageUrl}, Size: {memoryStream.Length}");
                }

                var imageBytes = memoryStream.ToArray();
                var mimeType = GetImageMimeType(imageUrl, imageBytes);
                result.Add(new AnswerImageContent($"data:{mimeType};base64,{Convert.ToBase64String(imageBytes)}"));
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                logger.LogWarning(ex, "参考答案图片读取失败，已跳过，TaskId: {TaskId}, Url: {Url}", taskId, imageUrl);
            }
        }

        return result;
    }

    private async Task<string> SendAiScoreRequestWithRetryAsync(
        long taskId,
        string requestUrl,
        string apiKey,
        string requestJson,
        int timeoutSeconds,
        CancellationToken cancellationToken)
    {
        var maxRetryCount = configuration.GetValue<int>("AiChat:ScoreMaxRetryCount");
        if (maxRetryCount <= 0)
        {
            maxRetryCount = DefaultAiScoreMaxRetryCount;
        }

        var retryDelayMilliseconds = configuration.GetValue<int>("AiChat:ScoreRetryDelayMilliseconds");
        if (retryDelayMilliseconds <= 0)
        {
            retryDelayMilliseconds = DefaultAiScoreRetryDelayMilliseconds;
        }

        Exception? lastException = null;
        for (var attempt = 1; attempt <= maxRetryCount; attempt++)
        {
            cancellationToken.ThrowIfCancellationRequested();

            try
            {
                var httpClient = httpClientFactory.CreateClient();
                httpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds);

                using var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
                request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json");

                using var response = await httpClient.SendAsync(request, cancellationToken);
                var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
                if (response.IsSuccessStatusCode)
                {
                    return responseContent;
                }

                logger.LogWarning("AI评分调用失败，TaskId: {TaskId}, Attempt: {Attempt}/{MaxRetryCount}, StatusCode: {StatusCode}, Response: {Response}",
                    taskId, attempt, maxRetryCount, response.StatusCode, responseContent);

                if (!ShouldRetry(response.StatusCode) || attempt == maxRetryCount)
                {
                    throw new InvalidOperationException($"AI评分调用失败：{response.StatusCode}，响应：{responseContent}");
                }
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex) when (attempt < maxRetryCount)
            {
                lastException = ex;
                logger.LogWarning(ex, "AI评分调用异常，准备重试，TaskId: {TaskId}, Attempt: {Attempt}/{MaxRetryCount}", taskId, attempt, maxRetryCount);
            }

            var delay = TimeSpan.FromMilliseconds(retryDelayMilliseconds * attempt);
            await Task.Delay(delay, cancellationToken);
        }

        throw new InvalidOperationException($"AI评分调用失败，TaskId: {taskId}", lastException);
    }

    private static bool ShouldRetry(System.Net.HttpStatusCode statusCode)
    {
        var status = (int)statusCode;
        return status == 408 || status == 429 || status >= 500;
    }

    private static string GetImageMimeType(string imageUrl, byte[] imageBytes)
    {
        if (imageBytes.Length >= 4)
        {
            if (imageBytes[0] == 0x89 && imageBytes[1] == 0x50 && imageBytes[2] == 0x4E && imageBytes[3] == 0x47)
            {
                return "image/png";
            }

            if (imageBytes[0] == 0xFF && imageBytes[1] == 0xD8)
            {
                return "image/jpeg";
            }

            if (imageBytes[0] == 0x47 && imageBytes[1] == 0x49 && imageBytes[2] == 0x46)
            {
                return "image/gif";
            }

            if (imageBytes[0] == 0x52 && imageBytes[1] == 0x49 && imageBytes[2] == 0x46 && imageBytes[3] == 0x46)
            {
                return "image/webp";
            }
        }

        var extension = Path.GetExtension(imageUrl).ToLowerInvariant();
        return extension switch
        {
            ".png" => "image/png",
            ".jpg" or ".jpeg" => "image/jpeg",
            ".gif" => "image/gif",
            ".webp" => "image/webp",
            _ => "image/jpeg"
        };
    }

    private static string ExtractAssistantContent(string responseContent)
    {
        using var document = JsonDocument.Parse(responseContent);
        var message = document.RootElement.GetProperty("choices")[0].GetProperty("message");
        if (!message.TryGetProperty("content", out var contentElement))
        {
            throw new InvalidOperationException("AI评分响应缺少 content");
        }

        if (contentElement.ValueKind == JsonValueKind.String)
        {
            return contentElement.GetString() ?? string.Empty;
        }

        return contentElement.GetRawText();
    }

    private static JournalAnswerScoreResult ParseScoreResult(string resultJson)
    {
        var cleanedJson = CleanJsonContent(resultJson);
        var result = JsonSerializer.Deserialize<JournalAnswerScoreResult>(cleanedJson, new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        });

        return result ?? throw new InvalidOperationException("AI评分结果解析失败");
    }

    private static string CleanJsonContent(string content)
    {
        var text = content.Trim();
        if (text.StartsWith("```", StringComparison.Ordinal))
        {
            var firstLineEnd = text.IndexOf('\n');
            if (firstLineEnd >= 0)
            {
                text = text[(firstLineEnd + 1)..];
            }

            var fenceIndex = text.LastIndexOf("```", StringComparison.Ordinal);
            if (fenceIndex >= 0)
            {
                text = text[..fenceIndex];
            }
        }

        var start = text.IndexOf('{');
        var end = text.LastIndexOf('}');
        if (start >= 0 && end > start)
        {
            text = text[start..(end + 1)];
        }

        return text.Trim();
    }

    private static JournalPageTaskUserAnswer BuildAnswerEntity(
        QuestionData data,
        Question question,
        JournalPageTask task,
        JournalPage? page,
        JournalAnswerScoreResult scoreResult)
    {
        var now = DateTime.Now;
        var growthPoint = Math.Max(0, scoreResult.GrowthPoint);
        var points = Math.Max(0, scoreResult.Points);

        return new JournalPageTaskUserAnswer
        {
            JournalId = data.JournalId,
            JournalPageId = data.PageId,
            JournalPageTaskId = task.Id,
            JournalPageTaskGroupId = task.GroupId,
            UserId = data.UserId,
            Result = scoreResult.Result,
            Points = points,
            GrowthPoints = growthPoint,
            Score = Math.Max(0, scoreResult.Score),
            Comprehension = Math.Max(0, scoreResult.Comprehension),
            Judgment = Math.Max(0, scoreResult.Judgment),
            Expression = Math.Max(0, scoreResult.Expression),
            Persuasiveness = Math.Max(0, scoreResult.Persuasiveness),
            QuestionAnswerUrl = question.Url,
            AnswerUrl = JsonSerializer.Serialize(question.AnswerUrl ?? []),
            PageAnswerUrl = data.PageAnswerUrl,
            Revision = 0,           
            AnswerStartTime = question.AnswerStartTime,
            AnswerEndTime = question.AnswerEndTime,
            AnswerSeconds = question.AnswerTime,
            ImageRecognition = 0,
            JournalPageNum = page?.PageNum ?? 0,
            Modify = 0,
            LastTag = 0,
            DotPageNum = page?.PageNum ?? 0,
            PageResultUrl = string.Empty,
            Type = task.Type.ToString(),
            DotPageNo = page?.PageNo ?? string.Empty,
            PageAnswerDotUrl = string.Empty,
            BreakCount = question.BreakCount,
            BreakTimes = JsonSerializer.Serialize(question.BreakTimes ?? []),
            AssignmentStatus = UserAnswerStatusEnum.Complete.ToString(),
            Status = (int)UserAnswerStatusEnum.Complete,
            CreatedBy = data.UserId.ToString(),
            CreatedAt = data.CreatedTime == default ? now : data.CreatedTime,
            UpdatedBy = data.UserId.ToString(),
            UpdatedAt = now
        };
    }

    private static JournalPageTaskUserAnswerSnapshot BuildAnswerSnapshot(JournalPageTaskUserAnswer answer)
    {
        var now = DateTime.Now;
        return new JournalPageTaskUserAnswerSnapshot
        {
            JournalPageTaskUserAnswerId = answer.Id,
            JournalId = answer.JournalId,
            JournalPageId = answer.JournalPageId,
            JournalPageTaskId = answer.JournalPageTaskId,
            JournalPageTaskGroupId = answer.JournalPageTaskGroupId,
            UserId = answer.UserId,
            Result = answer.Result,
            Points = answer.Points,
            GrowthPoints = answer.GrowthPoints,
            Score = answer.Score,
            Comprehension = answer.Comprehension,
            Judgment = answer.Judgment,
            Expression = answer.Expression,
            Persuasiveness = answer.Persuasiveness,
            QuestionAnswerUrl = answer.QuestionAnswerUrl,
            AnswerUrl = answer.AnswerUrl,
            PageAnswerUrl = answer.PageAnswerUrl,
            Revision = answer.Revision,           
            AnswerStartTime = answer.AnswerStartTime,
            AnswerEndTime = answer.AnswerEndTime,
            AnswerSeconds = answer.AnswerSeconds,
            ImageRecognition = answer.ImageRecognition,
            JournalPageNum = answer.JournalPageNum,
            Modify = answer.Modify,
            LastTag = answer.LastTag,
            DotPageNum = answer.DotPageNum,
            PageResultUrl = answer.PageResultUrl,
            Type = answer.Type,
            DotPageNo = answer.DotPageNo,
            PageAnswerDotUrl = answer.PageAnswerDotUrl,
            BreakCount = answer.BreakCount,
            BreakTimes = answer.BreakTimes,
            AssignmentStatus = answer.AssignmentStatus,
            Status = answer.Status,
            CreatedBy = answer.UpdatedBy ?? answer.CreatedBy ?? string.Empty,
            CreatedAt = now,
            UpdatedBy = answer.UpdatedBy ?? string.Empty,
            UpdatedAt = now
        };
    }

    private static string TrimResult(string? result)
    {
        if (string.IsNullOrWhiteSpace(result))
        {
            return string.Empty;
        }

        return result.Length <= 50 ? result : result[..50];
    }
}

/// <summary>
/// 期刊题目作答消息
/// </summary>
public class QuestionData
{
    /// <summary>
    /// 用户ID
    /// </summary>
    public long UserId { get; set; }

    /// <summary>
    /// 期刊ID
    /// </summary>
    public long JournalId { get; set; }

    /// <summary>
    /// 页ID
    /// </summary>
    public long PageId { get; set; }

    /// <summary>
    /// 页面作答图片地址
    /// </summary>
    public string PageAnswerUrl { get; set; } = string.Empty;

    /// <summary>
    /// 题目作答列表
    /// </summary>
    public Question[] Questions { get; set; } = [];

    /// <summary>
    /// 创建时间
    /// </summary>
    public DateTime CreatedTime { get; set; }
}

/// <summary>
/// 题目作答数据
/// </summary>
public class Question
{
    /// <summary>
    /// 题目ID
    /// </summary>
    public long Id { get; set; }

    /// <summary>
    /// 题目图片地址
    /// </summary>
    public string Url { get; set; } = string.Empty;

    /// <summary>
    /// 答案图片地址
    /// </summary>
    public string[] AnswerUrl { get; set; } = [];

    /// <summary>
    /// 作答开始时间
    /// </summary>
    public DateTime AnswerStartTime { get; set; }

    /// <summary>
    /// 作答结束时间
    /// </summary>
    public DateTime AnswerEndTime { get; set; }

    /// <summary>
    /// 作答耗时秒数
    /// </summary>
    public int AnswerTime { get; set; }

    /// <summary>
    /// 中断次数
    /// </summary>
    public int BreakCount { get; set; }

    /// <summary>
    /// 中断记录
    /// </summary>
    public List<BreakTime> BreakTimes { get; set; } = [];
}

/// <summary>
/// 中断时间记录
/// </summary>
public class BreakTime
{
    /// <summary>
    /// 中断时间
    /// </summary>
    public DateTime Time { get; set; }

    /// <summary>
    /// 等待时间
    /// </summary>
    public long WaitTime { get; set; }
}

/// <summary>
/// AI评分结果
/// </summary>
public class JournalAnswerScoreResult
{
    /// <summary>
    /// 题目得分
    /// </summary>
    public float Score { get; set; }

    /// <summary>
    /// 成长值
    /// </summary>
    public int GrowthPoint { get; set; }

    /// <summary>
    /// 积分
    /// </summary>
    public int Points { get; set; }

    /// <summary>
    /// 理解力评分
    /// </summary>
    public float Comprehension { get; set; }

    /// <summary>
    /// 判断力评分
    /// </summary>
    public float Judgment { get; set; }

    /// <summary>
    /// 表达力评分
    /// </summary>
    public float Expression { get; set; }

    /// <summary>
    /// 说服力评分
    /// </summary>
    public float Persuasiveness { get; set; }

    /// <summary>
    /// 50字内评语
    /// </summary>
    public string Result { get; set; } = string.Empty;
}

public record AnswerImageContent(string DataUrl);
