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

using System.Threading;
using System.Threading.Channels;

namespace ZB.MOM.NatsNet.Server;

internal sealed partial class Raft
{
    public void SetWriteErrLocked(Exception? error)
    {
        if (error is not null && WriteErr is null)
        {
            WriteErr = error;
        }
    }

    public bool IsClosed() => State() == RaftState.Closed;

    public void SetWriteErr(Exception? error)
    {
        _lock.EnterWriteLock();
        try
        {
            SetWriteErrLocked(error);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public Exception? WriteTermVote() => WriteTermVoteStatic(StoreDir, Term_, Vote);

    public void HandleVoteResponse(VoteResponse response)
    {
        ArgumentNullException.ThrowIfNull(response);

        _lock.EnterWriteLock();
        try
        {
            if (response.TermV > Term_)
            {
                Term_ = response.TermV;
                SwitchToFollowerLocked(string.Empty);
                return;
            }

            if (State() != RaftState.Candidate)
            {
                return;
            }

            Votes_ ??= new ZB.MOM.NatsNet.Server.Internal.IpQueue<VoteResponse>($"{GroupName}-votes");
            Votes_.Push(response);
            if (WonElection())
            {
                SwitchToLeader();
            }
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public bool ProcessVoteRequest(VoteRequest request)
    {
        ArgumentNullException.ThrowIfNull(request);

        _lock.EnterWriteLock();
        try
        {
            if (request.TermV < Term_)
            {
                return false;
            }

            if (request.TermV > Term_)
            {
                Term_ = request.TermV;
                Vote = string.Empty;
                SwitchToFollowerLocked(string.Empty);
            }

            var upToDate = request.LastTerm > PTerm || (request.LastTerm == PTerm && request.LastIndex >= PIndex);
            if (!upToDate)
            {
                return false;
            }

            if (string.IsNullOrWhiteSpace(Vote) || string.Equals(Vote, request.Candidate, StringComparison.Ordinal))
            {
                Vote = request.Candidate;
                _ = WriteTermVote();
                Active = DateTime.UtcNow;
                return true;
            }

            return false;
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void HandleVoteRequest(VoteRequest request)
    {
        ArgumentNullException.ThrowIfNull(request);

        var granted = ProcessVoteRequest(request);
        if (!string.IsNullOrWhiteSpace(request.Reply))
        {
            var response = new VoteResponse
            {
                TermV = Term_,
                Peer = Id,
                Granted = granted,
                Empty = PIndex == 0,
            };
            SendReply(request.Reply, response.Encode());
        }
    }

    public void RequestVote()
    {
        _lock.EnterWriteLock();
        try
        {
            var request = new VoteRequest
            {
                TermV = Term_,
                LastTerm = PTerm,
                LastIndex = PIndex,
                Candidate = Id,
                Reply = VReply,
            };

            Reqs ??= new ZB.MOM.NatsNet.Server.Internal.IpQueue<VoteRequest>($"{GroupName}-requests");
            Reqs.Push(request);
            SendRPC(VSubj, request.Encode());
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void SendRPC(string subject, byte[] payload)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(subject);
        ArgumentNullException.ThrowIfNull(payload);

        _lock.EnterWriteLock();
        try
        {
            var pending = SendQ as List<(string Subject, byte[] Payload)>;
            if (pending is null)
            {
                pending = [];
                SendQ = pending;
            }

            pending.Add((subject, [.. payload]));
            Active = DateTime.UtcNow;
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void SendReply(string subject, byte[] payload)
    {
        SendRPC(subject, payload);
    }

    public bool WonElection()
    {
        if (State() != RaftState.Candidate)
        {
            return false;
        }

        var votes = 1; // self-vote
        if (Votes_ is not null)
        {
            votes += Votes_.Len();
        }

        return votes >= QuorumNeeded();
    }

    public int QuorumNeeded()
    {
        return Qn > 0 ? Qn : (Math.Max(ClusterSize(), 1) / 2) + 1;
    }

    public void UpdateLeadChange(bool isLeader)
    {
        _lock.EnterWriteLock();
        try
        {
            LeadC ??= Channel.CreateUnbounded<bool>();
            LeadC.Writer.TryWrite(isLeader);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void SwitchState(RaftState newState)
    {
        switch (newState)
        {
            case RaftState.Follower:
                SwitchToFollower();
                break;
            case RaftState.Candidate:
                SwitchToCandidate();
                break;
            case RaftState.Leader:
                SwitchToLeader();
                break;
            default:
                _lock.EnterWriteLock();
                try
                {
                    StateValue = (int)newState;
                }
                finally
                {
                    _lock.ExitWriteLock();
                }
                break;
        }
    }

    public void SwitchToFollower(string newLeader = "")
    {
        _lock.EnterWriteLock();
        try
        {
            SwitchToFollowerLocked(newLeader);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void SwitchToFollowerLocked(string newLeader = "")
    {
        StateValue = (int)RaftState.Follower;
        if (!string.IsNullOrWhiteSpace(newLeader))
        {
            LeaderId = newLeader;
        }

        Interlocked.Exchange(ref HasLeaderV, string.IsNullOrWhiteSpace(LeaderId) ? 0 : 1);
        UpdateLeadChange(false);
        ResetElectionTimeoutWithLock();
    }

    public void SwitchToCandidate()
    {
        _lock.EnterWriteLock();
        try
        {
            StateValue = (int)RaftState.Candidate;
            Term_++;
            Vote = Id;
            Votes_ ??= new ZB.MOM.NatsNet.Server.Internal.IpQueue<VoteResponse>($"{GroupName}-votes");
            Votes_.Push(new VoteResponse { TermV = Term_, Peer = Id, Granted = true });
            UpdateLeadChange(false);
            ResetElectionTimeoutWithLock();
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void SwitchToLeader()
    {
        _lock.EnterWriteLock();
        try
        {
            StateValue = (int)RaftState.Leader;
            LeaderId = Id;
            Lsut = DateTime.UtcNow;
            Interlocked.Exchange(ref HasLeaderV, 1);
            Interlocked.Exchange(ref PLeaderV, 1);
            UpdateLeadChange(true);
            SendHeartbeat();
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }
}
