namespace RpgRoller.Tests;

public sealed class ServiceSkillGroupAndOwnershipTests
{
    [Fact]
    public void SkillGroups_CanBeManagedAndAssignedWithinCharacterScope()
    {
        using var harness = ServiceTestSupport.CreateHarness(4, 3, 2);
        var service = harness.Service;

        service.Register("gm", "Password123", "GM");
        service.Register("owner", "Password123", "Owner");
        service.Register("other", "Password123", "Other");

        var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken;
        var ownerSession = ServiceTestSupport.GetValue(service.Login("owner", "Password123")).SessionToken;
        var otherSession = ServiceTestSupport.GetValue(service.Login("other", "Password123")).SessionToken;

        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Main", "d6"));
        var ownerCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Owner Char", campaign.Id));
        var otherCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(otherSession, "Other Char", campaign.Id));

        var ownerGroup = ServiceTestSupport.GetValue(service.CreateSkillGroup(ownerSession, ownerCharacter.Id, "Combat", "2D+1", 1, true));
        Assert.False(service.CreateSkillGroup(ownerSession, otherCharacter.Id, "Not Allowed", "2D+1", 1, true).Succeeded);

        Assert.False(service.UpdateSkillGroup(otherSession, ownerGroup.Id, "Renamed by Other", "2D+1", 1, true).Succeeded);
        var renamedGroup = ServiceTestSupport.GetValue(service.UpdateSkillGroup(gmSession, ownerGroup.Id, "Battle", "3D+2", 2, false));
        Assert.Equal("Battle", renamedGroup.Name);
        Assert.Equal("3D+2", renamedGroup.DiceRollDefinition);
        Assert.Equal(2, renamedGroup.WildDice);
        Assert.False(renamedGroup.AllowFumble);

