namespace DodoSSH.Client.Domain;

/// <summary>The merged entry, and everything that had to be overridden to produce it.</summary>
/// <param name="Merged">The entry to store and push.</param>
/// <param name="Conflicts">Empty when the two sides were reconcilable field by field.</param>
public sealed record ConnectionLogMergeResult(
    ConnectionLogSecret Merged,
    IReadOnlyList<HostFieldConflict> Conflicts)
{
    /// <summary>Whether anything had to be overridden.</summary>
    public bool HasConflicts => Conflicts.Count > 0;
}

/// <inheritdoc cref="ConnectionLogMergeResult" />
public sealed record ActivityLogMergeResult(
    ActivityLogSecret Merged,
    IReadOnlyList<HostFieldConflict> Conflicts)
{
    /// <summary>Whether anything had to be overridden.</summary>
    public bool HasConflicts => Conflicts.Count > 0;
}

/// <summary>
/// Merges two divergent versions of a connection log entry.
/// </summary>
/// <remarks>
/// <para>
/// <b>This exists because the item-kind pipeline requires it, and it should never run.</b> A log entry is
/// written once, at the moment a connection closes, and nothing updates one — so there is no second version
/// for a first to diverge from. Reaching this code means two clients wrote different records under one
/// entity id, and entity ids are v7 GUIDs minted independently on each machine.
/// </para>
/// <para>
/// It is still a real merge rather than a throw. The reconciler runs inside a sync pass, and an exception
/// there would strand every item queued behind this one — for a situation that is a bug in some client and
/// not an emergency. So the remote side wins, the difference is recorded like any other, and somebody reads
/// a conflict notice about a log entry, which is the loudest signal this could reasonably give.
/// </para>
/// <para>
/// Nothing is redacted. Every field is already an audit record of something that happened, and a notice that
/// hid which of two records was dropped would defeat the point of noticing.
/// </para>
/// </remarks>
public static class ConnectionLogSecretMerge
{
    /// <summary>Produces the merged entry.</summary>
    /// <param name="ancestor">The version both sides branched from.</param>
    /// <param name="local">The pending local version.</param>
    /// <param name="remote">The server's current version.</param>
    public static ConnectionLogMergeResult Merge(
        ConnectionLogSecret ancestor,
        ConnectionLogSecret local,
        ConnectionLogSecret remote)
    {
        ArgumentNullException.ThrowIfNull(ancestor);
        ArgumentNullException.ThrowIfNull(local);
        ArgumentNullException.ThrowIfNull(remote);

        // Whole-value, not field by field. The fields of one entry describe one event, and a merge that took
        // the host from one side and the duration from the other would invent a connection nobody made —
        // which is a worse outcome than losing the record this machine happened to hold.
        if (local == remote)
        {
            return new ConnectionLogMergeResult(remote, []);
        }

        return new ConnectionLogMergeResult(
            remote,
            [
                new HostFieldConflict(
                    "Entry",
                    MergeSide.Local,
                    remote.Label,
                    local.Label,
                    DiscardedWasRemoval: false),
            ]);
    }
}

/// <summary>
/// Merges two divergent versions of an activity log entry.
/// </summary>
/// <inheritdoc cref="ConnectionLogSecretMerge" path="/remarks" />
public static class ActivityLogSecretMerge
{
    /// <inheritdoc cref="ConnectionLogSecretMerge.Merge" />
    public static ActivityLogMergeResult Merge(
        ActivityLogSecret ancestor,
        ActivityLogSecret local,
        ActivityLogSecret remote)
    {
        ArgumentNullException.ThrowIfNull(ancestor);
        ArgumentNullException.ThrowIfNull(local);
        ArgumentNullException.ThrowIfNull(remote);

        if (local == remote)
        {
            return new ActivityLogMergeResult(remote, []);
        }

        return new ActivityLogMergeResult(
            remote,
            [
                new HostFieldConflict(
                    "Entry",
                    MergeSide.Local,
                    remote.Label,
                    local.Label,
                    DiscardedWasRemoval: false),
            ]);
    }
}
