using System.Text;

namespace DodoSSH.Client.Domain.Tests;

/// <summary>
/// A group: one name, and the reasons it is only that.
/// </summary>
/// <remarks>
/// There is very little behaviour here to test, which is itself the design — every field that was considered
/// and left out (a parent, a member list) was left out because of what it would do to the merge. What these
/// tests pin is that the envelope round-trips, that a nameless group cannot be stored, and that renaming the
/// same group on two machines is reported rather than silently resolved.
/// </remarks>
public sealed class HostGroupSecretTests
{
    [Fact]
    public void AGroup_RoundTrips()
    {
        var group = new HostGroupSecret { Label = "production" };

        HostGroupSecretCodec.TryDecode(HostGroupSecretCodec.Encode(group), out var document)
            .ShouldBeTrue();

        document.ShouldNotBeNull();
        document.Group.ShouldBe(group);
        document.SchemaVersion.ShouldBe(HostGroupSecretCodec.CurrentSchemaVersion);
        document.IsReadOnly.ShouldBeFalse();
    }

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    public void AGroupWithNoName_IsRefused(string label)
    {
        new HostGroupSecret { Label = label }.TryValidate(out var reason).ShouldBeFalse();

        reason.ShouldNotBeNull();
    }

    [Fact]
    public void AGroupWrittenByANewerClient_IsReadableButNotWritableHere()
    {
        var payload = Encoding.UTF8.GetBytes(
            """
            {"schemaVersion":99,"label":"production","colour":"a field this build has never heard of"}
            """);

        HostGroupSecretCodec.TryDecode(payload, out var document).ShouldBeTrue();

        document.ShouldNotBeNull();
        document.Group.Label.ShouldBe("production");
        document.IsReadOnly.ShouldBeTrue();
    }

    [Fact]
    public void AnUnnamedPayload_FailsToDecodeRatherThanProducingABlankGroup()
    {
        // Failing closed, as every codec in this folder does: a group with no name is indistinguishable in
        // the sidebar from the ungrouped heading it would sit beside.
        var payload = Encoding.UTF8.GetBytes("""{"schemaVersion":1}""");

        HostGroupSecretCodec.TryDecode(payload, out var document).ShouldBeFalse();

        document.ShouldBeNull();
    }

    [Fact]
    public void TwoDifferentRenames_AreReportedWithBothNames()
    {
        var ancestor = new HostGroupSecret { Label = "production" };

        var result = HostGroupSecretMerge.Merge(
            ancestor,
            ancestor with { Label = "prod" },
            ancestor with { Label = "live" });

        result.Merged.Label.ShouldBe("live");

        var conflict = result.Conflicts.ShouldHaveSingleItem();

        conflict.Field.ShouldBe(nameof(HostGroupSecret.Label));
        conflict.Kept.ShouldBe("live");
        conflict.Discarded.ShouldBe("prod");
    }

    /// <remarks>
    /// The case that would collide if membership were held on the group instead of on each host: two people
    /// filing two different machines into one group at the same time. It cannot reach the merge at all,
    /// because neither of those actions writes to this item.
    /// </remarks>
    [Fact]
    public void FilingHostsIntoAGroup_DoesNotTouchTheGroup()
    {
        var ancestor = new HostGroupSecret { Label = "production" };

        var result = HostGroupSecretMerge.Merge(ancestor, ancestor, ancestor);

        result.HasConflicts.ShouldBeFalse();
        result.Merged.ShouldBe(ancestor);
    }
}
