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

using System.Security.Cryptography;
using System.Text;
using System.Linq;

namespace ZB.MOM.NatsNet.Server;

public sealed partial class NatsServer
{
    internal byte[] GenerateRouteInitialInfoJSON(string accName, string compression, int poolIdx, byte gossipMode)
    {
        var info = _routeInfo.ShallowClone();
        Span<byte> nonce = stackalloc byte[16];
        RandomNumberGenerator.Fill(nonce);
        info.Nonce = Convert.ToBase64String(nonce);
        info.RouteAccount = string.IsNullOrEmpty(accName) ? null : accName;
        info.RoutePoolIdx = poolIdx;
        info.GossipMode = gossipMode;
        info.Compression = CompressionModeForInfoProtocol(GetOpts().Cluster.Compression, compression);
        return GenerateInfoJson(info);
    }

    internal bool AddRoute(ClientConnection route, bool didSolicit, bool sendDelayedInfo, byte gossipMode, ServerInfo info, string accName)
    {
        _mu.EnterWriteLock();
        try
        {
            route.Route ??= new Route();
            route.Route.RemoteId = info.Id;
            route.Route.DidSolicit = didSolicit;
            route.Route.RemoteName = info.Name;
            if (!string.IsNullOrEmpty(accName))
                route.Route.AccName = Encoding.ASCII.GetBytes(accName);

            if (!_routes.TryGetValue(info.Id, out var pool))
            {
                pool = [];
                _routes[info.Id] = pool;
            }
            else
            {
                foreach (var existing in pool.ToArray())
                {
                    if (!RouteHandler.HandleDuplicateRoute(existing, route))
                        return false;
                    pool.Remove(existing);
                }
            }

            route.Route.PoolIdx = pool.Count;
            pool.Add(route);

            if (sendDelayedInfo)
                route.EnqueueProto(GenerateRouteInitialInfoJSON(accName, info.Compression ?? string.Empty, route.Route.PoolIdx, gossipMode));

            ForwardNewRouteInfoToKnownServers(info, didSolicit ? RouteType.Explicit : RouteType.Implicit, didSolicit, gossipMode);
            return true;
        }
        finally
        {
            _mu.ExitWriteLock();
        }
    }

    internal Exception? StartRouteAcceptLoop()
    {
        if (_routeListener == null)
            return null;

        if (!StartGoRoutine(() => Noticef("Route accept loop started")))
            return new InvalidOperationException("unable to start route accept loop");
        return null;
    }

    internal Exception? SetRouteInfoHostPortAndIP()
    {
        var opts = GetOpts();
        string host;
        int port;
        if (!string.IsNullOrWhiteSpace(opts.Cluster.Advertise))
        {
            var (advHost, advPort, advErr) = Internal.ServerUtilities.ParseHostPort(opts.Cluster.Advertise, opts.Cluster.Port);
            if (advErr != null)
                return new InvalidOperationException($"Cluster.Advertise invalid: {opts.Cluster.Advertise}", advErr);
            host = advHost;
            port = advPort;
        }
        else
        {
            host = opts.Cluster.Host;
            if (string.IsNullOrWhiteSpace(host))
                host = opts.Host;
            port = opts.Cluster.Port;
        }

        _mu.EnterWriteLock();
        try
        {
            _routeInfo.Host = host;
            _routeInfo.Port = port;
            _routeInfo.Ip = $"nats-route://{host}:{port}/";
            return null;
        }
        finally
        {
            _mu.ExitWriteLock();
        }
    }

    public Exception? StartRouting()
    {
        var err = SetRouteInfoHostPortAndIP();
        if (err != null)
            return err;
        SolicitRoutes();
        return StartRouteAcceptLoop();
    }

    internal void ReConnectToRoute(Uri routeUrl, string accName = "")
    {
        StartGoRoutine(() =>
        {
            _ = ConnectToRoute(routeUrl, RouteType.Explicit, false, GossipMode.Default, accName);
        });
    }

    internal bool RouteStillValid(Uri routeUrl)
    {
        var opts = GetOpts();
        return opts.Routes.Any(r => string.Equals(r.Host, routeUrl.Host, StringComparison.OrdinalIgnoreCase) && r.Port == routeUrl.Port);
    }

    internal Exception? ConnectToRoute(Uri routeUrl, RouteType routeType, bool firstConnect, byte gossipMode, string accName)
    {
        _ = firstConnect;
        if (!RouteStillValid(routeUrl))
            return new InvalidOperationException($"route is no longer configured: {routeUrl}");

        SaveRouteTLSName(routeUrl);
        var route = CreateRoute(null, routeUrl, routeType, gossipMode, accName);
        if (route is null)
            return new InvalidOperationException("failed to create route");
        return null;
    }

    internal bool SaveRouteTLSName(Uri routeUrl)
    {
        if (routeUrl is null || string.IsNullOrWhiteSpace(routeUrl.Host))
            return false;

        _mu.EnterWriteLock();
        try
        {
            _routeTlsName = routeUrl.Host;
            return true;
        }
        finally
        {
            _mu.ExitWriteLock();
        }
    }

    internal void SolicitRoutes()
    {
        foreach (var route in GetOpts().Routes)
            _ = ConnectToRoute(route, RouteType.Explicit, true, GossipMode.Default, string.Empty);
    }

    internal void RemoveAllRoutesExcept(string remoteId)
    {
        _mu.EnterWriteLock();
        try
        {
            foreach (var (rid, routes) in _routes.ToArray())
            {
                if (rid == remoteId)
                    continue;

                foreach (var route in routes)
                    route.CloseConnection(ClosedState.RouteRemoved);
                _routes.Remove(rid);
            }
        }
        finally
        {
            _mu.ExitWriteLock();
        }
    }

    internal bool IsDuplicateServerName(string serverName)
    {
        var duplicate = false;
        _mu.EnterReadLock();
        try
        {
            ForEachRoute(route =>
            {
                if (route.Route?.RemoteName == serverName)
                    duplicate = true;
            });
        }
        finally
        {
            _mu.ExitReadLock();
        }
        return duplicate;
    }

    internal void ForEachNonPerAccountRoute(Func<ClientConnection, bool> fn)
    {
        _mu.EnterReadLock();
        try
        {
            foreach (var route in _routes.Values.SelectMany(v => v))
            {
                if (route.Route?.AccName is { Length: > 0 })
                    continue;
                if (!fn(route))
                    break;
            }
        }
        finally
        {
            _mu.ExitReadLock();
        }
    }

    internal void ForEachRouteIdx(int idx, Func<ClientConnection, bool> fn)
    {
        _mu.EnterReadLock();
        try
        {
            foreach (var pool in _routes.Values)
            {
                if (idx < 0 || idx >= pool.Count)
                    continue;
                if (!fn(pool[idx]))
                    break;
            }
        }
        finally
        {
            _mu.ExitReadLock();
        }
    }
}
