using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tiku.Application.Assets;
using Tiku.Application.Learning;
using Tiku.Application.PlatformAdmin;
using Tiku.Application.Security;
using Tiku.Domain.Common;
using Tiku.Domain.Content;
using Tiku.Domain.Operations;
using Tiku.Domain.QuestionBanks;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Persistence;

namespace Tiku.Infrastructure.PlatformAdmin;

internal abstract partial class PlatformQuestionBankServiceBase
{
    protected Task<PlatformQuestionPage> GetQuestionsCoreAsync(
        PlatformAdminActor actor,
        PlatformQuestionBankFilter filter,
        CancellationToken cancellationToken = default)
    {
        return ExecuteAsync(actor, "查询公共题库题目", async (_, dbContext, tenantId, token) =>
        {
            if (!filter.QuestionBankId.HasValue) throw Error("请选择公共题库。", "question_bank_id_required");

            await RequireBankAsync(dbContext, tenantId, filter.QuestionBankId.Value, token);
            var query = dbContext.Questions.AsNoTracking().Where(item =>
                item.TenantId == tenantId && item.QuestionBankId == filter.QuestionBankId);
            if (filter.ContentNodeId.HasValue) query = query.Where(item => item.ContentNodeId == filter.ContentNodeId);
            if (!string.IsNullOrWhiteSpace(filter.Type))
            {
                var type = filter.Type.Trim();
                query = query.Where(item => item.Type == type);
            }

            if (filter.Difficulty.HasValue) query = query.Where(item => item.Difficulty == filter.Difficulty);
            if (!string.IsNullOrWhiteSpace(filter.Status) &&
                !filter.Status.Equals("all", StringComparison.OrdinalIgnoreCase))
            {
                var status = ParseQuestionStatus(filter.Status);
                query = query.Where(item => item.Status == status);
            }

            if (!string.IsNullOrWhiteSpace(filter.Keyword))
            {
                var keyword = filter.Keyword.Trim();
                query = query.Where(item => item.Type.Contains(keyword) || dbContext.QuestionVersions.Any(version =>
                    version.TenantId == tenantId && version.QuestionId == item.Id &&
                    version.Id == item.CurrentVersionId && version.Content != null &&
                    version.Content.Contains(keyword)));
            }

            var page = Math.Max(1, filter.Page);
            var pageSize = Math.Clamp(filter.PageSize, 1, MaxPageSize);
            var total = await query.CountAsync(token);
            var questions = await query.OrderByDescending(item => item.UpdatedAt).Skip((page - 1) * pageSize)
                .Take(pageSize).ToArrayAsync(token);
            var versionIds = questions.Where(item => item.CurrentVersionId.HasValue)
                .Select(item => item.CurrentVersionId!.Value).ToArray();
            var versions = await dbContext.QuestionVersions.AsNoTracking()
                .Where(item => item.TenantId == tenantId && versionIds.Contains(item.Id))
                .ToDictionaryAsync(item => item.Id, token);
            return new PlatformQuestionPage(
                questions.Select(item => ToQuestionItem(item,
                    item.CurrentVersionId.HasValue && versions.TryGetValue(item.CurrentVersionId.Value, out var version)
                        ? version
                        : null)).ToArray(), total, page, pageSize);
        }, cancellationToken);
    }

    protected Task<PlatformQuestionItem> UpsertQuestionCoreAsync(
        PlatformAdminActor actor,
        UpsertPlatformQuestionCommand command,
        CancellationToken cancellationToken = default)
    {
        return ExecuteAsync(actor, "保存公共题库题目", async (_, dbContext, tenantId, token) =>
        {
            var bank = await RequireBankWithEntryAsync(dbContext, tenantId, command.QuestionBankId, token);
            var node = await RequireNodeAsync(dbContext, tenantId, bank, command.ContentNodeId, token);
            ValidateQuestion(command);
            var question = command.Id.HasValue
                ? await dbContext.Questions.SingleOrDefaultAsync(
                    item => item.TenantId == tenantId && item.Id == command.Id.Value && item.QuestionBankId == bank.Id,
                    token)
                : null;
            if (command.Id.HasValue && question is null) throw Error("题目不存在。", "question_not_found");
            question ??= new Question
            {
                TenantId = tenantId,
                QuestionBankId = bank.Id,
                EntryId = bank.ContentEntryId,
                CreatedAt = DateTimeOffset.UtcNow
            };
            if (!command.Id.HasValue) dbContext.Questions.Add(question);
            ApplyQuestion(question, command, bank.ContentEntryId!.Value, node.Id);
            await dbContext.SaveChangesAsync(token);
            var nextVersion = await dbContext.QuestionVersions
                .Where(item => item.TenantId == tenantId && item.QuestionId == question.Id)
                .Select(item => (int?)item.VersionNo).MaxAsync(token) ?? 0;
            var version = BuildVersion(actor, tenantId, question.Id, nextVersion + 1, command);
            dbContext.QuestionVersions.Add(version);
            question.CurrentVersionId = version.Id;
            AddAudit(dbContext, actor, "platform.question_bank.question_saved", question.Id,
                new { bankId = bank.Id, nodeId = node.Id, version = version.VersionNo });
            await dbContext.SaveChangesAsync(token);
            return ToQuestionItem(question, version);
        }, cancellationToken);
    }

