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

using System.Text;
using System.Text.Json;

namespace ZB.MOM.NatsNet.Server;

internal sealed partial class Raft
{
    public void ProcessAppendEntry(AppendEntry appendEntry)
    {
        ArgumentNullException.ThrowIfNull(appendEntry);

        ProcessAppendEntries(appendEntry);
        if (appendEntry.ShouldStore())
        {
            StoreToWAL(appendEntry);
        }
    }

    public void ResetInitializing()
    {
        Initializing = false;
    }

    public void ProcessPeerState(Entry entry)
    {
        ArgumentNullException.ThrowIfNull(entry);
        if (entry.Type != EntryType.EntryPeerState || entry.Data.Length == 0)
        {
            return;
        }

        var peerState = DecodePeerState(entry.Data);
        _lock.EnterWriteLock();
        try
        {
            ProposeKnownPeers(peerState.KnownPeers);
            Csz = Math.Max(peerState.ClusterSize, 1);
            Qn = (Csz / 2) + 1;
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void ProcessAppendEntryResponse(AppendEntryResponse response)
    {
        ArgumentNullException.ThrowIfNull(response);
        TrackResponse(response);
    }

    public void HandleAppendEntryResponse(AppendEntryResponse response)
    {
        ProcessAppendEntryResponse(response);
    }

    public AppendEntry BuildAppendEntry(string peer)
    {
        _lock.EnterReadLock();
        try
        {
            var appendEntry = NewAppendEntry(Id, Term_, Commit, PTerm, PIndex);
            appendEntry.Reply = AReply;
            if (Pae.TryGetValue(PIndex, out var pending))
            {
                appendEntry.Entries.AddRange(pending.Entries.Select(entry => NewEntry(entry.Type, entry.Data)));
            }

            if (!string.IsNullOrWhiteSpace(peer))
            {
                appendEntry.Reply = $"{AReply}.{peer}";
            }

            return appendEntry;
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    public bool StoreToWAL(AppendEntry appendEntry)
    {
        ArgumentNullException.ThrowIfNull(appendEntry);
        if (!appendEntry.ShouldStore())
        {
            return false;
        }

        _lock.EnterWriteLock();
        try
        {
            var index = appendEntry.PIndex;
            foreach (var entry in appendEntry.Entries)
            {
                index++;
                var stored = NewAppendEntry(appendEntry.Leader, appendEntry.TermV, appendEntry.Commit, appendEntry.PTerm, index, [entry]);
                CachePendingEntry(stored, index);
            }

            PIndex = Math.Max(PIndex, index);
            PTerm = appendEntry.TermV;
            Commit = Math.Max(Commit, appendEntry.Commit);
            WalBytes += (ulong)appendEntry.Encode().Length;
            return true;
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void SendAppendEntry(AppendEntry appendEntry, string? peer = null)
    {
        ArgumentNullException.ThrowIfNull(appendEntry);

        _lock.EnterWriteLock();
        try
        {
            if (!string.IsNullOrWhiteSpace(peer))
            {
                SendAppendEntryLocked(peer, appendEntry);
                return;
            }

            foreach (var peerName in PeerNames())
            {
                SendAppendEntryLocked(peerName, appendEntry);
            }
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void SendAppendEntryLocked(string peer, AppendEntry appendEntry)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(peer);
        ArgumentNullException.ThrowIfNull(appendEntry);

        CachePendingEntry(appendEntry);
        TrackPeer(peer, appendEntry.PIndex);
        SendRPC($"{GroupName}.append.{peer}", appendEntry.Encode());
    }

    public void CachePendingEntry(AppendEntry appendEntry, ulong? index = null)
    {
        ArgumentNullException.ThrowIfNull(appendEntry);
        var cacheIndex = index ?? (appendEntry.PIndex > 0 ? appendEntry.PIndex : PIndex + 1);
        Pae[cacheIndex] = appendEntry;
    }

    public IReadOnlyList<string> PeerNames()
    {
        _lock.EnterReadLock();
        try
        {
            return [.. Peers_.Keys.OrderBy(static key => key, StringComparer.Ordinal)];
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    public PeerState CurrentPeerState()
    {
        _lock.EnterReadLock();
        try
        {
            return CurrentPeerStateLocked();
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    public PeerState CurrentPeerStateLocked()
    {
        return new PeerState
        {
            KnownPeers = [.. PeerNames()],
            ClusterSize = ClusterSize(),
        };
    }

    public void SendPeerState()
    {
        var state = CurrentPeerState();
        Wps = EncodePeerState(state);
        var entry = NewEntry(EntryType.EntryPeerState, Wps);
        var appendEntry = NewAppendEntry(Id, Term_, Commit, PTerm, PIndex, [entry]);
        SendAppendEntry(appendEntry);
    }

    public void SendHeartbeat()
    {
        var heartbeat = NewAppendEntry(Id, Term_, Commit, PTerm, PIndex);
        SendAppendEntry(heartbeat);
    }

    public Exception? WritePeerState()
    {
        return WritePeerStateStatic(StoreDir, CurrentPeerState());
    }

    public (ulong Term, string Vote, Exception? Error) ReadTermVote()
    {
        try
        {
            var path = Path.Combine(StoreDir, "tav.idx");
            if (!File.Exists(path))
            {
                return (0, string.Empty, new FileNotFoundException("term vote file not found", path));
            }

            var payload = File.ReadAllBytes(path);
            var state = JsonSerializer.Deserialize<TermVoteFile>(payload);
            if (state is null)
            {
                return (0, string.Empty, new InvalidDataException("term vote file is invalid"));
            }

            Term_ = state.Term;
            Vote = state.Vote;
            Wtv = payload;
            return (state.Term, state.Vote, null);
        }
        catch (Exception ex)
        {
            return (0, string.Empty, ex);
        }
    }
}
