namespace RpgRoller.Tests;

public sealed class ServiceRolemasterRollTests
{
    [Fact]
    public void RollSkill_RolemasterStandardMultiDie_ComputesTotalAndTagsDice()
    {
        using var harness = ServiceTestSupport.CreateHarness(7, 10);
        var service = harness.Service;

        service.Register("gm-init", "Password123", "GM");
        var session = ServiceTestSupport.GetValue(service.Login("gm-init", "Password123")).SessionToken;
        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(session, "Rolemaster", "rolemaster"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(session, "Hero", campaign.Id));
        var skill = ServiceTestSupport.GetValue(service.CreateSkill(session, character.Id, "Healing", "2d10+48", 0, false));

        var roll = ServiceTestSupport.GetValue(service.RollSkill(session, skill.Id, "public"));
        var logPage = ServiceTestSupport.GetValue(service.GetCampaignLogPage(session, campaign.Id, limit: 5));

        Assert.Equal(65, roll.Result);
        Assert.Equal("7+10+48=65", roll.Breakdown);
        Assert.Equal("7 + 10 | rolemaster", Assert.Single(logPage.Entries).SummaryText);
        Assert.Collection(roll.Dice, die =>
        {
            Assert.Equal(7, die.Roll);
            Assert.Equal(1, die.Sequence);
            Assert.Equal(RollDieKinds.RolemasterStandard, die.Kind);
            Assert.Equal(7, die.SignedContribution);
        }, die =>
        {
            Assert.Equal(10, die.Roll);
            Assert.Equal(2, die.Sequence);
            Assert.Equal(RollDieKinds.RolemasterStandard, die.Kind);
            Assert.Equal(10, die.SignedContribution);
        });
    }

    [Fact]
    public void RollSkill_RolemasterStandardSingleDie_ComputesTotalAndTagsDice()
    {
        using var harness = ServiceTestSupport.CreateHarness(73);
        var service = harness.Service;

        service.Register("gm-percentile", "Password123", "GM");
        var session = ServiceTestSupport.GetValue(service.Login("gm-percentile", "Password123")).SessionToken;
        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(session, "Rolemaster", "rolemaster"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(session, "Hero", campaign.Id));
        var skill = ServiceTestSupport.GetValue(service.CreateSkill(session, character.Id, "Perception", "d100-15", 0, false));

        var roll = ServiceTestSupport.GetValue(service.RollSkill(session, skill.Id, "public"));
        var logPage = ServiceTestSupport.GetValue(service.GetCampaignLogPage(session, campaign.Id, limit: 5));

        Assert.Equal(58, roll.Result);
        Assert.Equal("73-15=58", roll.Breakdown);
        Assert.Equal("73 | rolemaster", Assert.Single(logPage.Entries).SummaryText);
        Assert.Null(Assert.Single(logPage.Entries).EventBadges);

        var die = Assert.Single(roll.Dice);
        Assert.Equal(73, die.Roll);
        Assert.Equal(1, die.Sequence);
        Assert.Equal(RollDieKinds.RolemasterStandard, die.Kind);
        Assert.Equal(73, die.SignedContribution);
    }

    [Fact]
    public void RollSkill_RolemasterStandardSingleDie_AppliesSituationalModifier()
    {
        using var harness = ServiceTestSupport.CreateHarness(73);
        var service = harness.Service;

        service.Register("gm-percentile-bonus", "Password123", "GM");
        var session = ServiceTestSupport.GetValue(service.Login("gm-percentile-bonus", "Password123")).SessionToken;
        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(session, "Rolemaster", "rolemaster"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(session, "Hero", campaign.Id));
        var skill = ServiceTestSupport.GetValue(service.CreateSkill(session, character.Id, "Perception", "d100-15", 0, false));

        var roll = ServiceTestSupport.GetValue(service.RollSkill(session, skill.Id, "public", 20));

        Assert.Equal(78, roll.Result);
        Assert.Equal("73-15+20=78", roll.Breakdown);
    }

