using DodoSSH.Contracts;
using DodoSSH.Domain;

namespace DodoSSH.Api.Tests;

/// <summary>
/// The two entity-type enums have to agree, and nothing but this makes them.
/// </summary>
/// <remarks>
/// <para>
/// <c>SyncService</c> converts between the wire's <see cref="SyncEntityType"/> and the change log's
/// <see cref="ChangeEntityType"/> with a raw <c>(ChangeEntityType)(int)</c> cast in both directions. That
/// works only because two independently maintained enums in two assemblies happen to number their members
/// identically — and they do not even name them identically: <c>Host = 1</c> against <c>SshHost = 1</c>.
/// </para>
/// <para>
/// If they ever drift, nothing fails loudly. Changes get filed in the log under a different item type's
/// name, and the next delta pull loads rows of the wrong type — or none — for entities the client asked
/// about. That is silent data loss on the sync path, which is precisely the failure this project has
/// designed everything else to avoid, so the alignment gets a test rather than a comment.
/// </para>
/// <para>
/// Numeric alignment only. The two lists are deliberately <em>not</em> aligned with
/// <c>CryptoSpec.AadResourceType</c>, which also carries None/User/Device/Vault and therefore numbers the
/// same item types differently — asserting three-way equality would be asserting something false.
/// </para>
/// </remarks>
public sealed class EntityTypeAlignmentTests
{
    [Fact]
    public void EveryWireEntityType_HasAChangeLogTypeWithTheSameValue()
    {
        var changeValues = Enum.GetValues<ChangeEntityType>().Select(value => (int)value).ToHashSet();

        foreach (var wire in Enum.GetValues<SyncEntityType>())
        {
            changeValues.ShouldContain(
                (int)wire,
                $"SyncEntityType.{wire} = {(int)wire} has no ChangeEntityType with that value, so "
                + "SyncService's cast would produce an undefined enum value.");
        }
    }

    [Fact]
    public void EveryChangeLogType_HasAWireTypeWithTheSameValue()
    {
        // The other direction matters just as much: a pull converts stored changes back to wire types, so a
        // change-log type with no wire counterpart would be served to clients as an undefined enum.
        var wireValues = Enum.GetValues<SyncEntityType>().Select(value => (int)value).ToHashSet();

        foreach (var change in Enum.GetValues<ChangeEntityType>())
        {
            wireValues.ShouldContain(
                (int)change,
                $"ChangeEntityType.{change} = {(int)change} has no SyncEntityType with that value.");
        }
    }

    [Fact]
    public void TheTwoEnums_HaveTheSameNumberOfMembers()
    {
        // Catches a member added to one list only, which the two checks above would miss if it reused a
        // value already present in the other.
        Enum.GetValues<SyncEntityType>().Length.ShouldBe(Enum.GetValues<ChangeEntityType>().Length);
    }

    /// <remarks>
    /// Spot-checked by name as well, because the pairing that is easiest to get wrong is the one whose names
    /// differ. Someone adding an item type may reasonably assume the lists are name-matched, notice
    /// <c>Host</c> has no <c>Host</c> on the other side, and renumber to "fix" it.
    /// </remarks>
    [Fact]
    public void TheDifferentlyNamedPair_IsTheOneThatMatches()
    {
        ((int)SyncEntityType.Host).ShouldBe((int)ChangeEntityType.SshHost);
        ((int)SyncEntityType.SshKey).ShouldBe((int)ChangeEntityType.SshKey);
    }
}
