using NATS.Server.Subscriptions;

namespace NATS.Server.LeafNodes;

public enum LeafMapDirection
{
    Inbound,
    Outbound,
}

public sealed record LeafMappingResult(string Account, string Subject);

/// <summary>
/// Maps accounts between hub and spoke, and applies subject-level export/import
/// filtering on leaf connections. Supports both allow-lists and deny-lists:
///
/// - <b>ExportSubjects</b> (allow) + <b>DenyExports</b> (deny): controls hub→leaf flow.
/// - <b>ImportSubjects</b> (allow) + <b>DenyImports</b> (deny): controls leaf→hub flow.
///
/// When an allow-list is non-empty, a subject must match at least one allow pattern.
/// A subject matching any deny pattern is always rejected (deny takes precedence).
///
/// Go reference: leafnode.go:470-507 (newLeafNodeCfg), opts.go:230-231,
/// auth.go:127 (SubjectPermission with Allow + Deny).
/// </summary>
public sealed class LeafHubSpokeMapper
{
    private readonly IReadOnlyDictionary<string, string> _hubToSpoke;
    private readonly IReadOnlyDictionary<string, string> _spokeToHub;
    private readonly IReadOnlyList<string> _denyExports;
    private readonly IReadOnlyList<string> _denyImports;
    private readonly IReadOnlyList<string> _allowExports;
    private readonly IReadOnlyList<string> _allowImports;

    public LeafHubSpokeMapper(IReadOnlyDictionary<string, string> hubToSpoke)
        : this(hubToSpoke, [], [], [], [])
    {
    }

    /// <summary>
    /// Creates a mapper with account mapping and subject deny filters (legacy constructor).
    /// </summary>
    public LeafHubSpokeMapper(
        IReadOnlyDictionary<string, string> hubToSpoke,
        IReadOnlyList<string> denyExports,
        IReadOnlyList<string> denyImports)
        : this(hubToSpoke, denyExports, denyImports, [], [])
    {
    }

    /// <summary>
    /// Creates a mapper with account mapping, deny filters, and allow-list filters.
    /// </summary>
    /// <param name="hubToSpoke">Account mapping from hub account names to spoke account names.</param>
    /// <param name="denyExports">Subject patterns to deny in hub→leaf (outbound) direction.</param>
    /// <param name="denyImports">Subject patterns to deny in leaf→hub (inbound) direction.</param>
    /// <param name="allowExports">Subject patterns to allow in hub→leaf (outbound) direction. Empty = allow all.</param>
    /// <param name="allowImports">Subject patterns to allow in leaf→hub (inbound) direction. Empty = allow all.</param>
    public LeafHubSpokeMapper(
        IReadOnlyDictionary<string, string> hubToSpoke,
        IReadOnlyList<string> denyExports,
        IReadOnlyList<string> denyImports,
        IReadOnlyList<string> allowExports,
        IReadOnlyList<string> allowImports)
    {
        _hubToSpoke = hubToSpoke;
        _spokeToHub = hubToSpoke.ToDictionary(static p => p.Value, static p => p.Key, StringComparer.Ordinal);
        _denyExports = denyExports;
        _denyImports = denyImports;
        _allowExports = allowExports;
        _allowImports = allowImports;
    }

    /// <summary>
    /// Maps an account from hub→spoke or spoke→hub based on direction.
    /// </summary>
    public LeafMappingResult Map(string account, string subject, LeafMapDirection direction)
    {
        if (direction == LeafMapDirection.Outbound && _hubToSpoke.TryGetValue(account, out var spoke))
            return new LeafMappingResult(spoke, subject);
        if (direction == LeafMapDirection.Inbound && _spokeToHub.TryGetValue(account, out var hub))
            return new LeafMappingResult(hub, subject);
        return new LeafMappingResult(account, subject);
    }

    /// <summary>
    /// Returns true if the subject is allowed to flow in the given direction.
    /// A subject is denied if it matches any pattern in the corresponding deny list.
    /// When an allow-list is set, the subject must also match at least one allow pattern.
    /// Deny takes precedence over allow (Go reference: auth.go SubjectPermission semantics).
    /// </summary>
    public bool IsSubjectAllowed(string subject, LeafMapDirection direction)
    {
        var (denyList, allowList) = direction switch
        {
            LeafMapDirection.Outbound => (_denyExports, _allowExports),
            LeafMapDirection.Inbound => (_denyImports, _allowImports),
            _ => ((IReadOnlyList<string>)[], (IReadOnlyList<string>)[]),
        };

        // Deny takes precedence: if subject matches any deny pattern, reject it.
        for (var i = 0; i < denyList.Count; i++)
        {
            if (SubjectMatch.MatchLiteral(subject, denyList[i]))
                return false;
        }

        // If allow-list is empty, everything not denied is allowed.
        if (allowList.Count == 0)
            return true;

        // With a non-empty allow-list, subject must match at least one allow pattern.
        for (var i = 0; i < allowList.Count; i++)
        {
            if (SubjectMatch.MatchLiteral(subject, allowList[i]))
                return true;
        }

        return false;
    }
}