    [Fact]
    public void RollSkill_RolemasterOpenEndedHigh_RecursesAndBuildsReadableLogSummary()
    {
        using var harness = ServiceTestSupport.CreateHarness(97, 96, 45);
        var service = harness.Service;

        service.Register("gm-open-high", "Password123", "GM");
        var session = ServiceTestSupport.GetValue(service.Login("gm-open-high", "Password123")).SessionToken;
        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(session, "Rolemaster", "rolemaster"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(session, "Hero", campaign.Id));
        var skill = ServiceTestSupport.GetValue(service.CreateSkill(session, character.Id, "Awareness", "d100!+85", 0, false, null, 5));

        var roll = ServiceTestSupport.GetValue(service.RollSkill(session, skill.Id, "public"));
        var detail = ServiceTestSupport.GetValue(service.GetRollDetail(session, roll.RollId));
        var logPage = ServiceTestSupport.GetValue(service.GetCampaignLogPage(session, campaign.Id, limit: 5));

        Assert.Equal(323, roll.Result);
        Assert.Equal("97+96+45+85=323", roll.Breakdown);
        Assert.Equal("97 + 96 + 45 | open-ended high", Assert.Single(logPage.Entries).SummaryText);
        Assert.Null(Assert.Single(logPage.Entries).EventBadges);
        Assert.Equal(roll.Breakdown, detail.Breakdown);
        Assert.Collection(detail.Dice, die =>
        {
            Assert.Equal(97, die.Roll);
            Assert.Equal(1, die.Sequence);
            Assert.Equal(RollDieKinds.RolemasterOpenEndedInitial, die.Kind);
            Assert.Equal(97, die.SignedContribution);
            Assert.False(die.Added);
        }, die =>
        {
            Assert.Equal(96, die.Roll);
            Assert.Equal(2, die.Sequence);
            Assert.Equal(RollDieKinds.RolemasterOpenEndedHigh, die.Kind);
            Assert.Equal(96, die.SignedContribution);
            Assert.True(die.Added);
        }, die =>
        {
            Assert.Equal(45, die.Roll);
            Assert.Equal(3, die.Sequence);
            Assert.Equal(RollDieKinds.RolemasterOpenEndedHigh, die.Kind);
            Assert.Equal(45, die.SignedContribution);
            Assert.True(die.Added);
        });
    }

    [Fact]
    public void RollSkill_RolemasterOpenEndedLow_SubtractsRecursiveHighChain()
    {
        using var harness = ServiceTestSupport.CreateHarness(5, 97, 100, 12);
        var service = harness.Service;

        service.Register("gm-open-low", "Password123", "GM");
        var session = ServiceTestSupport.GetValue(service.Login("gm-open-low", "Password123")).SessionToken;
        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(session, "Rolemaster", "rolemaster"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(session, "Hero", campaign.Id));
        var skill = ServiceTestSupport.GetValue(service.CreateSkill(session, character.Id, "Awareness", "d100!+85", 0, false, null, 5));

        var roll = ServiceTestSupport.GetValue(service.RollSkill(session, skill.Id, "public"));
        var logPage = ServiceTestSupport.GetValue(service.GetCampaignLogPage(session, campaign.Id, limit: 5));

        Assert.Equal(-124, roll.Result);
        Assert.Equal("(05) -97 -100 -12 +85 = -124", roll.Breakdown);
        var logEntry = Assert.Single(logPage.Entries);
        Assert.Equal("(05) -97 -100 -12 | open-ended low", logEntry.SummaryText);
        var lowEventBadges = Assert.IsType<string[]>(logEntry.EventBadges);
        Assert.Collection(lowEventBadges, badge => Assert.Equal("rf", badge), badge => Assert.Equal("r100", badge));
        Assert.Collection(roll.Dice, die =>
        {
            Assert.Equal(5, die.Roll);
            Assert.Equal(1, die.Sequence);
            Assert.Equal(RollDieKinds.RolemasterOpenEndedInitial, die.Kind);
            Assert.Null(die.SignedContribution);
        }, die =>
        {
            Assert.Equal(97, die.Roll);
            Assert.Equal(2, die.Sequence);
            Assert.Equal(RollDieKinds.RolemasterOpenEndedLowSubtract, die.Kind);
            Assert.Equal(-97, die.SignedContribution);
        }, die =>
        {
            Assert.Equal(100, die.Roll);
            Assert.Equal(3, die.Sequence);
            Assert.Equal(RollDieKinds.RolemasterOpenEndedLowSubtract, die.Kind);
            Assert.Equal(-100, die.SignedContribution);
        }, die =>
        {
            Assert.Equal(12, die.Roll);
            Assert.Equal(4, die.Sequence);
            Assert.Equal(RollDieKinds.RolemasterOpenEndedLowSubtract, die.Kind);
            Assert.Equal(-12, die.SignedContribution);
        });
    }

