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

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

namespace ZB.MOM.NatsNet.Server;

public sealed partial class NatsServer
{
    internal void GossipGatewaysToInboundGateway(ClientConnection inboundGateway)
    {
        if (_gateway.Info == null)
            return;

        var info = _gateway.Info.ShallowClone();
        info.GatewayCmd = 1;
        info.GatewayUrls = [.. _gateway.Urls.GetAsStringSlice()];
        inboundGateway.EnqueueProto(GenerateInfoJson(info));
    }

    internal void ForwardNewGatewayToLocalCluster(string gatewayName, IReadOnlyCollection<string> gatewayUrls)
    {
        if (gatewayUrls.Count == 0)
            return;

        var info = _routeInfo.ShallowClone();
        info.Gateway = gatewayName;
        info.GatewayUrls = [.. gatewayUrls];

        var proto = GenerateInfoJson(info);

        _mu.EnterReadLock();
        try
        {
            ForEachRoute(route => route.EnqueueProto(proto));
            foreach (var inbound in _gateway.In.Values)
                inbound.EnqueueProto(proto);
        }
        finally
        {
            _mu.ExitReadLock();
        }
    }

    internal void SendQueueSubsToGateway(ClientConnection gateway, string accountName)
    {
        SendAccountSubsToGateway(gateway, accountName, sendQueueOnly: true);
    }

    internal void SendAccountSubsToGateway(ClientConnection gateway, string accountName, bool sendQueueOnly = false)
    {
        var (account, _) = LookupAccount(accountName);
        if (account == null)
            return;

        SendSubsToGateway(gateway, account, sendQueueOnly);
    }

    internal static byte[] GwBuildSubProto(string accountName, Subscription subscription, bool isUnsub = false)
    {
        var op = isUnsub ? "RS-" : "RS+";
        var subject = Encoding.ASCII.GetString(subscription.Subject);
        if (subscription.Queue is { Length: > 0 } queue)
            return Encoding.ASCII.GetBytes($"{op} {accountName} {subject} {Encoding.ASCII.GetString(queue)}\r\n");

        return Encoding.ASCII.GetBytes($"{op} {accountName} {subject}\r\n");
    }

    internal void SendSubsToGateway(ClientConnection gateway, Account account, bool sendQueueOnly)
    {
        if (account.Sublist == null)
            return;

        var subs = new List<Subscription>();
        account.Sublist.All(subs);
        foreach (var sub in subs)
        {
            if (sendQueueOnly && sub.Queue is not { Length: > 0 })
                continue;

            gateway.EnqueueProto(GwBuildSubProto(account.Name, sub));
        }
    }

    internal void ProcessGatewayInfoFromRoute(string gatewayName, IReadOnlyCollection<string> gatewayUrls)
    {
        ProcessImplicitGateway(gatewayName, gatewayUrls);
    }

    internal void SendGatewayConfigsToRoute(ClientConnection route)
    {
        _gateway.AcquireReadLock();
        try
        {
            foreach (var (name, cfg) in _gateway.Remotes)
            {
                var info = _routeInfo.ShallowClone();
                info.Gateway = name;
                info.GatewayUrls = cfg.GetUrlsAsStrings();
                route.EnqueueProto(GenerateInfoJson(info));
            }
        }
        finally
        {
            _gateway.ReleaseReadLock();
        }
    }

    internal void ProcessImplicitGateway(string gatewayName, IReadOnlyCollection<string> gatewayUrls)
    {
        if (string.IsNullOrWhiteSpace(gatewayName) || gatewayUrls.Count == 0)
            return;

        _gateway.AcquireWriteLock();
        try
        {
            if (!_gateway.Remotes.TryGetValue(gatewayName, out var cfg))
            {
                cfg = new GatewayCfg
                {
                    RemoteOpts = new RemoteGatewayOpts { Name = gatewayName },
                    Hash = GatewayHandler.GetGWHash(gatewayName),
                    OldHash = GatewayHandler.GetOldHash(gatewayName),
                    Implicit = true,
                    Urls = new Dictionary<string, Uri>(StringComparer.Ordinal),
                };
                _gateway.Remotes[gatewayName] = cfg;
            }

            var urls = gatewayUrls
                .Select(url => Uri.TryCreate(url, UriKind.Absolute, out var uri) ? uri : null)
                .Where(uri => uri != null)
                .Cast<Uri>()
                .ToList();

            cfg.AddUrls(urls);
            cfg.VarzUpdateUrls = true;
        }
        finally
        {
            _gateway.ReleaseWriteLock();
        }
    }