    protected Task<int> ArchiveQuestionsCoreAsync(
        PlatformAdminActor actor,
        ArchivePlatformQuestionsCommand command,
        CancellationToken cancellationToken = default)
    {
        return ExecuteAsync(actor, "归档公共题库题目", async (_, dbContext, tenantId, token) =>
        {
            var ids = command.QuestionIds.Distinct().Take(500).ToArray();
            if (ids.Length == 0) throw Error("请选择需要归档的题目。", "question_ids_required");
            var questions = await dbContext.Questions.Where(item => item.TenantId == tenantId && ids.Contains(item.Id))
                .ToArrayAsync(token);
            foreach (var question in questions) question.Status = QuestionStatus.Archived;
            AddAudit(dbContext, actor, "platform.question_bank.questions_archived", Guid.NewGuid(),
                new { questionIds = questions.Select(item => item.Id).ToArray() });
            await dbContext.SaveChangesAsync(token);
            return questions.Length;
        }, cancellationToken);
    }

    private static void ValidateQuestion(UpsertPlatformQuestionCommand command)
    {
        if (string.IsNullOrWhiteSpace(command.Content)) throw Error("题干不能为空。", "question_content_required");
        var type = string.IsNullOrWhiteSpace(command.Type) ? "choice" : command.Type.Trim();
        if (!SupportedQuestionTypes.Contains(type)) throw Error("题型不受支持。", "question_type_invalid");
        if (command.Difficulty is < 1 or > 5) throw Error("难度必须在 1 到 5 之间。", "question_difficulty_invalid");
        if (ParseQuestionStatus(command.Status) == QuestionStatus.Published &&
            !QuestionGrader.HasValidAuthoritativeAnswer(
                type,
                command.CorrectOptionIndex,
                command.CorrectOptionIndices,
                command.AnswerText))
            throw Error("发布题目必须提供有效的标准答案。", "question_grading_rule_invalid");
    }

    private static void ApplyQuestion(Question question, UpsertPlatformQuestionCommand command, Guid entryId,
        Guid nodeId)
    {
        question.QuestionBankId = command.QuestionBankId;
        question.EntryId = entryId;
        question.ContentNodeId = nodeId;
        question.LegacyId = Normalize(command.LegacyId);
        question.Type = Normalize(command.Type) ?? "choice";
        question.TypeLabel = Normalize(command.TypeLabel);
        question.Difficulty = command.Difficulty;
        question.Tags = ArrayOrDefault(command.Tags);
        question.MediaUrl = Normalize(command.MediaUrl);
        question.ExamMarkers = ObjectOrDefault(command.ExamMarkers);
        question.Status = ParseQuestionStatus(command.Status);
    }

    private static QuestionVersion BuildVersion(PlatformAdminActor actor, Guid tenantId, Guid questionId, int versionNo,
        UpsertPlatformQuestionCommand command)
    {
        return new QuestionVersion
        {
            TenantId = tenantId,
            QuestionId = questionId,
            VersionNo = versionNo,
            Content = Normalize(command.Content),
            Options = ArrayOrDefault(command.Options),
            CorrectOptionIndex = command.CorrectOptionIndex,
            CorrectOptionIndices = ArrayOrDefault(command.CorrectOptionIndices),
            AnswerText = Normalize(command.AnswerText),
            Explanation = Normalize(command.Explanation),
            SubQuestions = ArrayOrDefault(command.SubQuestions),
            CodeLang = Normalize(command.CodeLang),
            CodeTemplate = Normalize(command.CodeTemplate),
            SourceHash = Normalize(command.SourceHash) ?? Hash(JsonSerializer.Serialize(command)),
            CreatedBy = actor.UserId
        };
    }

}
