namespace RpgRoller.Tests;

public sealed class RolemasterApiTests(WebApplicationFactory<Program> factory) : ApiTestBase(factory)
{
    [Fact]
    public async Task RolemasterRollEndpoints_ExecuteGenericRolemasterExpressions()
    {
        using var factory = CreateFactory(8, 6, 74);
        using var client = factory.CreateClient(new() { AllowAutoRedirect = false });

        await RegisterAsync(client, "rolemaster-api", "Password123", "Rolemaster Api");
        await LoginAsync(client, "rolemaster-api", "Password123");

        var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(client, "/api/campaigns", new("Rolemaster", "rolemaster"));
        var character = await PostAsync<CreateCharacterRequest, CharacterSummary>(client, "/api/characters", new("Hero", campaign.Id));
        var initiative = await PostAsync<CreateSkillRequest, SkillSummary>(client, $"/api/characters/{character.Id}/skills", new("Healing", "2d10+48", 0, false));
        var perception = await PostAsync<CreateSkillRequest, SkillSummary>(client, $"/api/characters/{character.Id}/skills", new("Perception", "d100-15", 0, false));

        var initiativeRoll = await PostAsync<RollSkillRequest, RollResult>(client, $"/api/skills/{initiative.Id}/roll", new("public"));
        var percentileRoll = await PostAsync<RollSkillRequest, RollResult>(client, $"/api/skills/{perception.Id}/roll", new("public"));
        var logPage = await GetAsync<CampaignLogPage>(client, $"/api/campaigns/{campaign.Id}/log/page?limit=10");

        Assert.Equal(62, initiativeRoll.Result);
        Assert.Equal("8+6+48=62", initiativeRoll.Breakdown);
        Assert.All(initiativeRoll.Dice, die => Assert.Equal(RollDieKinds.RolemasterStandard, die.Kind));

        Assert.Equal(59, percentileRoll.Result);
        Assert.Equal("74-15=59", percentileRoll.Breakdown);
        Assert.Equal(RollDieKinds.RolemasterStandard, Assert.Single(percentileRoll.Dice).Kind);

        Assert.Equal(2, logPage.Entries.Length);
        Assert.Equal("8 + 6 | rolemaster", logPage.Entries[0].SummaryText);
        Assert.Null(logPage.Entries[0].EventBadges);
        Assert.Equal("74 | rolemaster", logPage.Entries[1].SummaryText);
        Assert.Null(logPage.Entries[1].EventBadges);
    }

    [Fact]
    public async Task RolemasterOpenEndedRolls_AppearInLogPageAndDetail()
    {
        using var factory = CreateFactory(5, 97, 100, 12);
        using var client = factory.CreateClient(new() { AllowAutoRedirect = false });

        await RegisterAsync(client, "rolemaster-open-api", "Password123", "Rolemaster Open Api");
        await LoginAsync(client, "rolemaster-open-api", "Password123");

        var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(client, "/api/campaigns", new("Rolemaster Open", "rolemaster"));
        var character = await PostAsync<CreateCharacterRequest, CharacterSummary>(client, "/api/characters", new("Hero", campaign.Id));
        var skill = await PostAsync<CreateSkillRequest, SkillSummary>(client, $"/api/characters/{character.Id}/skills", new("Awareness", "d100!+85", 0, false, null, 5));

        var roll = await PostAsync<RollSkillRequest, RollResult>(client, $"/api/skills/{skill.Id}/roll", new("public"));
        var logPage = await GetAsync<CampaignLogPage>(client, $"/api/campaigns/{campaign.Id}/log/page?limit=5");
        var detail = await GetAsync<CampaignRollDetail>(client, $"/api/rolls/{roll.RollId}");

        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 eventBadges = Assert.IsType<string[]>(logEntry.EventBadges);
        Assert.Collection(eventBadges, badge => Assert.Equal("rf", badge), badge => Assert.Equal("r100", badge));
        Assert.Equal(roll.Breakdown, detail.Breakdown);
        Assert.Collection(detail.Dice, die =>
        {
            Assert.Equal(RollDieKinds.RolemasterOpenEndedInitial, die.Kind);
            Assert.Equal(1, die.Sequence);
            Assert.Null(die.SignedContribution);
        }, die =>
        {
            Assert.Equal(RollDieKinds.RolemasterOpenEndedLowSubtract, die.Kind);
            Assert.Equal(2, die.Sequence);
            Assert.Equal(-97, die.SignedContribution);
        }, die =>
        {
            Assert.Equal(RollDieKinds.RolemasterOpenEndedLowSubtract, die.Kind);
            Assert.Equal(3, die.Sequence);
            Assert.Equal(-100, die.SignedContribution);
        }, die =>
        {
            Assert.Equal(RollDieKinds.RolemasterOpenEndedLowSubtract, die.Kind);
            Assert.Equal(4, die.Sequence);
            Assert.Equal(-12, die.SignedContribution);
        });
    }

