using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;

namespace ZB.MOM.WW.ScadaBridge.Security;

public class RoleMapper
{
    private readonly ISecurityRepository _securityRepository;

    /// <summary>Initializes the mapper with the security repository.</summary>
    /// <param name="securityRepository">Repository used to retrieve LDAP group-to-role mappings and scope rules.</param>
    public RoleMapper(ISecurityRepository securityRepository)
    {
        _securityRepository = securityRepository ?? throw new ArgumentNullException(nameof(securityRepository));
    }

    // virtual: a test seam so HTTP-pipeline tests (e.g. the audit
    // endpoints) can substitute the LDAP-group→role resolution.
    /// <summary>Maps a list of LDAP group names to ScadaBridge roles and computes site-scope permissions.</summary>
    /// <param name="ldapGroups">LDAP group names from the authenticated user's directory entry.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>A <see cref="RoleMappingResult"/> containing matched roles, permitted site IDs, and the system-wide flag.</returns>
    public virtual async Task<RoleMappingResult> MapGroupsToRolesAsync(
        IReadOnlyList<string> ldapGroups,
        CancellationToken ct = default)
    {
        var allMappings = await _securityRepository.GetAllMappingsAsync(ct);

        var matchedRoles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        var permittedSiteIds = new HashSet<string>();
        var hasDeploymentRole = false;
        var hasScopedDeploymentMapping = false;
        var hasUnscopedDeploymentMapping = false;

        foreach (var mapping in allMappings)
        {
            // Match LDAP group names (case-insensitive)
            if (!ldapGroups.Any(g => g.Equals(mapping.LdapGroupName, StringComparison.OrdinalIgnoreCase)))
                continue;

            matchedRoles.Add(mapping.Role);

            if (mapping.Role.Equals(Roles.Deployer, StringComparison.OrdinalIgnoreCase))
            {
                hasDeploymentRole = true;

                var scopeRules = await _securityRepository.GetScopeRulesForMappingAsync(mapping.Id, ct);
                if (scopeRules.Count > 0)
                {
                    hasScopedDeploymentMapping = true;
                    foreach (var rule in scopeRules)
                    {
                        permittedSiteIds.Add(rule.SiteId.ToString());
                    }
                }
                else
                {
                    hasUnscopedDeploymentMapping = true;
                }
            }
        }

        // Union semantics: a Deployment user is system-wide iff
        // *any* matched Deployment mapping has no scope rules. A user in both
        // SCADA-Deploy-All (unscoped) and SCADA-Deploy-SiteA (scoped to Site A)
        // gets the broader grant, not the narrower one — matching the design's
        // "roles are independent — there is no implied hierarchy" rule.
        var isSystemWide = hasUnscopedDeploymentMapping
                           || (hasDeploymentRole && !hasScopedDeploymentMapping);

        // When system-wide, drop any accumulated scope ids — the empty
        // permitted set is the system-wide signal downstream consumers
        // (SiteScopeService, ManagementActor) already use.
        if (isSystemWide)
        {
            permittedSiteIds.Clear();
        }

        return new RoleMappingResult(
            matchedRoles.ToList(),
            permittedSiteIds.ToList(),
            isSystemWide);
    }
}
