namespace RpgRoller.Tests;

public sealed class ServiceAdminAndCampaignDeletionTests
{
    [Fact]
    public void AdminRoleManagement_RequiresAdminAndProtectsSelf()
    {
        using var harness = ServiceTestSupport.CreateHarness();
        var service = harness.Service;

        var bootstrapAdmin = ServiceTestSupport.GetValue(service.Register("admin", "Password123", "Admin"));
        _ = ServiceTestSupport.GetValue(service.Register("member", "Password123", "Member"));

        Assert.Contains(bootstrapAdmin.Roles, role => string.Equals(role, UserRoles.Admin, StringComparison.OrdinalIgnoreCase));

        var adminSession = ServiceTestSupport.GetValue(service.Login("admin", "Password123")).SessionToken;
        var memberSession = ServiceTestSupport.GetValue(service.Login("member", "Password123")).SessionToken;

        var usernames = ServiceTestSupport.GetValue(service.GetUsernames(memberSession));
        Assert.Equal(["admin", "member"], usernames);

        var forbiddenList = service.GetUsers(memberSession);
        Assert.False(forbiddenList.Succeeded);

        var users = ServiceTestSupport.GetValue(service.GetUsers(adminSession));
        var memberUser = users.Single(user => string.Equals(user.Username, "member", StringComparison.OrdinalIgnoreCase));
        Assert.Empty(memberUser.Roles);

        var promoted = ServiceTestSupport.GetValue(service.UpdateUserRoles(adminSession, memberUser.Id, [UserRoles.Admin]));
        Assert.Contains(promoted.Roles, role => string.Equals(role, UserRoles.Admin, StringComparison.OrdinalIgnoreCase));

        var invalidRole = service.UpdateUserRoles(adminSession, memberUser.Id, [UserRoles.Admin, "gm"]);
        Assert.False(invalidRole.Succeeded);
        Assert.Equal("invalid_role", invalidRole.Error?.Code);

        var selfDemote = service.UpdateUserRoles(adminSession, bootstrapAdmin.Id, Array.Empty<string>());
        Assert.False(selfDemote.Succeeded);

        var selfDelete = service.DeleteUser(adminSession, bootstrapAdmin.Id);
        Assert.False(selfDelete.Succeeded);

        var deletedMember = ServiceTestSupport.GetValue(service.DeleteUser(adminSession, memberUser.Id));
        Assert.True(deletedMember);

        var remainingUsers = ServiceTestSupport.GetValue(service.GetUsers(adminSession));
        Assert.Single(remainingUsers);
        Assert.Equal(bootstrapAdmin.Id, remainingUsers[0].Id);
    }

    [Fact]
    public void CampaignDeletion_ByOwnerOrAdmin_UnlinksCharactersAndClearsLog()
    {
        using var harness = ServiceTestSupport.CreateHarness(4, 5, 6);
        var service = harness.Service;

        _ = ServiceTestSupport.GetValue(service.Register("admin", "Password123", "Admin"));
        _ = ServiceTestSupport.GetValue(service.Register("gm", "Password123", "GM"));
        _ = ServiceTestSupport.GetValue(service.Register("player", "Password123", "Player"));

        var adminSession = ServiceTestSupport.GetValue(service.Login("admin", "Password123")).SessionToken;
        var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken;
        var playerSession = ServiceTestSupport.GetValue(service.Login("player", "Password123")).SessionToken;

        var ownerDeletedCampaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Owner Delete", "d6"));
        var ownerDeleteResult = ServiceTestSupport.GetValue(service.DeleteCampaign(gmSession, ownerDeletedCampaign.Id));
        Assert.True(ownerDeleteResult);
        Assert.False(service.GetCampaign(gmSession, ownerDeletedCampaign.Id).Succeeded);

        var adminDeletedCampaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Admin Delete", "d6"));
        var playerCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(playerSession, "Scout", adminDeletedCampaign.Id));
        var playerSkill = ServiceTestSupport.GetValue(service.CreateSkill(playerSession, playerCharacter.Id, "Stealth", "2D+1", 1, true));
        _ = ServiceTestSupport.GetValue(service.RollSkill(playerSession, playerSkill.Id, "public"));

        var forbiddenDelete = service.DeleteCampaign(playerSession, adminDeletedCampaign.Id);
        Assert.False(forbiddenDelete.Succeeded);

