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

using System.Threading.Channels;

namespace ZB.MOM.NatsNet.Server;

internal sealed partial class Raft
{
    public void Shutdown()
    {
        Stop();
    }

    public string NewCatchupInbox() => $"_INBOX.CATCHUP.{Id}.{Guid.NewGuid():N}";

    public string NewInbox() => $"_INBOX.{Id}.{Guid.NewGuid():N}";

    public object Subscribe(string subject, Action<byte[]?>? handler = null)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(subject);

        var subscription = new RaftSubscription(subject, handler);
        _lock.EnterWriteLock();
        try
        {
            AeSub = subscription;
            Active = DateTime.UtcNow;
        }
        finally
        {
            _lock.ExitWriteLock();
        }

        return subscription;
    }

    public void Unsubscribe(object subscription)
    {
        _lock.EnterWriteLock();
        try
        {
            if (ReferenceEquals(AeSub, subscription))
            {
                AeSub = null;
            }
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public Exception? CreateInternalSubs()
    {
        try
        {
            var aeSubj = string.IsNullOrWhiteSpace(ASubj) ? $"{GroupName}.append" : ASubj;
            Subscribe(aeSubj);
            return null;
        }
        catch (Exception ex)
        {
            return ex;
        }
    }

    public TimeSpan RandElectionTimeout()
    {
        var min = 150;
        var max = 300;
        return TimeSpan.FromMilliseconds(Random.Shared.Next(min, max));
    }

    public void ResetElectionTimeout()
    {
        _lock.EnterWriteLock();
        try
        {
            ResetElectionTimeoutWithLock();
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void ResetElectionTimeoutWithLock()
    {
        ResetElectWithLock(RandElectionTimeout());
    }

    public void ResetElect(TimeSpan timeout)
    {
        _lock.EnterWriteLock();
        try
        {
            ResetElectWithLock(timeout);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void ResetElectWithLock(TimeSpan timeout)
    {
        Elect?.Dispose();
        var due = timeout < TimeSpan.Zero ? TimeSpan.Zero : timeout;
        Elect = new Timer(_ => { }, null, due, Timeout.InfiniteTimeSpan);
        Active = DateTime.UtcNow;
    }

    public void Run()
    {
        RunAsFollower();
    }

    public void Debug(string format, params object?[] args)
    {
        _ = string.Format(format, args);
    }

    public void Warn(string format, params object?[] args)
    {
        _ = string.Format(format, args);
    }

    public void Error(string format, params object?[] args)
    {
        _ = string.Format(format, args);
    }

    public DateTime ElectTimer() => Active;

    public void SetObserverInternal(bool isObserver) => SetObserver(isObserver);

    public void SetObserverLocked(bool isObserver)
    {
        Observer_ = isObserver;
    }

    public void ProcessAppendEntries(AppendEntry appendEntry)
    {
        ArgumentNullException.ThrowIfNull(appendEntry);
        _lock.EnterWriteLock();
        try
        {
            if (appendEntry.TermV >= Term_)
            {
                Term_ = appendEntry.TermV;
            }

            if (appendEntry.Commit > Commit)
            {
                Commit = appendEntry.Commit;
            }

            if (appendEntry.PIndex > PIndex)
            {
                PIndex = appendEntry.PIndex;
                PTerm = appendEntry.PTerm;
            }

            LeaderId = appendEntry.Leader;
            Interlocked.Exchange(ref HasLeaderV, string.IsNullOrWhiteSpace(LeaderId) ? 0 : 1);
            Active = DateTime.UtcNow;

            if (EntryQ is null)
            {
                EntryQ = new ZB.MOM.NatsNet.Server.Internal.IpQueue<AppendEntry>($"{GroupName}-entry");
            }

            EntryQ.Push(appendEntry);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void RunAsFollower()
    {
        _lock.EnterWriteLock();
        try
        {
            StateValue = (int)RaftState.Follower;
            ResetElectionTimeoutWithLock();
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public void RunAsLeader()
    {
        _lock.EnterWriteLock();
        try
        {
            StateValue = (int)RaftState.Leader;
            Lsut = DateTime.UtcNow;
            Interlocked.Exchange(ref HasLeaderV, 1);
            if (LeadC is null)
            {
                LeadC = Channel.CreateUnbounded<bool>();
            }

            LeadC.Writer.TryWrite(true);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }

    public bool LostQuorum()
    {
        _lock.EnterReadLock();
        try
        {
            return LostQuorumLocked();
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    public bool LostQuorumLocked()
    {
        var expected = Qn > 0 ? Qn : Math.Max(1, (ClusterSize() / 2) + 1);
        var activePeers = 1;
        var now = DateTime.UtcNow;
        foreach (var peer in Peers_.Values)
        {
            if (now - peer.Ts <= TimeSpan.FromSeconds(30))
            {
                activePeers++;
            }
        }

        return activePeers < expected;
    }

    public bool NotActive()
    {
        return DateTime.UtcNow - Active > TimeSpan.FromSeconds(30);
    }

    public AppendEntry? LoadFirstEntry()
    {
        _lock.EnterReadLock();
        try
        {
            if (EntryQ is null || EntryQ.Len() == 0)
            {
                return null;
            }

            var batch = EntryQ.Pop();
            if (batch is not { Length: > 0 })
            {
                return null;
            }

            return batch[0];
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    public void RunCatchup()
    {
        _lock.EnterWriteLock();
        try
        {
            Catchup ??= new CatchupState();
            Catchup.Active = DateTime.UtcNow;
            Catchup.Signal = true;
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }
}

internal sealed record RaftSubscription(string Subject, Action<byte[]?>? Handler);