    [Fact]
    public async Task RolemasterAutoRetryRolls_AppearInLogPageAndDetail()
    {
        using var factory = CreateFactory(66, 42, 90, 32, 65);
        using var client = factory.CreateClient(new() { AllowAutoRedirect = false });

        await RegisterAsync(client, "rolemaster-retry-api", "Password123", "Rolemaster Retry Api");
        await LoginAsync(client, "rolemaster-retry-api", "Password123");

        var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(client, "/api/campaigns", new("Rolemaster Retry", "rolemaster"));
        var character = await PostAsync<CreateCharacterRequest, CharacterSummary>(client, "/api/characters", new("Hero", campaign.Id));
        var retryFiveSkill = await PostAsync<CreateSkillRequest, SkillSummary>(client, $"/api/characters/{character.Id}/skills", new("Awareness +5", "d100!+10", 0, false, null, 5, true));
        var retryTenSkill = await PostAsync<CreateSkillRequest, SkillSummary>(client, $"/api/characters/{character.Id}/skills", new("Awareness +10", "d100!+1", 0, false, null, 5, true));
        var disabledSkill = await PostAsync<CreateSkillRequest, SkillSummary>(client, $"/api/characters/{character.Id}/skills", new("Awareness Off", "d100!+10", 0, false, null, 5));

        var retryFiveRoll = await PostAsync<RollSkillRequest, RollResult>(client, $"/api/skills/{retryFiveSkill.Id}/roll", new("public"));
        var retryTenRoll = await PostAsync<RollSkillRequest, RollResult>(client, $"/api/skills/{retryTenSkill.Id}/roll", new("public"));
        var disabledRoll = await PostAsync<RollSkillRequest, RollResult>(client, $"/api/skills/{disabledSkill.Id}/roll", new("public"));
        var logPage = await GetAsync<CampaignLogPage>(client, $"/api/campaigns/{campaign.Id}/log/page?limit=10");
        var detail = await GetAsync<CampaignRollDetail>(client, $"/api/rolls/{retryFiveRoll.RollId}");

        Assert.Equal(57, retryFiveRoll.Result);
        Assert.Equal("66+10=76; retry(+5): 42+10=52; final=57", retryFiveRoll.Breakdown);
        Assert.Collection(retryFiveRoll.Dice, die =>
        {
            Assert.Equal(1, die.Attempt);
            Assert.Equal(1, die.Sequence);
        }, die =>
        {
            Assert.Equal(2, die.Attempt);
            Assert.Equal(1, die.Sequence);
        });

        Assert.Equal(43, retryTenRoll.Result);
        Assert.Equal("90+1=91; retry(+10): 32+1=33; final=43", retryTenRoll.Breakdown);

        Assert.Equal(75, disabledRoll.Result);
        Assert.Equal("65+10=75", disabledRoll.Breakdown);
        Assert.All(disabledRoll.Dice, die => Assert.Null(die.Attempt));

        Assert.Equal(3, logPage.Entries.Length);
        Assert.Equal("66 | open-ended | retry +5", logPage.Entries[0].SummaryText);
        Assert.Equal(["r66", "rs5"], Assert.IsType<string[]>(logPage.Entries[0].EventBadges));
        Assert.Equal("90 | open-ended | retry +10", logPage.Entries[1].SummaryText);
        Assert.Equal(["rs10"], Assert.IsType<string[]>(logPage.Entries[1].EventBadges));
        Assert.Equal("65 | open-ended", logPage.Entries[2].SummaryText);
        Assert.Null(logPage.Entries[2].EventBadges);

        Assert.Equal(retryFiveRoll.Breakdown, detail.Breakdown);
        Assert.Collection(detail.Dice, die =>
        {
            Assert.Equal(1, die.Attempt);
            Assert.Equal(RollDieKinds.RolemasterOpenEndedInitial, die.Kind);
        }, die =>
        {
            Assert.Equal(2, die.Attempt);
            Assert.Equal(RollDieKinds.RolemasterOpenEndedInitial, die.Kind);
        });
    }

    [Fact]
    public async Task RolemasterSkillRoll_AcceptsSituationalModifier_AndAppliesItToRetryMath()
    {
        using var factory = CreateFactory(8, 42);
        using var client = factory.CreateClient(new() { AllowAutoRedirect = false });

        await RegisterAsync(client, "rolemaster-situational-api", "Password123", "Rolemaster Situational Api");
        await LoginAsync(client, "rolemaster-situational-api", "Password123");

        var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(client, "/api/campaigns", new("Rolemaster Situational", "rolemaster"));
        var character = await PostAsync<CreateCharacterRequest, CharacterSummary>(client, "/api/characters", new("Hero", campaign.Id));
        var skill = await PostAsync<CreateSkillRequest, SkillSummary>(client, $"/api/characters/{character.Id}/skills", new("Observation", "d100!+50", 0, false, null, 5, true));

        var roll = await PostAsync<RollSkillRequest, RollResult>(client, $"/api/skills/{skill.Id}/roll", new("public", 20));

        Assert.Equal(117, roll.Result);
        Assert.Equal("8+50+20=78; retry(+5): 42+50+20=112; final=117", roll.Breakdown);
        Assert.Collection(roll.Dice, die => Assert.Equal(1, die.Attempt), die => Assert.Equal(2, die.Attempt));
    }

    [Fact]
    public async Task SkillRoll_RejectsSituationalModifier_ForNonRolemasterCampaigns()
    {
        using var factory = CreateFactory(12);
        using var client = factory.CreateClient(new() { AllowAutoRedirect = false });

        await RegisterAsync(client, "non-rolemaster-situational-api", "Password123", "Non Rolemaster Situational Api");
        await LoginAsync(client, "non-rolemaster-situational-api", "Password123");

        var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(client, "/api/campaigns", new("Dnd Situational", "dnd5e"));
        var character = await PostAsync<CreateCharacterRequest, CharacterSummary>(client, "/api/characters", new("Hero", campaign.Id));
        var skill = await PostAsync<CreateSkillRequest, SkillSummary>(client, $"/api/characters/{character.Id}/skills", new("Attack", "1d20+5", 0, false));

        var response = await client.PostAsJsonAsync($"/api/skills/{skill.Id}/roll", new RollSkillRequest("public", 20));
        var error = await response.Content.ReadFromJsonAsync<ApiError>();

        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
        Assert.NotNull(error);
        Assert.Equal("invalid_situational_modifier", error.Code);
    }
}