    internal void AddGatewayURL(string gatewayName, string gatewayUrl)
    {
        if (string.IsNullOrWhiteSpace(gatewayUrl))
            return;

        _gateway.AcquireWriteLock();
        try
        {
            _gateway.Urls.AddUrl(gatewayUrl);
            if (_gateway.Remotes.TryGetValue(gatewayName, out var cfg) && Uri.TryCreate(gatewayUrl, UriKind.Absolute, out var url))
                cfg.AddUrls([url]);
        }
        finally
        {
            _gateway.ReleaseWriteLock();
        }
    }

    internal void RemoveGatewayURL(string gatewayName, string gatewayUrl)
    {
        if (string.IsNullOrWhiteSpace(gatewayUrl))
            return;

        _gateway.AcquireWriteLock();
        try
        {
            _gateway.Urls.RemoveUrl(gatewayUrl);

            if (_gateway.Remotes.TryGetValue(gatewayName, out var cfg))
            {
                cfg.AcquireWriteLock();
                try
                {
                    cfg.Urls.Remove(gatewayUrl);
                    cfg.VarzUpdateUrls = true;
                }
                finally
                {
                    cfg.ReleaseWriteLock();
                }
            }
        }
        finally
        {
            _gateway.ReleaseWriteLock();
        }
    }

    internal void SendAsyncGatewayInfo()
    {
        byte[] infoProto;
        _gateway.AcquireReadLock();
        try
        {
            infoProto = _gateway.InfoJson is { Length: > 0 } proto
                ? [.. proto]
                : _gateway.GenerateInfoJSON();
        }
        finally
        {
            _gateway.ReleaseReadLock();
        }

        _mu.EnterReadLock();
        try
        {
            foreach (var route in _routes.Values.SelectMany(v => v))
                route.EnqueueProto(infoProto);

            foreach (var inbound in _gateway.In.Values)
                inbound.EnqueueProto(infoProto);
        }
        finally
        {
            _mu.ExitReadLock();
        }
    }

    internal string GetGatewayURL()
    {
        _gateway.AcquireReadLock();
        try { return _gateway.Url; }
        finally { _gateway.ReleaseReadLock(); }
    }

    internal string GetGatewayName()
    {
        _gateway.AcquireReadLock();
        try { return _gateway.Name; }
        finally { _gateway.ReleaseReadLock(); }
    }

    internal void RegisterInboundGatewayConnection(ClientConnection connection)
    {
        _gateway.AcquireWriteLock();
        try { _gateway.In[connection.Cid] = connection; }
        finally { _gateway.ReleaseWriteLock(); }
    }

    internal void RegisterOutboundGatewayConnection(string gatewayName, ClientConnection connection)
    {
        _gateway.AcquireWriteLock();
        try
        {
            _gateway.Out[gatewayName] = connection;
            _gateway.OrderOutboundConnectionsLocked();
        }
        finally
        {
            _gateway.ReleaseWriteLock();
        }
    }

    internal ClientConnection? GetOutboundGatewayConnection(string gatewayName)
    {
        _gateway.AcquireReadLock();
        try { return _gateway.Out.GetValueOrDefault(gatewayName); }
        finally { _gateway.ReleaseReadLock(); }
    }

    internal IReadOnlyList<ClientConnection> GetOutboundGatewayConnections()
    {
        _gateway.AcquireReadLock();
        try { return [.. _gateway.Outo]; }
        finally { _gateway.ReleaseReadLock(); }
    }

    public int NumOutboundGateways() => NumOutboundGatewaysInternal();

    internal int NumOutboundGatewaysInternal()
    {
        _gateway.AcquireReadLock();
        try { return _gateway.Out.Count; }
        finally { _gateway.ReleaseReadLock(); }
    }

    internal int NumInboundGateways()
    {
        _gateway.AcquireReadLock();
        try { return _gateway.In.Count; }
        finally { _gateway.ReleaseReadLock(); }
    }

    internal GatewayCfg? GetRemoteGateway(string gatewayName)
    {
        _gateway.AcquireReadLock();
        try { return _gateway.Remotes.GetValueOrDefault(gatewayName); }
        finally { _gateway.ReleaseReadLock(); }
    }
}