    [Fact]
    public void RollSkill_RolemasterSixtySix_AddsRareBadgeToLogSummary()
    {
        using var harness = ServiceTestSupport.CreateHarness(66);
        var service = harness.Service;

        service.Register("gm-sixty-six", "Password123", "GM");
        var session = ServiceTestSupport.GetValue(service.Login("gm-sixty-six", "Password123")).SessionToken;
        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(session, "Rolemaster", "rolemaster"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(session, "Hero", campaign.Id));
        var skill = ServiceTestSupport.GetValue(service.CreateSkill(session, character.Id, "Perception", "d100-15", 0, false));

        _ = ServiceTestSupport.GetValue(service.RollSkill(session, skill.Id, "public"));
        var logEntry = Assert.Single(ServiceTestSupport.GetValue(service.GetCampaignLogPage(session, campaign.Id, limit: 5)).Entries);

        var badge = Assert.Single(Assert.IsType<string[]>(logEntry.EventBadges));
        Assert.Equal("r66", badge);
        Assert.Equal("66 | rolemaster", logEntry.SummaryText);
    }

    [Fact]
    public void RollSkill_RolemasterAutoRetryPlusFive_UsesRetryResultAndMarksAttempts()
    {
        using var harness = ServiceTestSupport.CreateHarness(66, 42);
        var service = harness.Service;

        service.Register("gm-retry-five", "Password123", "GM");
        var session = ServiceTestSupport.GetValue(service.Login("gm-retry-five", "Password123")).SessionToken;
        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(session, "Rolemaster Retry", "rolemaster"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(session, "Hero", campaign.Id));
        var skill = ServiceTestSupport.GetValue(service.CreateSkill(session, character.Id, "Awareness", "d100!+10", 0, false, null, 5, true));

        var roll = ServiceTestSupport.GetValue(service.RollSkill(session, skill.Id, "public"));
        var detail = ServiceTestSupport.GetValue(service.GetRollDetail(session, roll.RollId));
        var logEntry = Assert.Single(ServiceTestSupport.GetValue(service.GetCampaignLogPage(session, campaign.Id, limit: 5)).Entries);

        Assert.Equal(57, roll.Result);
        Assert.Equal("66+10=76; retry(+5): 42+10=52; final=57", roll.Breakdown);
        Assert.Equal("66 | open-ended | retry +5", logEntry.SummaryText);
        Assert.Equal(["r66", "rs5"], Assert.IsType<string[]>(logEntry.EventBadges));
        Assert.Equal(roll.Breakdown, detail.Breakdown);
        Assert.Collection(detail.Dice, die =>
        {
            Assert.Equal(66, die.Roll);
            Assert.Equal(1, die.Sequence);
            Assert.Equal(1, die.Attempt);
            Assert.Equal(RollDieKinds.RolemasterOpenEndedInitial, die.Kind);
            Assert.Equal(66, die.SignedContribution);
        }, die =>
        {
            Assert.Equal(42, die.Roll);
            Assert.Equal(1, die.Sequence);
            Assert.Equal(2, die.Attempt);
            Assert.Equal(RollDieKinds.RolemasterOpenEndedInitial, die.Kind);
            Assert.Equal(42, die.SignedContribution);
        });
    }

