using System.Runtime.Serialization;
using System.Text.Json;
using Akka.Actor;
using Akka.Serialization;
using ZB.MOM.WW.Secrets.Replicator.AkkaDotNet.Protocol;

namespace ZB.MOM.WW.Secrets.Replicator.AkkaDotNet;

/// <summary>
/// Explicit System.Text.Json serializer for the secret-replication wire protocol.
/// </summary>
/// <remarks>
/// <para>
/// Registering this is <b>optional but recommended</b>. The protocol DTOs are all primitives, so
/// Akka's default JSON serializer round-trips them correctly if an application never wires this up —
/// but relying on the default means secret ciphertext is serialized by whatever the cluster's
/// fallback happens to be, which is not a decision worth leaving implicit for this payload. Binding
/// it explicitly also pins the manifest strings below as the versioning boundary: a future protocol
/// change adds a manifest, so a mixed-version cluster fails loudly on an unknown manifest instead of
/// silently mis-deserializing a secret.
/// </para>
/// <para>
/// Wire it up by merging <see cref="AkkaSecretsReplication.SerializationConfig"/> into the
/// application's Akka configuration — see that member for the HOCON.
/// </para>
/// </remarks>
/// <param name="system">The actor system this serializer belongs to.</param>
public sealed class SecretReplicationSerializer(ExtendedActorSystem system) : SerializerWithStringManifest(system)
{
    /// <summary>Stable serializer id. Must not collide with other serializers in the cluster.</summary>
    public const int SerializerId = 7710;

    private const string RowsManifest = "zbs-rows-v1";
    private const string ManifestAnnounceManifest = "zbs-manifest-v1";
    private const string PullRequestManifest = "zbs-pull-v1";

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        // Explicitly NOT indented and NOT camel-cased: the wire form should be compact and stable,
        // not pretty. Property names are the C# names, pinned by the manifest version.
        WriteIndented = false,
    };

    /// <inheritdoc />
    public override int Identifier => SerializerId;

    /// <inheritdoc />
    public override string Manifest(object o) => o switch
    {
        SecretRowsMessage => RowsManifest,
        SecretManifestAnnounce => ManifestAnnounceManifest,
        SecretPullRequest => PullRequestManifest,
        _ => throw new ArgumentException(
            $"{nameof(SecretReplicationSerializer)} cannot serialize {o?.GetType().FullName ?? "null"}.",
            nameof(o)),
    };

    /// <inheritdoc />
    public override byte[] ToBinary(object obj) => obj switch
    {
        SecretRowsMessage rows => JsonSerializer.SerializeToUtf8Bytes(rows, JsonOptions),
        SecretManifestAnnounce announce => JsonSerializer.SerializeToUtf8Bytes(announce, JsonOptions),
        SecretPullRequest pull => JsonSerializer.SerializeToUtf8Bytes(pull, JsonOptions),
        _ => throw new ArgumentException(
            $"{nameof(SecretReplicationSerializer)} cannot serialize {obj?.GetType().FullName ?? "null"}.",
            nameof(obj)),
    };

    /// <inheritdoc />
    public override object FromBinary(byte[] bytes, string manifest) => manifest switch
    {
        RowsManifest => Deserialize<SecretRowsMessage>(bytes, manifest),
        ManifestAnnounceManifest => Deserialize<SecretManifestAnnounce>(bytes, manifest),
        PullRequestManifest => Deserialize<SecretPullRequest>(bytes, manifest),
        _ => throw new SerializationException(
            $"Unknown secret-replication manifest '{manifest}'. This usually means a peer is running a " +
            "newer protocol version than this node."),
    };

    private static T Deserialize<T>(byte[] bytes, string manifest) =>
        JsonSerializer.Deserialize<T>(bytes, JsonOptions)
        ?? throw new SerializationException(
            $"Secret-replication payload with manifest '{manifest}' deserialized to null.");
}
