namespace RpgRoller.Tests;

public sealed class RollVisibilityApiTests(WebApplicationFactory<Program> factory) : ApiTestBase(factory)
{
    [Fact]
    public async Task RollVisibilityAndAuthorization_AreEnforced()
    {
        using var factory = CreateFactory(4, 3, 5, 2, 6);
        using var gmClient = factory.CreateClient(new() { AllowAutoRedirect = false });
        using var playerClient = factory.CreateClient(new() { AllowAutoRedirect = false });
        using var observerClient = factory.CreateClient(new() { AllowAutoRedirect = false });
        using var outsiderClient = factory.CreateClient(new() { AllowAutoRedirect = false });

        await RegisterAsync(gmClient, "gm", "Password123", "GM");
        await LoginAsync(gmClient, "gm", "Password123");
        var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Main", "d6"));

        await RegisterAsync(playerClient, "player", "Password123", "Player");
        await LoginAsync(playerClient, "player", "Password123");
        var playerCharacter = await PostAsync<CreateCharacterRequest, CharacterSummary>(playerClient, "/api/characters", new("Rogue", campaign.Id));

        var skill = await PostAsync<CreateSkillRequest, SkillSummary>(playerClient, $"/api/characters/{playerCharacter.Id}/skills", new("Stealth", "2D+1", 1, true));
        Assert.Equal(1, skill.WildDice);
        Assert.True(skill.AllowFumble);

        await RegisterAsync(observerClient, "observer", "Password123", "Observer");
        await LoginAsync(observerClient, "observer", "Password123");
        await PostAsync<CreateCharacterRequest, CharacterSummary>(observerClient, "/api/characters", new("Watcher", campaign.Id));

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

        Assert.Equal("private", privateRoll.Visibility);
        Assert.Equal("public", publicRoll.Visibility);

        var gmLog = await GetAsync<IReadOnlyList<CampaignLogEntry>>(gmClient, $"/api/campaigns/{campaign.Id}/log");
        Assert.Equal(2, gmLog.Count);
        Assert.All(gmLog, entry => Assert.NotEmpty(entry.Dice));

        var playerLog = await GetAsync<IReadOnlyList<CampaignLogEntry>>(playerClient, $"/api/campaigns/{campaign.Id}/log");
        Assert.Equal(2, playerLog.Count);
        Assert.All(playerLog, entry => Assert.NotEmpty(entry.Dice));

        var observerLog = await GetAsync<IReadOnlyList<CampaignLogEntry>>(observerClient, $"/api/campaigns/{campaign.Id}/log");
        Assert.Single(observerLog);
        Assert.Equal("public", observerLog[0].Visibility);
        Assert.NotEmpty(observerLog[0].Dice);

        var observerLogPage = await GetAsync<CampaignLogPage>(observerClient, $"/api/campaigns/{campaign.Id}/log/page");
        Assert.Single(observerLogPage.Entries);
        Assert.Equal(publicRoll.RollId, observerLogPage.Entries[0].RollId);
        Assert.Equal(publicRoll.RollId, observerLogPage.Cursor);
        Assert.Equal("Public", observerLogPage.Entries[0].VisibilityLabel);

        var observerPublicDetail = await GetAsync<CampaignRollDetail>(observerClient, $"/api/rolls/{publicRoll.RollId}");
        Assert.Equal(publicRoll.RollId, observerPublicDetail.RollId);
        Assert.NotEmpty(observerPublicDetail.Dice);

        var observerPrivateDetail = await observerClient.GetAsync($"/api/rolls/{privateRoll.RollId}");
        Assert.Equal(HttpStatusCode.BadRequest, observerPrivateDetail.StatusCode);

        await RegisterAsync(outsiderClient, "outsider", "Password123", "Outsider");
        await LoginAsync(outsiderClient, "outsider", "Password123");

        var forbiddenCampaign = await outsiderClient.GetAsync($"/api/campaigns/{campaign.Id}");
        Assert.Equal(HttpStatusCode.BadRequest, forbiddenCampaign.StatusCode);

        var outsiderPublicDetail = await outsiderClient.GetAsync($"/api/rolls/{publicRoll.RollId}");
        Assert.Equal(HttpStatusCode.BadRequest, outsiderPublicDetail.StatusCode);

        var invalidVisibility = await playerClient.PostAsJsonAsync($"/api/skills/{skill.Id}/roll", new RollSkillRequest("hidden"));
        Assert.Equal(HttpStatusCode.BadRequest, invalidVisibility.StatusCode);

        var customRoll = await PostAsync<CustomRollRequest, RollResult>(playerClient, $"/api/characters/{playerCharacter.Id}/custom-rolls", new("1D+2", "public"));
        Assert.Equal(Guid.Empty, customRoll.SkillId);

        var customRollLogPage = await GetAsync<CampaignLogPage>(observerClient, $"/api/campaigns/{campaign.Id}/log/page");
        Assert.Equal(2, customRollLogPage.Entries.Length);
        Assert.Equal("Custom roll", customRollLogPage.Entries[1].SkillName);

        var invalidCustomRollResponse = await playerClient.PostAsJsonAsync($"/api/characters/{playerCharacter.Id}/custom-rolls", new CustomRollRequest("bad", "public"));
        Assert.Equal(HttpStatusCode.BadRequest, invalidCustomRollResponse.StatusCode);
        var invalidCustomRoll = await invalidCustomRollResponse.Content.ReadFromJsonAsync<ApiError>();
        Assert.NotNull(invalidCustomRoll);
        Assert.Equal("invalid_expression", invalidCustomRoll.Code);

        using var anonymousClient = factory.CreateClient(new() { AllowAutoRedirect = false });
        var unauthorizedCampaignCreate = await anonymousClient.PostAsJsonAsync("/api/campaigns", new CreateCampaignRequest("Nope", "d6"));
        Assert.Equal(HttpStatusCode.Unauthorized, unauthorizedCampaignCreate.StatusCode);

        var invalidSessionRequest = new HttpRequestMessage(HttpMethod.Get, "/api/campaigns");
        invalidSessionRequest.Headers.Add("Cookie", "rpgroller_session=invalid-token");
        var unauthorizedWithInvalidSession = await anonymousClient.SendAsync(invalidSessionRequest);
        Assert.Equal(HttpStatusCode.Unauthorized, unauthorizedWithInvalidSession.StatusCode);
    }
}