using System.Buffers.Binary;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Lumi.Companion.Protocol;

public static class ProtocolV1
{
    public const int Version = 1;
    public const int AudioHeaderBytes = 64;
    public const int MaxAudioPayloadBytes = 6400;
    public const int MaxJsonBytes = 64 * 1024;

    public static byte[] EncodeAudio(AudioFrame frame)
    {
        if (frame.Pcm.Length > MaxAudioPayloadBytes || frame.Pcm.Length % 2 != 0)
            throw new ArgumentOutOfRangeException(nameof(frame), "PCM payload must be even and at most 6,400 bytes.");
        var output = new byte[AudioHeaderBytes + frame.Pcm.Length];
        "LACP"u8.CopyTo(output);
        output[4] = Version;
        output[5] = (byte)((frame.Active ? 1 : 0) | (frame.Muted ? 2 : 0));
        BinaryPrimitives.WriteUInt16LittleEndian(output.AsSpan(6), AudioHeaderBytes);
        BinaryPrimitives.WriteUInt32LittleEndian(output.AsSpan(8), frame.Sequence);
        BinaryPrimitives.WriteUInt64LittleEndian(output.AsSpan(12), frame.CaptureTimestampUs);
        frame.SessionId.TryWriteBytes(output.AsSpan(20), bigEndian: true, out _);
        frame.SourceUuid.TryWriteBytes(output.AsSpan(36), bigEndian: true, out _);
        BinaryPrimitives.WriteUInt32LittleEndian(output.AsSpan(52), 16000);
        BinaryPrimitives.WriteUInt16LittleEndian(output.AsSpan(56), 1);
        BinaryPrimitives.WriteUInt16LittleEndian(output.AsSpan(58), 16);
        BinaryPrimitives.WriteUInt32LittleEndian(output.AsSpan(60), (uint)frame.Pcm.Length);
        frame.Pcm.Span.CopyTo(output.AsSpan(AudioHeaderBytes));
        return output;
    }

    public static byte[] EncodeEnvelope(string type, object payload, Guid? sessionId = null) =>
        JsonSerializer.SerializeToUtf8Bytes(new Envelope(Version, type, Guid.NewGuid(), DateTimeOffset.UtcNow, sessionId, payload), JsonOptions);

    public static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };
}

public sealed record AudioFrame(Guid SessionId, Guid SourceUuid, uint Sequence, ulong CaptureTimestampUs, bool Active, bool Muted, ReadOnlyMemory<byte> Pcm);
public sealed record Envelope(
    [property: JsonPropertyName("version")] int Version,
    [property: JsonPropertyName("type")] string Type,
    [property: JsonPropertyName("id")] Guid Id,
    [property: JsonPropertyName("sent_at")] DateTimeOffset SentAt,
    [property: JsonPropertyName("session_id")] Guid? SessionId,
    [property: JsonPropertyName("payload")] object Payload);
public sealed record ServerEnvelope(
    [property: JsonPropertyName("version")] int Version,
    [property: JsonPropertyName("type")] string Type,
    [property: JsonPropertyName("id")] Guid Id,
    [property: JsonPropertyName("sent_at")] DateTimeOffset SentAt,
    [property: JsonPropertyName("session_id")] Guid? SessionId,
    [property: JsonPropertyName("payload")] JsonElement Payload);
public sealed record PairingBootstrap(
    [property: JsonPropertyName("format")] string Format,
    [property: JsonPropertyName("token")] string Token,
    [property: JsonPropertyName("host")] string Host,
    [property: JsonPropertyName("exchange_url")] string ExchangeUrl,
    [property: JsonPropertyName("expires_at")] long ExpiresAt,
    [property: JsonPropertyName("protocol_version")] int ProtocolVersion);
public sealed record DeviceCredential(string DeviceId, string DeviceSecret, string Host, string[] Capabilities, int ProtocolVersion);