        var adminDelete = ServiceTestSupport.GetValue(service.DeleteCampaign(adminSession, adminDeletedCampaign.Id));
        Assert.True(adminDelete);
        Assert.False(service.GetCampaign(gmSession, adminDeletedCampaign.Id).Succeeded);

        var playerCharacters = ServiceTestSupport.GetValue(service.GetOwnCharacters(playerSession));
        Assert.Single(playerCharacters);
        Assert.Null(playerCharacters[0].CampaignId);

        using var db = harness.CreateDbContext();
        Assert.Empty(db.RollLogEntries.Where(entry => entry.CampaignId == adminDeletedCampaign.Id));
    }

    [Fact]
    public void DeleteUser_DeletesOwnedCampaigns_AndUnlinksCharactersInThoseCampaigns()
    {
        using var harness = ServiceTestSupport.CreateHarness(4, 5, 6);
        var service = harness.Service;

        _ = ServiceTestSupport.GetValue(service.Register("admin", "Password123", "Admin"));
        _ = ServiceTestSupport.GetValue(service.Register("gm", "Password123", "GM"));
        _ = ServiceTestSupport.GetValue(service.Register("player", "Password123", "Player"));

        var adminSession = ServiceTestSupport.GetValue(service.Login("admin", "Password123")).SessionToken;
        var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken;
        var playerSession = ServiceTestSupport.GetValue(service.Login("player", "Password123")).SessionToken;

        var gmOwnedCampaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "GM Campaign", "d6"));
        var adminOwnedCampaign = ServiceTestSupport.GetValue(service.CreateCampaign(adminSession, "Admin Campaign", "d6"));

        var gmCharacterInOwnedCampaign = ServiceTestSupport.GetValue(service.CreateCharacter(gmSession, "GM Hero", gmOwnedCampaign.Id));
        var playerCharacterInGmCampaign = ServiceTestSupport.GetValue(service.CreateCharacter(playerSession, "Player Hero", gmOwnedCampaign.Id));
        var gmCharacterOutsideOwnedCampaign = ServiceTestSupport.GetValue(service.CreateCharacter(gmSession, "Visitor", adminOwnedCampaign.Id));

        var playerSkill = ServiceTestSupport.GetValue(service.CreateSkill(playerSession, playerCharacterInGmCampaign.Id, "Scout", "2D+1", 1, true));
        _ = ServiceTestSupport.GetValue(service.RollSkill(playerSession, playerSkill.Id, "public"));

        var gmUsers = ServiceTestSupport.GetValue(service.GetUsers(adminSession));
        var gmUser = gmUsers.Single(user => string.Equals(user.Username, "gm", StringComparison.OrdinalIgnoreCase));

        var deleteResult = ServiceTestSupport.GetValue(service.DeleteUser(adminSession, gmUser.Id));
        Assert.True(deleteResult);

        Assert.Null(service.GetUserBySession(gmSession));
        Assert.False(service.GetMe(gmSession).Succeeded);
        Assert.False(service.GetUsernames(gmSession).Succeeded);
        Assert.False(service.GetCampaign(adminSession, gmOwnedCampaign.Id).Succeeded);

        var playerCharacters = ServiceTestSupport.GetValue(service.GetOwnCharacters(playerSession));
        var unlinkedPlayerCharacter = playerCharacters.Single(character => character.Id == playerCharacterInGmCampaign.Id);
        Assert.Null(unlinkedPlayerCharacter.CampaignId);

        using var db = harness.CreateDbContext();
        Assert.DoesNotContain(db.Campaigns, campaign => campaign.Id == gmOwnedCampaign.Id);
        Assert.Empty(db.RollLogEntries.Where(entry => entry.CampaignId == gmOwnedCampaign.Id));

        var preservedGmCharacter = db.Characters.Single(character => character.Id == gmCharacterInOwnedCampaign.Id);
        Assert.Null(preservedGmCharacter.CampaignId);

        var preservedPlayerCharacter = db.Characters.Single(character => character.Id == playerCharacterInGmCampaign.Id);
        Assert.Null(preservedPlayerCharacter.CampaignId);

        Assert.DoesNotContain(db.Characters, character => character.Id == gmCharacterOutsideOwnedCampaign.Id);
    }
}