        var skill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, ownerCharacter.Id, "Strike", "2D+1", 1, true, renamedGroup.Id));
        Assert.Equal(renamedGroup.Id, skill.SkillGroupId);

        var otherGroup = ServiceTestSupport.GetValue(service.CreateSkillGroup(otherSession, otherCharacter.Id, "Other Group", "2D+1", 1, true));
        Assert.False(service.UpdateSkill(ownerSession, skill.Id, "Strike", "2D+1", 1, true, otherGroup.Id).Succeeded);

        var ungroupedSkill = ServiceTestSupport.GetValue(service.UpdateSkill(ownerSession, skill.Id, "Strike", "2D+1", 1, true));
        Assert.Null(ungroupedSkill.SkillGroupId);

        var regroupedSkill = ServiceTestSupport.GetValue(service.UpdateSkill(ownerSession, skill.Id, "Strike", "2D+1", 1, true, renamedGroup.Id));
        Assert.Equal(renamedGroup.Id, regroupedSkill.SkillGroupId);

        var deletedGroup = ServiceTestSupport.GetValue(service.DeleteSkillGroup(ownerSession, renamedGroup.Id));
        Assert.True(deletedGroup);

        var ownerSheetAfterGroupDelete = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, ownerCharacter.Id));
        var otherSheetAfterGroupDelete = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, otherCharacter.Id));
        Assert.DoesNotContain(ownerSheetAfterGroupDelete.SkillGroups, group => group.Id == renamedGroup.Id);
        Assert.Contains(otherSheetAfterGroupDelete.SkillGroups, group => group.Id == otherGroup.Id);
        Assert.Null(ownerSheetAfterGroupDelete.Skills.Single(s => s.Id == regroupedSkill.Id).SkillGroupId);

        var deletedSkill = ServiceTestSupport.GetValue(service.DeleteSkill(ownerSession, regroupedSkill.Id));
        Assert.True(deletedSkill);

        var ownerView = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, ownerCharacter.Id));
        var otherView = ServiceTestSupport.GetValue(service.GetCharacterSheet(ownerSession, otherCharacter.Id));
        Assert.DoesNotContain(ownerView.SkillGroups, group => group.Id == renamedGroup.Id);
        Assert.Contains(otherView.SkillGroups, group => group.Id == otherGroup.Id);
        Assert.DoesNotContain(ownerView.Skills, skillSummary => skillSummary.Id == regroupedSkill.Id);
    }

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

        service.Register("gm", "Password123", "GM");
        service.Register("owner", "Password123", "Owner");
        service.Register("receiver", "Password123", "Receiver");

        var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken;
        var ownerSession = ServiceTestSupport.GetValue(service.Login("owner", "Password123")).SessionToken;
        var receiverSession = ServiceTestSupport.GetValue(service.Login("receiver", "Password123")).SessionToken;

        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Main", "d6"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Transfer Me", campaign.Id));
        Assert.True(service.ActivateCharacter(ownerSession, character.Id).Succeeded);

        var ownerTransferAttempt = service.UpdateCharacter(ownerSession, character.Id, "Transfer Me", campaign.Id, "receiver");
        Assert.False(ownerTransferAttempt.Succeeded);

        var missingOwnerAttempt = service.UpdateCharacter(gmSession, character.Id, "Transfer Me", campaign.Id, "missing-user");
        Assert.False(missingOwnerAttempt.Succeeded);

        var gmTransfer = ServiceTestSupport.GetValue(service.UpdateCharacter(gmSession, character.Id, "Transferred", campaign.Id, "receiver"));
        var receiver = service.GetUserBySession(receiverSession);
        Assert.NotNull(receiver);
        Assert.Equal(receiver.Id, gmTransfer.OwnerUserId);

        var previousOwnerMe = ServiceTestSupport.GetValue(service.GetMe(ownerSession));
        Assert.Null(previousOwnerMe.ActiveCharacterId);

        Assert.False(service.ActivateCharacter(ownerSession, character.Id).Succeeded);
        Assert.True(service.ActivateCharacter(receiverSession, character.Id).Succeeded);
    }

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

        service.Register("gm", "Password123", "GM");
        service.Register("owner", "Password123", "Owner");
        service.Register("outsider", "Password123", "Outsider");
        service.Register("admin2", "Password123", "Admin Two");

        var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken;
        var ownerSession = ServiceTestSupport.GetValue(service.Login("owner", "Password123")).SessionToken;
        var outsiderSession = ServiceTestSupport.GetValue(service.Login("outsider", "Password123")).SessionToken;
        var adminTwoSession = ServiceTestSupport.GetValue(service.Login("admin2", "Password123")).SessionToken;

        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Main", "d6"));
        var character = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Unlink Me", campaign.Id));

        var outsiderUnlink = service.UpdateCharacter(outsiderSession, character.Id, "Unlink Me", null);
        Assert.False(outsiderUnlink.Succeeded);

        var ownerUnlink = ServiceTestSupport.GetValue(service.UpdateCharacter(ownerSession, character.Id, "Owner Unlink", null));
        Assert.Null(ownerUnlink.CampaignId);

        var relinkByOwner = ServiceTestSupport.GetValue(service.UpdateCharacter(ownerSession, character.Id, "Relink", campaign.Id));
        Assert.Equal(campaign.Id, relinkByOwner.CampaignId);

        var gmUnlink = ServiceTestSupport.GetValue(service.UpdateCharacter(gmSession, character.Id, "Gm Unlink", null));
        Assert.Null(gmUnlink.CampaignId);

        var relinkByGm = ServiceTestSupport.GetValue(service.UpdateCharacter(gmSession, character.Id, "Relink Again", campaign.Id));
        Assert.Equal(campaign.Id, relinkByGm.CampaignId);

        var adminTwo = service.GetUserBySession(adminTwoSession);
        Assert.NotNull(adminTwo);
        _ = ServiceTestSupport.GetValue(service.UpdateUserRoles(gmSession, adminTwo.Id, ["admin"]));

        var adminUnlink = ServiceTestSupport.GetValue(service.UpdateCharacter(adminTwoSession, character.Id, "Admin Unlink", null));
        Assert.Null(adminUnlink.CampaignId);
    }

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

        service.Register("admin", "Password123", "Admin");
        service.Register("gm", "Password123", "GM");
        service.Register("owner", "Password123", "Owner");
        service.Register("other", "Password123", "Other");

        var adminSession = ServiceTestSupport.GetValue(service.Login("admin", "Password123")).SessionToken;
        var gmSession = ServiceTestSupport.GetValue(service.Login("gm", "Password123")).SessionToken;
        var ownerSession = ServiceTestSupport.GetValue(service.Login("owner", "Password123")).SessionToken;
        var otherSession = ServiceTestSupport.GetValue(service.Login("other", "Password123")).SessionToken;

        var campaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Main", "d6"));
        var ownerCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Owner Character", campaign.Id));
        var otherCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(otherSession, "Other Character", campaign.Id));

        var gmDeleteAttempt = service.DeleteCharacter(gmSession, ownerCharacter.Id);
        Assert.False(gmDeleteAttempt.Succeeded);

        var otherDeleteAttempt = service.DeleteCharacter(otherSession, ownerCharacter.Id);
        Assert.False(otherDeleteAttempt.Succeeded);

        var ownerDelete = ServiceTestSupport.GetValue(service.DeleteCharacter(ownerSession, ownerCharacter.Id));
        Assert.True(ownerDelete);

        var adminDelete = ServiceTestSupport.GetValue(service.DeleteCharacter(adminSession, otherCharacter.Id));
        Assert.True(adminDelete);

        var campaignAfterDeletes = ServiceTestSupport.GetValue(service.GetCampaign(gmSession, campaign.Id));
        Assert.Empty(campaignAfterDeletes.Characters);
    }

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

        service.Register("gm-rm", "Password123", "GM");
        service.Register("owner-rm", "Password123", "Owner");

        var gmSession = ServiceTestSupport.GetValue(service.Login("gm-rm", "Password123")).SessionToken;
        var ownerSession = ServiceTestSupport.GetValue(service.Login("owner-rm", "Password123")).SessionToken;

        var rolemasterCampaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Shadow World", "rolemaster"));
        var dndCampaign = ServiceTestSupport.GetValue(service.CreateCampaign(gmSession, "Forgotten Realms", "dnd5e"));
        var rolemasterCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Harn", rolemasterCampaign.Id));
        var dndCharacter = ServiceTestSupport.GetValue(service.CreateCharacter(ownerSession, "Mage", dndCampaign.Id));

        var negativeDndSkill = service.CreateSkill(ownerSession, dndCharacter.Id, "Invalid", "1d20-1", 0, false);
        Assert.False(negativeDndSkill.Succeeded);

        var invalidRolemasterOptions = service.CreateSkillGroup(ownerSession, rolemasterCharacter.Id, "Invalid", "2d10-15", 3, true);
        Assert.False(invalidRolemasterOptions.Succeeded);

        var rolemasterGroup = ServiceTestSupport.GetValue(service.CreateSkillGroup(ownerSession, rolemasterCharacter.Id, "Awareness", "d100!+15", 0, false, 5));
        Assert.Equal("d100!+15", rolemasterGroup.DiceRollDefinition);
        Assert.Equal(0, rolemasterGroup.WildDice);
        Assert.False(rolemasterGroup.AllowFumble);
        Assert.Equal(5, rolemasterGroup.FumbleRange);

        var percentileWithFumbleRange = service.CreateSkill(ownerSession, rolemasterCharacter.Id, "Bad Percentile", "1d100-20", 0, false, null, 5);
        Assert.False(percentileWithFumbleRange.Succeeded);

        var percentileSkill = ServiceTestSupport.GetValue(service.CreateSkill(ownerSession, rolemasterCharacter.Id, "Perception", "1d100-20", 0, false, rolemasterGroup.Id));
        Assert.Equal("d100-20", percentileSkill.DiceRollDefinition);
        Assert.Equal(0, percentileSkill.WildDice);
        Assert.False(percentileSkill.AllowFumble);
        Assert.Null(percentileSkill.FumbleRange);

        var missingOpenEndedFumbleRange = service.UpdateSkill(ownerSession, percentileSkill.Id, "Perception", "1d100!+85", 0, false, rolemasterGroup.Id);
        Assert.False(missingOpenEndedFumbleRange.Succeeded);

        var invalidOpenEndedFumbleRange = service.UpdateSkill(ownerSession, percentileSkill.Id, "Perception", "1d100!+85", 0, false, rolemasterGroup.Id, 96);
        Assert.False(invalidOpenEndedFumbleRange.Succeeded);

        var openEndedSkill = ServiceTestSupport.GetValue(service.UpdateSkill(ownerSession, percentileSkill.Id, "Perception", "1d100!+85", 0, false, rolemasterGroup.Id, 5));
        Assert.Equal("d100!+85", openEndedSkill.DiceRollDefinition);
        Assert.Equal(0, openEndedSkill.WildDice);
        Assert.False(openEndedSkill.AllowFumble);
        Assert.Equal(5, openEndedSkill.FumbleRange);

        var invalidRetrySkill = service.UpdateSkill(ownerSession, percentileSkill.Id, "Perception", "d100+15", 0, false, rolemasterGroup.Id, null, true);
        Assert.False(invalidRetrySkill.Succeeded);
        Assert.Equal("invalid_rolemaster_retry", invalidRetrySkill.Error!.Code);

        var retrySkill = ServiceTestSupport.GetValue(service.UpdateSkill(ownerSession, percentileSkill.Id, "Perception", "d100!+85", 0, false, rolemasterGroup.Id, 5, true));
        Assert.True(retrySkill.RolemasterAutoRetry);
    }
}