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

using System.Text;
using System.Text.Json;

namespace ZB.MOM.NatsNet.Server;

public sealed partial class ClientConnection
{
    internal Exception? SendGatewayConnect()
    {
        if (Server is not NatsServer server)
            return new InvalidOperationException("gateway server unavailable");

        var opts = server.GetOpts();
        var connect = new ConnectInfo
        {
            Echo = true,
            Verbose = false,
            Pedantic = false,
            User = opts.Gateway.Username,
            Pass = opts.Gateway.Password,
            Tls = opts.Gateway.TlsConfig != null,
            Name = server.ID(),
            Headers = server.SupportsHeaders(),
            Gateway = opts.Gateway.Name,
            Dynamic = server.IsClusterNameDynamic(),
        };

        var payload = JsonSerializer.Serialize(connect);
        EnqueueProto(Encoding.ASCII.GetBytes($"CONNECT {payload}\r\n"));
        return null;
    }

    internal Exception? ProcessGatewayConnect(byte[] arg)
    {
        ConnectInfo? connect;
        try
        {
            connect = JsonSerializer.Deserialize<ConnectInfo>(arg);
        }
        catch (Exception ex)
        {
            return ex;
        }

        if (connect == null)
            return new InvalidOperationException("invalid gateway connect payload");

        Gateway ??= new Gateway();
        Gateway.Connected = true;

        if (!string.IsNullOrWhiteSpace(connect.Gateway))
            Gateway.Name = connect.Gateway;
        if (!string.IsNullOrWhiteSpace(connect.Name))
            Gateway.RemoteName = connect.Name;

        Headers = connect.Headers;
        return null;
    }

    internal Exception? ProcessGatewayInfo(byte[] arg)
    {
        ServerInfo? info;
        try
        {
            info = JsonSerializer.Deserialize<ServerInfo>(arg);
        }
        catch (Exception ex)
        {
            return ex;
        }

        if (info == null)
            return new InvalidOperationException("invalid gateway info payload");

        return ProcessGatewayInfo(info);
    }

    internal Exception? ProcessGatewayInfo(ServerInfo info)
    {
        if (Server is not NatsServer server)
            return new InvalidOperationException("gateway server unavailable");

        Gateway ??= new Gateway();

        if (!string.IsNullOrWhiteSpace(info.Gateway))
            Gateway.Name = info.Gateway;

        if (!string.IsNullOrWhiteSpace(info.GatewayUrl) && Uri.TryCreate(info.GatewayUrl, UriKind.Absolute, out var url))
            Gateway.ConnectUrl = url;

        Gateway.UseOldPrefix = info.GatewayNrp;
        Gateway.InterestOnlyMode = info.GatewayIom;

        if (info.GatewayUrls is { Length: > 0 })
            server.ProcessImplicitGateway(Gateway.Name, info.GatewayUrls);

        return null;
    }

    internal Exception? ProcessGatewayAccountUnsub(byte[] arg)
    {
        if (Server is not NatsServer server)
            return new InvalidOperationException("gateway server unavailable");

        var tokens = Encoding.ASCII.GetString(arg).Split(' ', StringSplitOptions.RemoveEmptyEntries);
        if (tokens.Length < 1)
            return null;

        if (tokens.Length == 1)
            return null;

        var accountName = tokens[0];
        var subject = Encoding.ASCII.GetBytes(tokens[1]);
        var queue = tokens.Length > 2 ? Encoding.ASCII.GetBytes(tokens[2]) : null;

        var (account, _) = server.LookupAccount(accountName);
        if (account == null)
            return null;

        var sub = new Internal.Subscription { Subject = subject, Queue = queue };
        server.SendQueueSubOrUnsubToGateways(account, sub, isUnsub: true);
        return null;
    }

    internal Exception? ProcessGatewayAccountSub(byte[] arg)
    {
        if (Server is not NatsServer server)
            return new InvalidOperationException("gateway server unavailable");

        var tokens = Encoding.ASCII.GetString(arg).Split(' ', StringSplitOptions.RemoveEmptyEntries);
        if (tokens.Length < 2)
            return null;

        var accountName = tokens[0];
        var subject = Encoding.ASCII.GetBytes(tokens[1]);
        var queue = tokens.Length > 2 ? Encoding.ASCII.GetBytes(tokens[2]) : null;

        var (account, _) = server.LookupAccount(accountName);
        if (account == null)
            return null;

        var sub = new Internal.Subscription { Subject = subject, Queue = queue };
        if (queue is { Length: > 0 })
            server.SendQueueSubOrUnsubToGateways(account, sub, isUnsub: false);
        else
            server.MaybeSendSubOrUnsubToGateways(account, sub, isUnsub: false);

        return null;
    }

    internal Exception? ProcessGatewayRUnsub(byte[] arg)
    {
        var tokens = Encoding.ASCII.GetString(arg).Split(' ', StringSplitOptions.RemoveEmptyEntries);
        if (tokens.Length < 2 || Server is not NatsServer server)
            return null;

        var accountName = tokens[0];
        var subject = Encoding.ASCII.GetBytes(tokens[1]);
        var queue = tokens.Length > 2 ? Encoding.ASCII.GetBytes(tokens[2]) : null;
        var (account, _) = server.LookupAccount(accountName);
        if (account == null)
            return null;

        var sub = new Internal.Subscription { Subject = subject, Queue = queue };
        server.MaybeSendSubOrUnsubToGateways(account, sub, isUnsub: true);
        return null;
    }

    internal Exception? ProcessGatewayRSub(byte[] arg)
    {
        var tokens = Encoding.ASCII.GetString(arg).Split(' ', StringSplitOptions.RemoveEmptyEntries);
        if (tokens.Length < 2 || Server is not NatsServer server)
            return null;

        var accountName = tokens[0];
        var subject = Encoding.ASCII.GetBytes(tokens[1]);
        var queue = tokens.Length > 2 ? Encoding.ASCII.GetBytes(tokens[2]) : null;
        var (account, _) = server.LookupAccount(accountName);
        if (account == null)
            return null;

        var sub = new Internal.Subscription { Subject = subject, Queue = queue };
        server.MaybeSendSubOrUnsubToGateways(account, sub, isUnsub: false);
        return null;
    }

    internal bool GatewayInterest(Account? account, string subject)
    {
        if (account == null || string.IsNullOrWhiteSpace(subject))
            return false;

        if (Gateway?.OutSim == null)
            return true;

        if (!Gateway.OutSim.TryGetValue(account.Name, out var outSi))
            return true;

        outSi.AcquireReadLock();
        try
        {
            if (outSi.Mode == GatewayInterestMode.InterestOnly && outSi.Sl != null)
            {
                var match = outSi.Sl.Match(subject);
                return match.PSubs.Count > 0 || match.QSubs.Count > 0;
            }

            return outSi.Ni == null || !outSi.Ni.Contains(subject);
        }
        finally
        {
            outSi.ReleaseReadLock();
        }
    }
}
