// Copyright 2012-2026 The NATS Authors
// Licensed under the Apache License, Version 2.0

using System.Text.Json;

namespace ZB.MOM.NatsNet.Server;

internal static class EntryTypeExtensions
{
    internal static string String(this EntryType entryType) => entryType switch
    {
        EntryType.EntryNormal => "EntryNormal",
        EntryType.EntryOldSnapshot => "EntryOldSnapshot",
        EntryType.EntryPeerState => "EntryPeerState",
        EntryType.EntryAddPeer => "EntryAddPeer",
        EntryType.EntryRemovePeer => "EntryRemovePeer",
        EntryType.EntryLeaderTransfer => "EntryLeaderTransfer",
        EntryType.EntrySnapshot => "EntrySnapshot",
        EntryType.EntryCatchup => "EntryCatchup",
        _ => "UNKNOWN",
    };
}

internal sealed partial class Raft
{
    public CommittedEntry NewCommittedEntry(ulong index, IReadOnlyList<Entry>? entries = null)
    {
        var committed = new CommittedEntry
        {
            Index = index,
        };
        if (entries is not null)
        {
            committed.Entries.AddRange(entries);
        }

        return committed;
    }

    public Entry NewEntry(EntryType type, byte[]? data = null) => new()
    {
        Type = type,
        Data = data is null ? [] : [.. data],
    };

    public AppendEntry NewAppendEntry(string leader, ulong term, ulong commit, ulong prevTerm, ulong prevIndex, IReadOnlyList<Entry>? entries = null)
    {
        var appendEntry = new AppendEntry
        {
            Leader = leader,
            TermV = term,
            Commit = commit,
            PTerm = prevTerm,
            PIndex = prevIndex,
        };
        if (entries is not null)
        {
            appendEntry.Entries.AddRange(entries);
        }

        return appendEntry;
    }

    public ProposedEntry NewProposedEntry(Entry? entry = null, string? reply = null) => new()
    {
        Entry = entry,
        Reply = reply ?? string.Empty,
    };

    public AppendEntry DecodeAppendEntry(byte[] buffer)
    {
        ArgumentNullException.ThrowIfNull(buffer);
        return JsonSerializer.Deserialize<AppendEntry>(buffer) ?? new AppendEntry();
    }

    public AppendEntryResponse NewAppendEntryResponse(ulong term, ulong index, string peer, string reply, bool success) => new()
    {
        TermV = term,
        Index = index,
        Peer = peer,
        Reply = reply,
        Success = success,
    };

    public AppendEntryResponse DecodeAppendEntryResponse(byte[] buffer)
    {
        ArgumentNullException.ThrowIfNull(buffer);
        return JsonSerializer.Deserialize<AppendEntryResponse>(buffer) ?? new AppendEntryResponse();
    }

    public void HandleForwardedRemovePeerProposal(string peer)
    {
        RemovePeer(peer);
    }

    public void HandleForwardedProposal(byte[] entry)
    {
        ForwardProposal(entry);
    }

    public void AddPeer(string peer)
    {
        ProposeAddPeer(peer);
    }

    public void RemovePeer(string peer)
    {
        ProposeRemovePeer(peer);
    }

    public void SendMembershipChange(EntryType changeType, string peer)
    {
        var entry = NewEntry(changeType, System.Text.Encoding.UTF8.GetBytes(peer));
        var proposed = NewProposedEntry(entry);
        PropQ ??= new ZB.MOM.NatsNet.Server.Internal.IpQueue<ProposedEntry>($"{GroupName}-propose");
        PropQ.Push(proposed);
    }

    public int PeerStateBufSize(PeerState state)
    {
        ArgumentNullException.ThrowIfNull(state);
        return EncodePeerState(state).Length;
    }

    public byte[] EncodePeerState(PeerState state)
    {
        ArgumentNullException.ThrowIfNull(state);
        return JsonSerializer.SerializeToUtf8Bytes(state);
    }

    public PeerState DecodePeerState(byte[] buffer)
    {
        ArgumentNullException.ThrowIfNull(buffer);
        return JsonSerializer.Deserialize<PeerState>(buffer) ?? new PeerState();
    }

    public VoteRequest DecodeVoteRequest(byte[] buffer)
    {
        ArgumentNullException.ThrowIfNull(buffer);
        return JsonSerializer.Deserialize<VoteRequest>(buffer) ?? new VoteRequest();
    }

    public Exception? WritePeerStateStatic(string storeDir, PeerState state)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(storeDir);
        ArgumentNullException.ThrowIfNull(state);
        try
        {
            Directory.CreateDirectory(storeDir);
            var path = Path.Combine(storeDir, "peerstate.json");
            File.WriteAllBytes(path, EncodePeerState(state));
            return null;
        }
        catch (Exception ex)
        {
            return ex;
        }
    }

    public (PeerState? State, Exception? Error) ReadPeerState(string storeDir)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(storeDir);
        try
        {
            var path = Path.Combine(storeDir, "peerstate.json");
            if (!File.Exists(path))
            {
                return (null, new FileNotFoundException("peer state file not found", path));
            }

            var buffer = File.ReadAllBytes(path);
            return (DecodePeerState(buffer), null);
        }
        catch (Exception ex)
        {
            return (null, ex);
        }
    }

    public Exception? WriteTermVoteStatic(string storeDir, ulong term, string vote)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(storeDir);
        try
        {
            Directory.CreateDirectory(storeDir);
            var payload = JsonSerializer.SerializeToUtf8Bytes(new TermVoteFile { Term = term, Vote = vote ?? string.Empty });
            var path = Path.Combine(storeDir, "tav.idx");
            File.WriteAllBytes(path, payload);
            Term_ = term;
            Vote = vote ?? string.Empty;
            return null;
        }
        catch (Exception ex)
        {
            return ex;
        }
    }

    public VoteResponse DecodeVoteResponse(byte[] buffer)
    {
        ArgumentNullException.ThrowIfNull(buffer);
        return JsonSerializer.Deserialize<VoteResponse>(buffer) ?? new VoteResponse();
    }
}

internal sealed class TermVoteFile
{
    public ulong Term { get; set; }
    public string Vote { get; set; } = string.Empty;
}

internal static class AppendEntryExtensions
{
    internal static string String(this AppendEntry appendEntry) =>
        $"AppendEntry[leader={appendEntry.Leader}, term={appendEntry.TermV}, commit={appendEntry.Commit}, prev={appendEntry.PIndex}]";

    internal static byte[] Encode(this AppendEntry appendEntry) =>
        JsonSerializer.SerializeToUtf8Bytes(appendEntry);

    internal static bool ShouldStore(this AppendEntry appendEntry) =>
        appendEntry.Entries.Count > 0 || appendEntry.Commit > appendEntry.PIndex;
}
