using System.Text.Json;
using DodoSSH.Contracts;

namespace DodoSSH.Client.Storage;

/// <summary>
/// Translates between the payload columns and <see cref="EncryptedPayload"/>.
/// </summary>
/// <remarks>
/// The columns are nullable because a tombstone has no payload, but the three of them are all-or-
/// nothing: an envelope without its wrapped data key is a row no client can ever open. Reconstructing
/// through here rather than at each call site means that pairing is checked in one place.
/// </remarks>
internal static class CacheMapping
{
    internal static EncryptedPayload? ToPayload(
        byte[]? envelope,
        byte[]? wrappedDataKey,
        Guid? dataKeyId,
        uint keyGeneration,
        byte aadVersion) =>
        envelope is null || wrappedDataKey is null || dataKeyId is null
            ? null
            : new EncryptedPayload(envelope, wrappedDataKey, dataKeyId.Value, keyGeneration, aadVersion);

    internal static EncryptedPayload? ToAncestorPayload(OutboxRow row) =>
        ToPayload(
            row.AncestorPayload,
            row.AncestorWrappedDataKey,
            row.AncestorDataKeyId,
            row.AncestorKeyGeneration ?? 0,
            row.AncestorAadVersion ?? 0);
}

/// <summary>
/// Serialises the plaintext columns so they can be sealed as one unit.
/// </summary>
/// <remarks>
/// <para>
/// The whole record is sealed together rather than split into columns. Nothing queries these yet — the
/// M1 interface lists every host in a vault — and the moment one field needs an index it gets its own
/// column, at which point the duplication is deliberate and visible rather than pre-emptive.
/// </para>
/// <para>
/// Goes through the Contracts serialiser rather than a hand-rolled encoding, so the local
/// representation cannot drift from the wire one. That matters when re-pushing a change: what the
/// server receives must be what the server sent.
/// </para>
/// </remarks>
internal static class PlaintextFieldsCodec
{
    internal static byte[] Encode(SyncPlaintextFields fields) =>
        JsonSerializer.SerializeToUtf8Bytes(
            fields, DodoSshJsonContext.Default.SyncPlaintextFields);

    /// <returns>
    /// The fields, or <see langword="null"/> if the bytes are not a record this build understands. A
    /// null must degrade to "treat the row as stale and re-pull", never to an exception inside a sync
    /// pass.
    /// </returns>
    internal static SyncPlaintextFields? TryDecode(ReadOnlySpan<byte> utf8)
    {
        try
        {
            return JsonSerializer.Deserialize(
                utf8, DodoSshJsonContext.Default.SyncPlaintextFields);
        }
        catch (JsonException)
        {
            return null;
        }
    }
}
