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

using System.Net;
using System.Text;
using ZB.MOM.NatsNet.Server.Internal;

namespace ZB.MOM.NatsNet.Server;

public sealed partial class NatsServer
{
    internal bool ShouldMapReplyForGatewaySend(byte[] reply, bool useOldPrefix)
    {
        return _gateway.ShouldMapReplyForGatewaySend(reply, useOldPrefix);
    }

    internal IReadOnlyList<ClientConnection> GetInboundGatewayConnections()
    {
        _gateway.AcquireReadLock();
        try { return [.. _gateway.In.Values]; }
        finally { _gateway.ReleaseReadLock(); }
    }

    internal string GatewayAddr()
    {
        var opts = GetOpts();
        var host = string.IsNullOrWhiteSpace(opts.Gateway.Host) ? opts.Host : opts.Gateway.Host;
        return $"{host}:{opts.Gateway.Port}";
    }

    internal void SwitchAccountToInterestMode(string accountName)
    {
        _gateway.AcquireWriteLock();
        try
        {
            foreach (var outbound in _gateway.Out.Values)
            {
                outbound.Gateway ??= new Gateway();
                outbound.Gateway.OutSim ??= new System.Collections.Concurrent.ConcurrentDictionary<string, OutSide>(StringComparer.Ordinal);
                outbound.Gateway.OutSim[accountName] = new OutSide
                {
                    Mode = GatewayInterestMode.InterestOnly,
                    Ni = null,
                };
            }
        }
        finally
        {
            _gateway.ReleaseWriteLock();
        }
    }

    internal void MaybeSendSubOrUnsubToGateways(Account account, Subscription sub, bool isUnsub)
    {
        if (sub.Subject.Length == 0)
            return;

        _gateway.AcquireReadLock();
        try
        {
            foreach (var gateway in _gateway.Out.Values)
                gateway.EnqueueProto(GatewayHandler.GwBuildSubProto(account.Name, sub, isUnsub));
        }
        finally
        {
            _gateway.ReleaseReadLock();
        }
    }

    internal void SendQueueSubOrUnsubToGateways(Account account, Subscription sub, bool isUnsub)
    {
        if (sub.Queue is not { Length: > 0 })
            return;

        MaybeSendSubOrUnsubToGateways(account, sub, isUnsub);
    }

    internal void GatewayHandleAccountNoInterest(ClientConnection connection, byte[] accountName)
    {
        lock (_gateway.PasiLock)
        {
            var key = Encoding.ASCII.GetString(accountName);
            if (_gateway.Pasi.TryGetValue(key, out var accountInterest) && accountInterest.Count > 0)
                return;

            connection.SendAccountUnsubToGateway(accountName);
        }
    }

    internal void GatewayHandleSubjectNoInterest(ClientConnection connection, Account account, byte[] accountName, byte[] subject)
    {
        lock (_gateway.PasiLock)
        {
            var hasSubs = (account.Sublist?.Count() ?? 0) > 0 || account.ServiceImportReply != null;
            if (!hasSubs)
            {
                connection.SendAccountUnsubToGateway(Encoding.ASCII.GetBytes(account.Name));
                return;
            }

            var sendProto = false;
            lock (connection)
            {
                connection.Gateway ??= new Gateway();
                connection.Gateway.InSim ??= new Dictionary<string, InSide>(StringComparer.Ordinal);

                var accountKey = Encoding.ASCII.GetString(accountName);
                if (!connection.Gateway.InSim.TryGetValue(accountKey, out var inSide) || inSide == null)
                {
                    inSide = new InSide
                    {
                        Ni = new HashSet<string>(StringComparer.Ordinal),
                    };
                    inSide.Ni.Add(Encoding.ASCII.GetString(subject));
                    connection.Gateway.InSim[accountKey] = inSide;
                    sendProto = true;
                }
                else if (inSide.Ni != null)
                {
                    var subjectKey = Encoding.ASCII.GetString(subject);
                    if (!inSide.Ni.Contains(subjectKey))
                    {
                        if (inSide.Ni.Count >= GatewayHandler.GatewayMaxRUnsubBeforeSwitch)
                        {
                            connection.GatewaySwitchAccountToSendAllSubs(inSide, accountKey);
                        }
                        else
                        {
                            inSide.Ni.Add(subjectKey);
                            sendProto = true;
                        }
                    }
                }
            }

            if (!sendProto)
                return;

            var protocol = Encoding.ASCII.GetBytes($"RS- {Encoding.ASCII.GetString(accountName)} {Encoding.ASCII.GetString(subject)}\r\n");
            connection.EnqueueProto(protocol);
            if (connection.Trace)
                connection.TraceOutOp(string.Empty, protocol.AsSpan(0, protocol.Length - 2).ToArray());
        }
    }
}
