using System.Globalization;
using System.Text;
using System.Text.Json;
using Tiku.Domain.Learning;

namespace Tiku.Application.Learning;

public sealed record QuestionGradingInput(
    string QuestionType,
    int? CorrectOptionIndex,
    JsonElement CorrectOptionIndices,
    string? CorrectAnswerText,
    JsonElement GradingRules,
    IReadOnlyCollection<int>? SelectedOptionIndices,
    string? AnswerText,
    decimal Score);

public sealed record QuestionGradingResult(
    AnswerGradingStatus Status,
    bool? IsCorrect,
    decimal? AwardedScore);

public static class QuestionGrader
{
    private static readonly IReadOnlySet<string> SubjectiveTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "short_answer", "reading", "programming"
    };

    public static QuestionGradingResult Grade(QuestionGradingInput input)
    {
        var type = input.QuestionType.Trim().ToLowerInvariant();
        if (SubjectiveTypes.Contains(type))
            return new QuestionGradingResult(AnswerGradingStatus.PendingReview, null, null);

        var correct = type switch
        {
            "choice" => GradeChoice(input),
            "multiple_choice" => GradeMultipleChoice(input),
            "true_false" => GradeTrueFalse(input),
            "fill_blank" => GradeFillBlank(input),
            _ => throw new InvalidOperationException($"Unsupported question type '{input.QuestionType}'.")
        };
        return new QuestionGradingResult(
            correct ? AnswerGradingStatus.Correct : AnswerGradingStatus.Incorrect,
            correct,
            correct ? input.Score : 0);
    }

    public static bool HasValidAuthoritativeAnswer(
        string questionType,
        int? correctOptionIndex,
        JsonElement correctOptionIndices,
        string? answerText)
    {
        return questionType.Trim().ToLowerInvariant() switch
        {
            "choice" => correctOptionIndex is >= 0,
            "multiple_choice" => ReadIndices(correctOptionIndices).Count > 0,
            "true_false" => TryNormalizeBoolean(answerText, out _),
            "fill_blank" => !string.IsNullOrWhiteSpace(answerText),
            "short_answer" or "reading" or "programming" => true,
            _ => false
        };
    }

    private static bool GradeChoice(QuestionGradingInput input)
    {
        var selected = input.SelectedOptionIndices?.Distinct().ToArray() ?? [];
        return input.CorrectOptionIndex.HasValue &&
               selected.Length == 1 &&
               selected[0] == input.CorrectOptionIndex.Value;
    }

    private static bool GradeMultipleChoice(QuestionGradingInput input)
    {
        var expected = ReadIndices(input.CorrectOptionIndices);
        var selected = (input.SelectedOptionIndices ?? []).Distinct().Order().ToArray();
        return expected.Count > 0 && expected.SequenceEqual(selected);
    }

    private static bool GradeTrueFalse(QuestionGradingInput input)
    {
        return TryNormalizeBoolean(input.CorrectAnswerText, out var expected) &&
               TryNormalizeBoolean(input.AnswerText, out var actual) &&
               expected == actual;
    }

    private static bool GradeFillBlank(QuestionGradingInput input)
    {
        var accepted = new HashSet<string>(StringComparer.Ordinal);
        AddNormalized(accepted, input.CorrectAnswerText);
        if (input.GradingRules.ValueKind == JsonValueKind.Object &&
            input.GradingRules.TryGetProperty("acceptedAnswers", out var alternatives) &&
            alternatives.ValueKind == JsonValueKind.Array)
            foreach (var alternative in alternatives.EnumerateArray())
                if (alternative.ValueKind == JsonValueKind.String)
                    AddNormalized(accepted, alternative.GetString());

        return accepted.Count > 0 && accepted.Contains(NormalizeText(input.AnswerText));
    }

    private static IReadOnlyList<int> ReadIndices(JsonElement value)
    {
        if (value.ValueKind != JsonValueKind.Array) return [];

        return value.EnumerateArray()
            .Where(item => item.TryGetInt32(out _))
            .Select(item => item.GetInt32())
            .Distinct()
            .Order()
            .ToArray();
    }

    private static bool TryNormalizeBoolean(string? value, out bool result)
    {
        switch (NormalizeText(value))
        {
            case "true" or "1" or "yes" or "正确" or "对":
                result = true;
                return true;
            case "false" or "0" or "no" or "错误" or "错":
                result = false;
                return true;
            default:
                result = false;
                return false;
        }
    }

    private static void AddNormalized(ISet<string> values, string? value)
    {
        var normalized = NormalizeText(value);
        if (normalized.Length > 0) values.Add(normalized);
    }

    private static string NormalizeText(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return string.Empty;

        var normalized = value.Normalize(NormalizationForm.FormKC).Trim().ToLower(CultureInfo.InvariantCulture);
        var builder = new StringBuilder(normalized.Length);
        var previousWhitespace = false;
        foreach (var character in normalized)
            if (char.IsWhiteSpace(character))
            {
                if (!previousWhitespace)
                {
                    builder.Append(' ');
                    previousWhitespace = true;
                }
            }
            else
            {
                builder.Append(character);
                previousWhitespace = false;
            }

        return builder.ToString();
    }
}