// Copyright 2019-2026 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");

using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Linq;
using ZB.MOM.NatsNet.Server.Internal;

namespace ZB.MOM.NatsNet.Server;

public sealed partial class NatsServer
{
    internal void SolicitLeafNodeRemotes(IReadOnlyList<RemoteLeafOpts> remotes)
    {
        foreach (var remote in remotes)
        {
            var cfg = LeafNodeHandler.NewLeafNodeCfg(remote);
            _mu.EnterWriteLock();
            try
            {
                _leafRemoteCfgs.Add(cfg);
            }
            finally
            {
                _mu.ExitWriteLock();
            }

            if (!remote.Disabled)
                _ = ConnectToRemoteLeafNode(cfg, firstConnect: true);
        }
    }

    internal bool RemoteLeafNodeStillValid(LeafNodeCfg remote)
    {
        if (remote.Disabled)
            return false;

        var opts = GetOpts();
        var current = remote.RemoteOpts;
        if (current is null)
            return false;

        foreach (var candidate in opts.LeafNode.Remotes)
        {
            if (candidate.Urls.Count != current.Urls.Count)
                continue;

            var allMatch = true;
            for (var i = 0; i < candidate.Urls.Count; i++)
            {
                if (!ServerUtilities.UrlsAreEqual(candidate.Urls[i], current.Urls[i]))
                {
                    allMatch = false;
                    break;
                }
            }

            if (allMatch)
                return true;
        }

        return false;
    }

    internal void UpdateRemoteLeafNodesTLSConfig(SslServerAuthenticationOptions? tlsConfig)
    {
        _mu.EnterWriteLock();
        try
        {
            foreach (var cfg in _leafRemoteCfgs)
            {
                if (cfg.RemoteOpts is null)
                    continue;
                cfg.RemoteOpts.TlsConfig = tlsConfig;
            }
        }
        finally
        {
            _mu.ExitWriteLock();
        }
    }

    internal void ReConnectToRemoteLeafNode(LeafNodeCfg remote)
    {
        _ = StartGoRoutine(() =>
        {
            _ = ConnectToRemoteLeafNode(remote, firstConnect: false);
        });
    }

    internal void SetLeafNodeNonExportedOptions(LeafNodeCfg remote)
    {
        if (remote.RemoteOpts is null)
            return;

        if (remote.RemoteOpts.FirstInfoTimeout <= TimeSpan.Zero)
            remote.RemoteOpts.FirstInfoTimeout = ServerConstants.DefaultLeafNodeInfoWait;

        if (remote.RemoteOpts.TlsTimeout <= 0)
            remote.RemoteOpts.TlsTimeout = ServerConstants.TlsTimeout.TotalSeconds;
    }

    internal Exception? ConnectToRemoteLeafNode(LeafNodeCfg remote, bool firstConnect)
    {
        _ = firstConnect;
        if (!RemoteLeafNodeStillValid(remote))
            return new InvalidOperationException("leafnode remote is no longer configured");

        if (IsLeafConnectDisabled())
            return ServerErrors.ErrLeafNodeDisabled;

        SetLeafNodeNonExportedOptions(remote);
        var nextUrl = remote.PickNextURL();
        if (nextUrl is null)
            return new InvalidOperationException("leafnode remote has no URLs configured");

        remote.SaveTLSHostname(nextUrl);
        remote.SaveUserPassword(nextUrl);

        return null;
    }

    internal void ClearObserverState(LeafNodeCfg remote)
    {
        remote.CancelMigrateTimer();
    }

    internal void CheckJetStreamMigrate(LeafNodeCfg remote)
    {
        if (remote.RemoteOpts is null || !remote.RemoteOpts.JetStreamClusterMigrate)
            return;

        var delay = remote.RemoteOpts.JetStreamClusterMigrateDelay;
        if (delay <= TimeSpan.Zero)
            return;

        remote.AcquireWriteLock();
        try
        {
            remote.JsMigrateTimer?.Dispose();
            remote.JsMigrateTimer = new Timer(_ => remote.CancelMigrateTimer(), null, delay, Timeout.InfiniteTimeSpan);
        }
        finally
        {
            remote.ReleaseWriteLock();
        }
    }

    internal bool IsLeafConnectDisabled() => _leafDisableConnect;

    internal Exception? StartLeafNodeAcceptLoop()
    {
        if (_leafNodeListener is null)
            return null;

        if (!StartGoRoutine(() => Noticef("Leafnode accept loop started")))
            return new InvalidOperationException("unable to start leafnode accept loop");

        return null;
    }

    internal ServerInfo CopyLeafNodeInfo()
    {
        _mu.EnterReadLock();
        try
        {
            return _leafNodeInfo.ShallowClone();
        }
        finally
        {
            _mu.ExitReadLock();
        }
    }

    internal void AddLeafNodeURL(string url)
    {
        if (string.IsNullOrWhiteSpace(url))
            return;

        _mu.EnterWriteLock();
        try
        {
            _leafNodeInfo.LeafNodeUrls ??= [];
            if (!_leafNodeInfo.LeafNodeUrls.Contains(url, StringComparer.Ordinal))
                _leafNodeInfo.LeafNodeUrls = [.. _leafNodeInfo.LeafNodeUrls, url];
        }
        finally
        {
            _mu.ExitWriteLock();
        }

        GenerateLeafNodeInfoJSON();
    }

    internal void RemoveLeafNodeURL(string url)
    {
        if (string.IsNullOrWhiteSpace(url))
            return;

        _mu.EnterWriteLock();
        try
        {
            if (_leafNodeInfo.LeafNodeUrls is not { Length: > 0 })
                return;

            _leafNodeInfo.LeafNodeUrls = [.. _leafNodeInfo.LeafNodeUrls.Where(u => !string.Equals(u, url, StringComparison.Ordinal))];
        }
        finally
        {
            _mu.ExitWriteLock();
        }

        GenerateLeafNodeInfoJSON();
    }

