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

using System.Text.Json;
using ZB.MOM.NatsNet.Server.Auth;
using ZB.MOM.NatsNet.Server.Internal;

namespace ZB.MOM.NatsNet.Server;

public sealed partial class NatsServer
{
    internal bool NegotiateRouteCompression(
        ClientConnection c,
        bool didSolicit,
        string accName,
        string infoCompression,
        ServerOptions opts)
    {
        var mode = SelectCompressionMode(opts.Cluster.Compression.Mode, infoCompression);

        lock (c)
        {
            c.Route ??= new Route();
            if (mode == CompressionMode.S2Auto)
            {
                if (c.Rtt == TimeSpan.Zero)
                    c.Rtt = ClientConnection.ComputeRtt(c.Start);
                mode = SelectS2AutoModeBasedOnRtt(c.Rtt, opts.Cluster.Compression.RttThresholds);
            }
            c.Route.Compression = mode;
        }

        if (!NeedsCompression(mode))
            return false;

        var info = CopyInfo();
        info.Compression = CompressionModeForInfoProtocol(opts.Cluster.Compression, mode);
        if (!string.IsNullOrEmpty(accName))
            info.RouteAccount = accName;
        var proto = GenerateInfoJson(info);

        lock (c)
        {
            if (didSolicit)
                c.EnqueueProto(proto);
            else
                c.EnqueueProto(proto);
            c.SetFirstPingTimer();
        }

        return true;
    }

    internal void UpdateRemoteRoutePerms(ClientConnection c, ServerInfo info)
    {
        SubjectPermission? oldExport;
        lock (c)
        {
            oldExport = c.Opts.Export?.Clone();
            c.Opts.Import = info.Import?.Clone();
            c.Opts.Export = info.Export?.Clone();
        }

        // Build old/new export checkers to preserve route permission semantics.
        var oldTester = new ClientConnection(ClientKind.Router) { Route = new Route() };
        oldTester.SetRoutePermissions(new RoutePermissions { Export = oldExport });
        var newTester = new ClientConnection(ClientKind.Router) { Route = new Route() };
        newTester.SetRoutePermissions(new RoutePermissions { Export = info.Export?.Clone() });

        if (oldExport is null && info.Export is null)
            return;

        // Subscription fanout wiring for route permission delta is completed in group 2.
        _ = oldTester;
        _ = newTester;
    }

    internal void ProcessImplicitRoute(ServerInfo info, bool routeNoPool)
    {
        var remoteId = info.Id;
        _mu.EnterWriteLock();
        try
        {
            if (remoteId == _info.Id)
                return;

            var opts = GetOpts();
            if (!string.IsNullOrEmpty(info.RouteAccount))
            {
                if (opts.Cluster.PoolSize <= 0)
                    return;
                if (_accRoutes != null
                    && _accRoutes.TryGetValue(info.RouteAccount, out var remotes)
                    && remotes.TryGetValue(remoteId, out var existing)
                    && existing != null)
                {
                    return;
                }
            }
            else if (_routes.ContainsKey(remoteId))
            {
                return;
            }

            if (HasThisRouteConfigured(info))
                return;
        }
        finally
        {
            _mu.ExitWriteLock();
        }

        if (!Uri.TryCreate(info.Ip, UriKind.Absolute, out var _))
            return;

        if (routeNoPool && string.IsNullOrEmpty(info.RouteAccount))
            Debugf("Implicit route from non-pooling remote {0} processed", info.Id);
    }

    internal bool HasThisRouteConfigured(ServerInfo info)
    {
        var routes = GetOpts().Routes;
        if (routes.Count == 0)
            return false;

        var infoPort = info.Port <= 0 ? 6222 : info.Port;
        var primary = $"{info.Host}:{infoPort}".ToLowerInvariant();

        var secondary = string.Empty;
        if (!string.IsNullOrWhiteSpace(info.Ip) && Uri.TryCreate(info.Ip, UriKind.Absolute, out var infoUri))
        {
            var host = infoUri.Host;
            if (!string.IsNullOrWhiteSpace(host))
                secondary = $"{host}:{infoPort}".ToLowerInvariant();
        }

        foreach (var route in routes)
        {
            var routePort = route.IsDefaultPort ? infoPort : route.Port;
            var hostPort = $"{route.Host}:{routePort}".ToLowerInvariant();
            if (hostPort == primary || (!string.IsNullOrEmpty(secondary) && hostPort == secondary))
                return true;
        }

        return false;
    }

    internal void ForwardNewRouteInfoToKnownServers(ServerInfo info, RouteType routeType, bool didSolicit, byte localGossipMode)
    {
        var fromGossip = didSolicit && routeType == RouteType.Implicit;
        if ((fromGossip && localGossipMode != GossipMode.Override) || info.GossipMode == GossipMode.Disabled)
            return;

        info.Nonce = string.Empty;

        byte[] BuildInfo(byte gossipMode)
        {
            info.GossipMode = gossipMode;
            return GenerateInfoJson(info);
        }

        byte[]? infoDefault = null;
        byte[]? infoDisabled = null;
        byte[]? infoOverride = null;

        byte[] SelectInfo(ClientConnection route)
        {
            var rType = route.Route?.RouteType ?? RouteType.Implicit;
            if ((!didSolicit && rType == RouteType.Explicit) || (didSolicit && routeType == RouteType.Explicit))
                return infoOverride ??= BuildInfo(GossipMode.Override);
            if (!didSolicit)
                return infoDisabled ??= BuildInfo(GossipMode.Disabled);
            return infoDefault ??= BuildInfo(GossipMode.Default);
        }

        ForEachRemote(route =>
        {
            if (route.Route?.RemoteId == info.Id)
                return;
            route.EnqueueProto(SelectInfo(route));
        });
    }
}