    [Fact]
    public void RollSkill_RolemasterAutoRetry_UsesSituationalModifierInBothAttempts()
    {
        using var harness = ServiceTestSupport.CreateHarness(8, 42);
        var service = harness.Service;

        service.Register("gm-retry-situational", "Password123", "GM");
        var session = ServiceTestSupport.GetValue(service.Login("gm-retry-situational", "Password123")).SessionToken;
        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(session, "Rolemaster Retry", "rolemaster"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(session, "Hero", campaign.Id));
        var skill = ServiceTestSupport.GetValue(service.CreateSkill(session, character.Id, "Observation", "d100!+50", 0, false, null, 5, true));

        var roll = ServiceTestSupport.GetValue(service.RollSkill(session, skill.Id, "public", 20));
        var logEntry = Assert.Single(ServiceTestSupport.GetValue(service.GetCampaignLogPage(session, campaign.Id, limit: 5)).Entries);

        Assert.Equal(117, roll.Result);
        Assert.Equal("8+50+20=78; retry(+5): 42+50+20=112; final=117", roll.Breakdown);
        Assert.Equal("8 | open-ended | retry +5", logEntry.SummaryText);
        Assert.Equal(["rs5"], Assert.IsType<string[]>(logEntry.EventBadges));
        Assert.All(roll.Dice, die => Assert.True(die.Attempt is 1 or 2));
    }

    [Fact]
    public void RollSkill_RolemasterAutoRetryPlusTen_UsesRetryResultAndMarksAttempts()
    {
        using var harness = ServiceTestSupport.CreateHarness(90, 32);
        var service = harness.Service;

        service.Register("gm-retry-ten", "Password123", "GM");
        var session = ServiceTestSupport.GetValue(service.Login("gm-retry-ten", "Password123")).SessionToken;
        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(session, "Rolemaster Retry", "rolemaster"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(session, "Hero", campaign.Id));
        var skill = ServiceTestSupport.GetValue(service.CreateSkill(session, character.Id, "Awareness", "d100!+1", 0, false, null, 5, true));

        var roll = ServiceTestSupport.GetValue(service.RollSkill(session, skill.Id, "public"));
        var logEntry = Assert.Single(ServiceTestSupport.GetValue(service.GetCampaignLogPage(session, campaign.Id, limit: 5)).Entries);

        Assert.Equal(43, roll.Result);
        Assert.Equal("90+1=91; retry(+10): 32+1=33; final=43", roll.Breakdown);
        Assert.Equal("90 | open-ended | retry +10", logEntry.SummaryText);
        Assert.Equal(["rs10"], Assert.IsType<string[]>(logEntry.EventBadges));
        Assert.All(roll.Dice, die => Assert.True(die.Attempt is 1 or 2));
    }

    [Fact]
    public void RollSkill_RolemasterAutoRetryDisabled_KeepsOriginalResult()
    {
        using var harness = ServiceTestSupport.CreateHarness(65);
        var service = harness.Service;

        service.Register("gm-retry-off", "Password123", "GM");
        var session = ServiceTestSupport.GetValue(service.Login("gm-retry-off", "Password123")).SessionToken;
        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(session, "Rolemaster Retry", "rolemaster"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(session, "Hero", campaign.Id));
        var skill = ServiceTestSupport.GetValue(service.CreateSkill(session, character.Id, "Awareness", "d100!+10", 0, false, null, 5));

        var roll = ServiceTestSupport.GetValue(service.RollSkill(session, skill.Id, "public"));
        var logEntry = Assert.Single(ServiceTestSupport.GetValue(service.GetCampaignLogPage(session, campaign.Id, limit: 5)).Entries);

        Assert.Equal(75, roll.Result);
        Assert.Equal("65+10=75", roll.Breakdown);
        Assert.Equal("65 | open-ended", logEntry.SummaryText);
        Assert.Null(logEntry.EventBadges);
        Assert.All(roll.Dice, die => Assert.Null(die.Attempt));
    }

    [Fact]
    public void RollSkill_SituationalModifier_IsRejectedForNonRolemasterCampaigns()
    {
        using var harness = ServiceTestSupport.CreateHarness(12);
        var service = harness.Service;

        service.Register("gm-non-rolemaster-modifier", "Password123", "GM");
        var session = ServiceTestSupport.GetValue(service.Login("gm-non-rolemaster-modifier", "Password123")).SessionToken;
        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(session, "DnD", "dnd5e"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(session, "Hero", campaign.Id));
        var skill = ServiceTestSupport.GetValue(service.CreateSkill(session, character.Id, "Attack", "1d20+5", 0, false));

        var roll = service.RollSkill(session, skill.Id, "public", 20);

        Assert.False(roll.Succeeded);
        Assert.Equal("invalid_situational_modifier", roll.Error!.Code);
    }
}