    internal byte[] GenerateLeafNodeInfoJSON()
    {
        _mu.EnterWriteLock();
        try
        {
            _leafNodeInfoJson = GenerateInfoJson(_leafNodeInfo);
            return _leafNodeInfoJson;
        }
        finally
        {
            _mu.ExitWriteLock();
        }
    }

    internal void SendAsyncLeafNodeInfo()
    {
        byte[] info;
        _mu.EnterReadLock();
        try
        {
            info = _leafNodeInfoJson.Length == 0 ? GenerateInfoJson(_leafNodeInfo) : [.. _leafNodeInfoJson];
        }
        finally
        {
            _mu.ExitReadLock();
        }

        _mu.EnterReadLock();
        try
        {
            foreach (var leaf in _leafs.Values)
                leaf.EnqueueProto(info);
        }
        finally
        {
            _mu.ExitReadLock();
        }
    }

    internal ClientConnection? CreateLeafNode(Stream connection, Uri? remoteUrl, LeafNodeCfg? remote, object? ws)
    {
        _ = remoteUrl;
        _ = ws;

        var now = DateTime.UtcNow;
        var leaf = new ClientConnection(ClientKind.Leaf, this, connection)
        {
            Start = now,
            Last = now,
            Opts = ClientOptions.Default,
            Leaf = new Leaf
            {
                Remote = remote,
                Smap = new Dictionary<string, int>(StringComparer.Ordinal),
            },
        };

        AddLeafNodeConnection(leaf, string.Empty, string.Empty, checkForDup: false);
        return leaf;
    }

    internal Exception? NegotiateLeafCompression(ClientConnection connection, bool didSolicit, string infoCompression, CompressionOpts options)
    {
        _ = didSolicit;
        var mode = string.IsNullOrWhiteSpace(infoCompression) ? options.Mode : infoCompression;
        if (string.IsNullOrWhiteSpace(mode))
            mode = CompressionModes.Off;

        lock (connection)
        {
            connection.Leaf ??= new Leaf();
            connection.Leaf.Compression = mode;
        }

        return null;
    }

    internal Exception? SetLeafNodeInfoHostPortAndIP()
    {
        var opts = GetOpts();
        string host;
        int port;

        if (!string.IsNullOrWhiteSpace(opts.LeafNode.Advertise))
        {
            var (advHost, advPort, advErr) = ServerUtilities.ParseHostPort(opts.LeafNode.Advertise, opts.LeafNode.Port);
            if (advErr != null)
                return advErr;

            host = advHost;
            port = advPort;
        }
        else
        {
            host = string.IsNullOrWhiteSpace(opts.LeafNode.Host) ? opts.Host : opts.LeafNode.Host;
            port = opts.LeafNode.Port;
        }

        _mu.EnterWriteLock();
        try
        {
            _leafNodeInfo.Host = host;
            _leafNodeInfo.Port = port;
            _leafNodeInfo.Ip = $"{host}:{port}";
        }
        finally
        {
            _mu.ExitWriteLock();
        }

        return null;
    }

    internal void AddLeafNodeConnection(ClientConnection connection, string srvName, string clusterName, bool checkForDup)
    {
        _ = srvName;
        _ = clusterName;
        _ = checkForDup;

        _mu.EnterWriteLock();
        try
        {
            _leafs[connection.Cid] = connection;
            _clients[connection.Cid] = connection;
        }
        finally
        {
            _mu.ExitWriteLock();
        }
    }

    internal void CheckInternalSyncConsumers(Account? account)
    {
        _ = account;
    }

    internal void SendPermsAndAccountInfo(ClientConnection connection)
    {
        var info = CopyLeafNodeInfo();
        lock (connection)
        {
            info.Cid = connection.Cid;
            info.Import = connection.Opts.Import;
            info.Export = connection.Opts.Export;
            info.RemoteAccount = connection.GetAccount()?.Name;
            info.IsSystemAccount = ReferenceEquals(connection.GetAccount(), SystemAccount());
            info.ConnectInfo = true;
            connection.EnqueueProto(GenerateInfoJson(info));
        }
    }

    internal void InitLeafNodeSmapAndSendSubs(ClientConnection connection)
    {
        lock (connection)
        {
            connection.Leaf ??= new Leaf();
            connection.Leaf.Smap ??= new Dictionary<string, int>(StringComparer.Ordinal);

            foreach (var sub in connection.Subs.Values)
            {
                var key = LeafNodeHandler.KeyFromSub(sub);
                connection.Leaf.Smap[key] = sub.Queue is { Length: > 0 } ? Math.Max(sub.Qw, 1) : 1;
                connection.SendLeafNodeSubUpdate(key, connection.Leaf.Smap[key]);
            }
        }
    }

    internal void UpdateInterestForAccountOnGateway(string accountName, Internal.Subscription sub, int delta)
    {
        GatewayUpdateSubInterest(accountName, sub, delta);
    }

    internal void LeafNodeResumeConnectProcess(ClientConnection connection)
    {
        if (connection.Server is not NatsServer server)
            return;

        var clusterName = server.CachedClusterName();
        _ = connection.SendLeafConnect(clusterName, server.SupportsHeaders());
    }

    internal void LeafNodeFinishConnectProcess(ClientConnection connection)
    {
        AddLeafNodeConnection(connection, string.Empty, string.Empty, checkForDup: false);
        InitLeafNodeSmapAndSendSubs(connection);
    }
}
