using System.Text.Json;
using NATS.Server.Raft;

namespace NATS.Server.Raft.Tests.Raft;

/// <summary>
/// Wire format encoding/decoding tests for RAFT RPC contracts.
/// Go: TestNRGAppendEntryEncode, TestNRGAppendEntryDecode in server/raft_test.go:82-152.
/// The .NET implementation uses JSON serialization rather than binary encoding,
/// so these tests validate JSON round-trip fidelity for all RPC types.
/// </summary>
public class RaftWireFormatTests
{
    // Go: TestNRGAppendEntryEncode server/raft_test.go:82
    [Fact]
    public void VoteRequest_json_round_trip()
    {
        var original = new VoteRequest { Term = 5, CandidateId = "node-alpha" };
        var json = JsonSerializer.Serialize(original);
        json.ShouldNotBeNullOrWhiteSpace();

        var decoded = JsonSerializer.Deserialize<VoteRequest>(json);
        decoded.ShouldNotBeNull();
        decoded.Term.ShouldBe(5);
        decoded.CandidateId.ShouldBe("node-alpha");
    }

    // Go: TestNRGAppendEntryEncode server/raft_test.go:82
    [Fact]
    public void VoteResponse_json_round_trip()
    {
        var granted = new VoteResponse { Granted = true };
        var json = JsonSerializer.Serialize(granted);
        var decoded = JsonSerializer.Deserialize<VoteResponse>(json);
        decoded.ShouldNotBeNull();
        decoded.Granted.ShouldBeTrue();

        var denied = new VoteResponse { Granted = false };
        var json2 = JsonSerializer.Serialize(denied);
        var decoded2 = JsonSerializer.Deserialize<VoteResponse>(json2);
        decoded2.ShouldNotBeNull();
        decoded2.Granted.ShouldBeFalse();
    }

    // Go: TestNRGAppendEntryEncode server/raft_test.go:82
    [Fact]
    public void AppendResult_json_round_trip()
    {
        var original = new AppendResult { FollowerId = "f1", Success = true };
        var json = JsonSerializer.Serialize(original);
        var decoded = JsonSerializer.Deserialize<AppendResult>(json);
        decoded.ShouldNotBeNull();
        decoded.FollowerId.ShouldBe("f1");
        decoded.Success.ShouldBeTrue();
    }

    // Go: TestNRGAppendEntryEncode server/raft_test.go:82 — multiple entries
    [Fact]
    public void RaftLogEntry_batch_json_round_trip_preserves_order()
    {
        var entries = Enumerable.Range(1, 50)
            .Select(i => new RaftLogEntry(Index: i, Term: (i % 3) + 1, Command: $"op-{i}"))
            .ToList();

        var json = JsonSerializer.Serialize(entries);
        var decoded = JsonSerializer.Deserialize<List<RaftLogEntry>>(json);

        decoded.ShouldNotBeNull();
        decoded.Count.ShouldBe(50);

        for (var i = 0; i < 50; i++)
        {
            decoded[i].Index.ShouldBe(i + 1);
            decoded[i].Term.ShouldBe((i + 1) % 3 + 1);
            decoded[i].Command.ShouldBe($"op-{i + 1}");
        }
    }

    // Go: TestNRGAppendEntryEncode server/raft_test.go:82 — large payload
    [Fact]
    public void RaftLogEntry_large_command_round_trips()
    {
        var largeCommand = new string('x', 65536);
        var entry = new RaftLogEntry(Index: 1, Term: 1, Command: largeCommand);

        var json = JsonSerializer.Serialize(entry);
        var decoded = JsonSerializer.Deserialize<RaftLogEntry>(json);

        decoded.ShouldNotBeNull();
        decoded.Command.Length.ShouldBe(65536);
        decoded.Command.ShouldBe(largeCommand);
    }

    // Go: TestNRGAppendEntryEncode server/raft_test.go:82 — snapshot marker
    [Fact]
    public void RaftSnapshot_json_round_trip()
    {
        var data = new byte[256];
        Random.Shared.NextBytes(data);

        var snapshot = new RaftSnapshot
        {
            LastIncludedIndex = 999,
            LastIncludedTerm = 42,
            Data = data,
        };

        var json = JsonSerializer.Serialize(snapshot);
        var decoded = JsonSerializer.Deserialize<RaftSnapshot>(json);

        decoded.ShouldNotBeNull();
        decoded.LastIncludedIndex.ShouldBe(999);
        decoded.LastIncludedTerm.ShouldBe(42);
        decoded.Data.ShouldBe(data);
    }

    // Go: TestNRGAppendEntryEncode server/raft_test.go:82 — empty snapshot data
    [Fact]
    public void RaftSnapshot_empty_data_round_trips()
    {
        var snapshot = new RaftSnapshot
        {
            LastIncludedIndex = 10,
            LastIncludedTerm = 2,
            Data = [],
        };

        var json = JsonSerializer.Serialize(snapshot);
        var decoded = JsonSerializer.Deserialize<RaftSnapshot>(json);

        decoded.ShouldNotBeNull();
        decoded.Data.ShouldBeEmpty();
    }

    // Go: TestNRGAppendEntryEncode server/raft_test.go:82 — special characters
    [Fact]
    public void RaftLogEntry_special_characters_in_command_round_trips()
    {
        var commands = new[]
        {
            "hello\nworld",
            "tab\there",
            "quote\"inside",
            "backslash\\path",
            "unicode-\u00e9\u00e0\u00fc",
            "{\"nested\":\"json\"}",
        };

        foreach (var cmd in commands)
        {
            var entry = new RaftLogEntry(Index: 1, Term: 1, Command: cmd);
            var json = JsonSerializer.Serialize(entry);
            var decoded = JsonSerializer.Deserialize<RaftLogEntry>(json);
            decoded.ShouldNotBeNull();
            decoded.Command.ShouldBe(cmd);
        }
    }

    // Go: TestNRGAppendEntryDecode server/raft_test.go:125 — deserialization of malformed input
    [Fact]
    public void Malformed_json_returns_null_or_throws()
    {
        var badJson = "not-json-at-all";
        Should.Throw<JsonException>(() => JsonSerializer.Deserialize<RaftLogEntry>(badJson));
    }
}
