using System.Text.Json.Nodes;
using Eis.Infrastructure.Administration;

namespace Eis.Infrastructure.Tests.Administration;

public sealed class AdminResultStatisticsTests
{
    [Theory]
    [InlineData(0, 100, true)]
    [InlineData(10, 100, true)]
    [InlineData(10.5, 100, true)]
    [InlineData(10.1, 100, false)]
    [InlineData(-0.5, 100, false)]
    [InlineData(100.5, 100, false)]
    public void IsValidScore_OnlyAcceptsWholeOrHalfPoints(double score, double fullScore, bool expected)
    {
        Assert.Equal(expected, AdminResultStatistics.IsValidScore(score, fullScore));
    }

    [Fact]
    public void Calculate_ComputesEachSubjectAndTotalRankingOnceForTheExam()
    {
        var exam = Exam("rank_percent", 50);
        var registrations = new[] { Registration("r1"), Registration("r2"), Registration("r3") };
        var results = new[]
        {
            Result("r1-s1", "r1", "s1", 90), Result("r1-s2", "r1", "s2", 80),
            Result("r2-s1", "r2", "s1", 90), Result("r2-s2", "r2", "s2", 70),
            Result("r3-s1", "r3", "s1", 60), Result("r3-s2", "r3", "s2", 70)
        };

        var calculation = AdminResultStatistics.Calculate(exam, registrations, results, "2026-07-23T00:00:00.000Z");

        Assert.Equal(6, calculation.Metrics.Count);
        Assert.Equal(1, calculation.Metrics.Single(item => item.ResultId == "r1-s1").Rank);
        Assert.Equal(1, calculation.Metrics.Single(item => item.ResultId == "r2-s1").Rank);
        Assert.Equal(3, calculation.Metrics.Single(item => item.ResultId == "r3-s1").Rank);
        Assert.Equal(2, calculation.Metrics.Single(item => item.ResultId == "r2-s2").Rank);
        Assert.Equal(2, calculation.Metrics.Single(item => item.ResultId == "r3-s2").Rank);

        var summaries = calculation.Summaries.ToDictionary(item => item.RegistrationId);
        Assert.Equal(170, summaries["r1"].TotalScore);
        Assert.Equal(1, summaries["r1"].Rank);
        Assert.True(summaries["r1"].Qualified);
        Assert.Equal(2, summaries["r2"].Rank);
        Assert.True(summaries["r2"].Qualified);
        Assert.Equal(3, summaries["r3"].Rank);
        Assert.False(summaries["r3"].Qualified);
        Assert.All(summaries.Values, item => Assert.Equal(3, item.CohortSize));
    }

    [Fact]
    public void Calculate_PersistsIncompleteAndDraftStatesWithoutIncludingDraftInPublishedSummary()
    {
        var exam = Exam("subject_scores", 0);
        var registrations = new[] { Registration("r1"), Registration("r2") };
        var results = new[]
        {
            Result("r1-s1", "r1", "s1", 95),
            Result("r1-s2", "r1", "s2", 88, published: false),
            Result("r2-s1", "r2", "s1", 80),
            Result("r2-s2", "r2", "s2", 75)
        };

        var calculation = AdminResultStatistics.Calculate(exam, registrations, results, "2026-07-23T00:00:00.000Z");
        var draft = calculation.Metrics.Single(item => item.ResultId == "r1-s2");
        var summaries = calculation.Summaries.ToDictionary(item => item.RegistrationId);

        Assert.Equal("待发布", draft.Grade);
        Assert.Equal(2, draft.CohortSize);
        Assert.False(summaries["r1"].Complete);
        Assert.Null(summaries["r1"].Qualified);
        Assert.Equal(1, summaries["r1"].PublishedSubjectCount);
        Assert.True(summaries["r2"].Complete);
    }

    private static OperationalExam Exam(string passPolicy, double passValue)
    {
        var subjects = new[]
        {
            new JsonObject { ["id"] = "s1", ["name"] = "语文", ["fullScore"] = 100, ["passRule"] = "fixed_score", ["passValue"] = 60 },
            new JsonObject { ["id"] = "s2", ["name"] = "数学", ["fullScore"] = 100, ["passRule"] = "fixed_score", ["passValue"] = 60 }
        };
        return new("exam-1", new JsonObject
        {
            ["id"] = "exam-1",
            ["passPolicy"] = passPolicy,
            ["passValue"] = passValue
        }, subjects);
    }

    private static OperationalRegistration Registration(string id) => new(
        id,
        $"user-{id}",
        "exam-1",
        "approved",
        "paid",
        null,
        "2026-07-23T00:00:00.000Z",
        new JsonObject
        {
            ["id"] = id,
            ["subjectIds"] = new JsonArray("s1", "s2")
        });

    private static AdminWorkflowResult Result(
        string id,
        string registrationId,
        string subjectId,
        double score,
        bool published = true) => new(
        id,
        registrationId,
        subjectId,
        score,
        published ? "" : "待发布",
        published,
        "2026-07-23T00:00:00.000Z",
        published ? "2026-07-23T00:00:00.000Z" : null);
}
