using NATS.Server.Subscriptions;

namespace NATS.Server.Auth;

public sealed class ClientPermissions : IDisposable
{
    private readonly PermissionSet? _publish;
    private readonly PermissionSet? _subscribe;
    private readonly ResponseTracker? _responseTracker;
    private readonly PermissionLruCache _pubCache = new(128);

    private ClientPermissions(PermissionSet? publish, PermissionSet? subscribe, ResponseTracker? responseTracker)
    {
        _publish = publish;
        _subscribe = subscribe;
        _responseTracker = responseTracker;
    }

    public static ClientPermissions? Build(Permissions? permissions)
    {
        if (permissions == null)
            return null;

        var pub = PermissionSet.Build(permissions.Publish);
        var sub = PermissionSet.Build(permissions.Subscribe);
        ResponseTracker? responseTracker = null;
        if (permissions.Response != null)
            responseTracker = new ResponseTracker(permissions.Response.MaxMsgs, permissions.Response.Expires);

        if (pub == null && sub == null && responseTracker == null)
            return null;

        return new ClientPermissions(pub, sub, responseTracker);
    }

    public ResponseTracker? ResponseTracker => _responseTracker;

    public bool IsPublishAllowed(string subject)
    {
        if (_publish == null)
            return true;

        if (_pubCache.TryGet(subject, out var cached))
            return cached;

        var allowed = _publish.IsAllowed(subject);

        // If denied but response tracking is enabled, check reply table
        if (!allowed && _responseTracker != null)
        {
            if (_responseTracker.IsReplyAllowed(subject))
                return true; // Don't cache dynamic reply permissions
        }

        _pubCache.Set(subject, allowed);
        return allowed;
    }

    public bool IsSubscribeAllowed(string subject, string? queue = null)
    {
        if (_subscribe == null)
            return true;
        if (!_subscribe.IsAllowed(subject))
            return false;
        if (queue != null && _subscribe.IsDenied(queue))
            return false;
        return true;
    }

    public bool IsDeliveryAllowed(string subject)
    {
        if (_subscribe == null)
            return true;
        return _subscribe.IsDeliveryAllowed(subject);
    }

    public void Dispose()
    {
        _publish?.Dispose();
        _subscribe?.Dispose();
    }
}

public sealed class PermissionSet : IDisposable
{
    private readonly SubList? _allow;
    private readonly SubList? _deny;

    private PermissionSet(SubList? allow, SubList? deny)
    {
        _allow = allow;
        _deny = deny;
    }

    public static PermissionSet? Build(SubjectPermission? permission)
    {
        if (permission == null)
            return null;

        bool hasAllow = permission.Allow is { Count: > 0 };
        bool hasDeny = permission.Deny is { Count: > 0 };

        if (!hasAllow && !hasDeny)
            return null;

        SubList? allow = null;
        SubList? deny = null;

        if (hasAllow)
        {
            allow = new SubList();
            foreach (var subject in permission.Allow!)
                allow.Insert(new Subscription { Subject = subject, Sid = "_perm_" });
        }

        if (hasDeny)
        {
            deny = new SubList();
            foreach (var subject in permission.Deny!)
                deny.Insert(new Subscription { Subject = subject, Sid = "_perm_" });
        }

        return new PermissionSet(allow, deny);
    }

    public bool IsAllowed(string subject)
    {
        bool allowed = true;

        if (_allow != null)
        {
            var result = _allow.Match(subject);
            allowed = result.PlainSubs.Length > 0 || result.QueueSubs.Length > 0;
        }

        if (allowed && _deny != null)
        {
            var result = _deny.Match(subject);
            allowed = result.PlainSubs.Length == 0 && result.QueueSubs.Length == 0;
        }

        return allowed;
    }

    public bool IsDenied(string subject)
    {
        if (_deny == null) return false;
        var result = _deny.Match(subject);
        return result.PlainSubs.Length > 0 || result.QueueSubs.Length > 0;
    }

    public bool IsDeliveryAllowed(string subject)
    {
        if (_deny == null)
            return true;
        var result = _deny.Match(subject);
        return result.PlainSubs.Length == 0 && result.QueueSubs.Length == 0;
    }

    public void Dispose()
    {
        _allow?.Dispose();
        _deny?.Dispose();
    }
}
