using C4IT_IAM_GET;
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.IO;
using System.Linq;
using System.Net;
using System.Security;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Xml.Serialization;

using static C4IT.Logging.cLogManager;
using System.Reflection;
using C4IT.Logging;

namespace C4IT_IAM_Engine
{
    public class SecurityGroups
    {
        public string domainName;
        public string effectiveDomainController;
        public string username;
        public SecureString password;
        public bool ForceStrictAdGroupNames;
        public bool PreserveAdGroupNameCase;

        public List<IAM_SecurityGroup> IAM_SecurityGroups;
        public string rootUID;
        public SecurityGroups()
        {
            IAM_SecurityGroups = new List<IAM_SecurityGroup>();
        }

        private string GetLdapServer()
        {
            return string.IsNullOrWhiteSpace(effectiveDomainController) ? domainName : effectiveDomainController;
        }
        public bool GroupsAllreadyExisting(string ouPath)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());

            try
            {

                int groupCount = 0;
                if (IAM_SecurityGroups != null)
                    foreach (var s in IAM_SecurityGroups)
                    {
                        if (s.securityGroupType != SecurityGroupType.Traverse)
                        {
                            DirectoryEntry entry = new DirectoryEntry
                            {
                                Path = "LDAP://" + GetLdapServer(),
                                Username = username,
                                Password = new NetworkCredential("", password).Password,
                                AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
                            };
                            DirectorySearcher dSearch = new DirectorySearcher(entry)
                            {
                                Filter = "(&(CN=" + GetConfiguredGroupName(s.Name) + ")(objectClass=group))"
                            };
                            dSearch.PageSize = 100000;
                            SearchResultCollection sr = dSearch.FindAll();
                            groupCount += sr.Count;
                        }
                    }
                return groupCount > 0;
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
        }

        public bool GroupAllreadyExisting(string CN)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());

            try
            {

                int groupCount = 0;
                if (CN != string.Empty)
                {
                    DirectoryEntry entry = new DirectoryEntry
                    {
                        Path = "LDAP://" + GetLdapServer(),
                        Username = username,
                        Password = new NetworkCredential("", password).Password,
                        AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
                    };
                    DirectorySearcher dSearch = new DirectorySearcher(entry)
                    {
                        Filter = "(&(CN=" + GetConfiguredGroupName(CN) + ")(objectClass=group))"
                    };
                    dSearch.PageSize = 100000;
                    SearchResultCollection sr = dSearch.FindAll();
                    groupCount += sr.Count;
                }
                return groupCount > 0;
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
        }

        public void GenerateNewSecurityGroups(
            string baseFolder,
            string newFolderPath,
            string groupPrefix,
            string ouPath,
            PermissionGroupStrategy groupPermissionStrategy,
            string groupTraverseTag,
            string groupReadTag,
            string groupWriteTag,
            string groupOwnerTag,
            string groupDLTag,
            string groupGTag,
            IDictionary<string, string> customTags,
        List<IAM_SecurityGroupTemplate> templates,
            int readACLPermission,
            int writeACLPermission,
            int ownerACLPermission,
            int loop = 0,
            int existingADGroupCount = 0,
            string groupNameSanitizeReplacement = Helper.DefaultGroupNameSanitizeReplacement,
            bool preserveAdGroupNameCase = false)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            try
            {
                var resolvedTemplates = (templates ?? new List<IAM_SecurityGroupTemplate>())
                    .Select(template => new IAM_SecurityGroupTemplate(
                        template.NamingTemplate,
                        template.DescriptionTemplate,
                        template.WildcardTemplate,
                        template.Type,
                        template.Scope))
                    .ToList();
                var relativePathRaw = DataArea.GetRelativePath(newFolderPath, baseFolder).Trim(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
                relativePathRaw = relativePathRaw.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
                var relativePathSegments = relativePathRaw.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
                var sanitizedSegments = relativePathSegments.Select(i => Helper.SanitizePathSegment(i, groupNameSanitizeReplacement)).ToArray();
                var relativePath = sanitizedSegments.Length > 0 ? Helper.JoinSanitizedPathSegments(sanitizedSegments, groupNameSanitizeReplacement) : string.Empty;
                var folderName = sanitizedSegments.Length > 0
                    ? sanitizedSegments[sanitizedSegments.Length - 1]
                    : Helper.SanitizePathSegment(Path.GetFileName(newFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)), groupNameSanitizeReplacement);
                var rootContext = Helper.GetRootPathTemplateContext(baseFolder, groupNameSanitizeReplacement);

                foreach (var template in resolvedTemplates)
                {
                    var GroupTypeTag = "";
                    switch (template.Type)
                    {
                        case SecurityGroupType.Owner:
                            GroupTypeTag = groupOwnerTag;
                            break;
                        case SecurityGroupType.Write:
                            GroupTypeTag = groupWriteTag;
                            break;
                        case SecurityGroupType.Read:
                            GroupTypeTag = groupReadTag;
                            break;
                        case SecurityGroupType.Traverse:
                            GroupTypeTag = groupTraverseTag;
                            break;
                        default:
                            break;
                    }

                    var GroupScopeTag = "";
                    switch (template.Scope)
                    {
                        case GroupScope.Global:
                            GroupScopeTag = groupGTag;
                            break;
                        case GroupScope.Local:
                            GroupScopeTag = groupDLTag;
                            break;
                        default:
                            break;
                    }
                    var tags = new Dictionary<string, string>();
                    tags.Add("PREFIX", groupPrefix);
                    tags.Add("GROUPTYPEPOSTFIX", GroupTypeTag);
                    tags.Add("SCOPETAG", GroupScopeTag);

                    var replacementTags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                    if (customTags != null)
                    {
                        foreach (var customTag in customTags)
                            replacementTags[customTag.Key] = customTag.Value;
                    }
                    foreach (var tag in tags)
                        replacementTags[tag.Key] = tag.Value;

                    var boundedNameContext = Helper.GetBoundedAdGroupTemplateContext(
                        template.NamingTemplate,
                        template.Type != SecurityGroupType.Traverse,
                        relativePath,
                        sanitizedSegments,
                        folderName,
                        replacementTags,
                        Helper.MaxAdGroupNameLength,
                        $"{template.Type}/{template.Scope} fuer '{newFolderPath}'",
                        "AD-Gruppenname",
                        rootContext,
                        preserveAdGroupNameCase,
                        groupNameSanitizeReplacement);

                    var boundedDescriptionContext = Helper.GetBoundedAdGroupTemplateContext(
                        template.DescriptionTemplate,
                        template.Type != SecurityGroupType.Traverse,
                        relativePath,
                        sanitizedSegments,
                        folderName,
                        replacementTags,
                        Helper.MaxAdGroupDescriptionLength,
                        $"{template.Type}/{template.Scope} fuer '{newFolderPath}'",
                        "AD-Gruppenbeschreibung",
                        rootContext,
                        preserveAdGroupNameCase,
                        groupNameSanitizeReplacement);

                    var adjustedNameSegments = boundedNameContext.SanitizedSegments ?? Array.Empty<string>();
                    var adjustedNameRelativePath = adjustedNameSegments.Length > 0 ? Helper.JoinSanitizedPathSegments(adjustedNameSegments, groupNameSanitizeReplacement) : string.Empty;
                    var adjustedNameFolderName = boundedNameContext.FolderName;
                    var adjustedDescriptionSegments = boundedDescriptionContext.SanitizedSegments ?? Array.Empty<string>();
                    var adjustedDescriptionRelativePath = adjustedDescriptionSegments.Length > 0 ? Helper.JoinSanitizedPathSegments(adjustedDescriptionSegments, groupNameSanitizeReplacement) : string.Empty;
                    var adjustedDescriptionFolderName = boundedDescriptionContext.FolderName;

                    template.NamingTemplate = Helper.ApplyAdGroupNameCasing(
                        Helper.ApplyTemplatePlaceholders(template.NamingTemplate, template.Type != SecurityGroupType.Traverse, adjustedNameRelativePath, adjustedNameSegments, adjustedNameFolderName, rootContext, groupNameSanitizeReplacement)
                            .ReplaceTags(customTags).ReplaceTags(tags),
                        preserveAdGroupNameCase);

                    template.DescriptionTemplate = Helper.ApplyAdGroupNameCasing(
                        Helper.ApplyTemplatePlaceholders(template.DescriptionTemplate, template.Type != SecurityGroupType.Traverse, adjustedDescriptionRelativePath, adjustedDescriptionSegments, adjustedDescriptionFolderName, rootContext, groupNameSanitizeReplacement)
                            .ReplaceTags(customTags).ReplaceTags(tags),
                        preserveAdGroupNameCase);


                    template.WildcardTemplate = Helper.ApplyAdGroupNameCasing(
                        Helper.ApplyTemplatePlaceholders(template.WildcardTemplate, template.Type != SecurityGroupType.Traverse, adjustedNameRelativePath, adjustedNameSegments, adjustedNameFolderName, rootContext, groupNameSanitizeReplacement)
                            .ReplaceTags(customTags).ReplaceTags(tags),
                        preserveAdGroupNameCase);

                }

                IAM_SecurityGroupTemplate ownerGlobal = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Global) && t.Type.Equals(SecurityGroupType.Owner));
                IAM_SecurityGroup osecGroup = new IAM_SecurityGroup()
                {
                    Name = ownerGlobal.NamingTemplate,
                    description = ownerGlobal.DescriptionTemplate,
                    WildcardPattern = ownerGlobal.WildcardTemplate,

                    technicalName = "CN=" + ownerGlobal.NamingTemplate + "," + ouPath,
                    targetTyp = (int)IAM_TargetType.FileSystem,
                    rights = (FileSystemRights)ownerACLPermission,
                    Scope = GroupScope.Global
                };
                IAM_SecurityGroups.Add(osecGroup);

                IAM_SecurityGroupTemplate writeGlobal = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Global) && t.Type.Equals(SecurityGroupType.Write));
                IAM_SecurityGroup wsecGroup = new IAM_SecurityGroup()
                {
                    Name = writeGlobal.NamingTemplate,
                    description = writeGlobal.DescriptionTemplate,
                    WildcardPattern = writeGlobal.WildcardTemplate,

                    technicalName = "CN=" + writeGlobal.NamingTemplate + "," + ouPath,
                    targetTyp = (int)IAM_TargetType.FileSystem,
                    rights = (FileSystemRights)writeACLPermission,
                    Scope = GroupScope.Global
                };
                IAM_SecurityGroups.Add(wsecGroup);

                IAM_SecurityGroupTemplate readGlobal = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Global) && t.Type.Equals(SecurityGroupType.Read));
                IAM_SecurityGroup rsecGroup = new IAM_SecurityGroup()
                {
                    Name = readGlobal.NamingTemplate,
                    description = readGlobal.DescriptionTemplate,
                    WildcardPattern = readGlobal.WildcardTemplate,

                    technicalName = "CN=" + readGlobal.NamingTemplate + "," + ouPath,
                    targetTyp = (int)IAM_TargetType.FileSystem,
                    rights = (FileSystemRights)readACLPermission,
                    Scope = GroupScope.Global
                };
                IAM_SecurityGroups.Add(rsecGroup);


                //
                if (groupPermissionStrategy == PermissionGroupStrategy.AGDLP)
                {
                    IAM_SecurityGroupTemplate ownerDL = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Local) && t.Type.Equals(SecurityGroupType.Owner));
                    IAM_SecurityGroup osecDLGroup = new IAM_SecurityGroup()
                    {
                        Name = ownerDL.NamingTemplate,
                        description = ownerDL.DescriptionTemplate,
                        WildcardPattern = ownerDL.WildcardTemplate,

                        technicalName = "CN=" + ownerDL.NamingTemplate + "," + ouPath,
                        targetTyp = (int)IAM_TargetType.FileSystem,
                        rights = (FileSystemRights)ownerACLPermission,
                        Scope = GroupScope.Local
                    };
                    osecDLGroup.memberGroups.Add(osecGroup);
                    IAM_SecurityGroups.Add(osecDLGroup);

                    IAM_SecurityGroupTemplate writeDL = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Local) && t.Type.Equals(SecurityGroupType.Write));
                    IAM_SecurityGroup wsecDLGroup = new IAM_SecurityGroup()
                    {
                        Name = writeDL.NamingTemplate,
                        description = writeDL.DescriptionTemplate,
                        WildcardPattern = writeDL.WildcardTemplate,

                        technicalName = "CN=" + writeDL.NamingTemplate + "," + ouPath,
                        targetTyp = (int)IAM_TargetType.FileSystem,
                        rights = (FileSystemRights)writeACLPermission,
                        Scope = GroupScope.Local
                    };
                    wsecDLGroup.memberGroups.Add(wsecGroup);
                    IAM_SecurityGroups.Add(wsecDLGroup);

                    IAM_SecurityGroupTemplate readDL = resolvedTemplates.First(t => t.Scope.Equals(GroupScope.Local) && t.Type.Equals(SecurityGroupType.Read));
                    IAM_SecurityGroup rsecDLGroup = new IAM_SecurityGroup()
                    {
                        Name = readDL.NamingTemplate,
                        description = readDL.DescriptionTemplate,
                        WildcardPattern = readDL.WildcardTemplate,

                        technicalName = "CN=" + readDL.NamingTemplate + "," + ouPath,
                        targetTyp = (int)IAM_TargetType.FileSystem,
                        rights = (FileSystemRights)readACLPermission,
                        Scope = GroupScope.Local
                    };
                    rsecDLGroup.memberGroups.Add(rsecGroup);
                    IAM_SecurityGroups.Add(rsecDLGroup);
                }
                foreach (var secGroup in IAM_SecurityGroups)
                {
                    secGroup.description = secGroup.description.ReplaceLoopTag(0);
                    secGroup.Name = secGroup.Name.ReplaceLoopTag(loop);
                    secGroup.technicalName = secGroup.technicalName.ReplaceLoopTag(loop);
                    secGroup.WildcardPattern = secGroup.WildcardPattern.ReplaceLoopTag(loop);
                    DefaultLogger.LogEntry(LogLevels.Debug, $"Security group generated: {secGroup.technicalName}");
                }
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
        }



        public static string GetRightPartOfPath(string path, string startAfterPart)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            try
            {
                // use the correct seperator for the environment
                var pathParts = path.Split(Path.DirectorySeparatorChar);
                if (startAfterPart.EndsWith(Path.DirectorySeparatorChar.ToString()))
                {
                    startAfterPart = startAfterPart.Substring(0, startAfterPart.Length - 1);
                }
                var startAfter = startAfterPart.Split(Path.DirectorySeparatorChar);
                string newPath = String.Empty;
                if (pathParts.Length > startAfter.Length)
                {
                    for (int i = startAfter.Length; pathParts.Length > i; i++)
                    {
                        newPath += pathParts[i] + Path.DirectorySeparatorChar;
                    }
                }
                if (newPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
                {
                    newPath = newPath.Substring(0, newPath.Length - 1);
                }

                // try and work out if last part was a directory - if not, drop the last part as we don't want the filename
                return newPath;
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
        }

        public static string getSID(DirectoryEntry ent)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            try
            {
                var usrId = (byte[])ent.Properties["objectSid"][0];
                return (new SecurityIdentifier(usrId, 0)).ToString();
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }

        }

        private DirectoryEntry FindGroupEntry(string groupName)
        {
            DirectoryEntry entry = new DirectoryEntry
            {
                Path = "LDAP://" + GetLdapServer(),
                Username = username,
                Password = new NetworkCredential("", password).Password,
                AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
            };

            DirectorySearcher search = new DirectorySearcher(entry)
            {
                Filter = "(&(objectClass=group)(sAMAccountName=" + GetConfiguredGroupName(groupName) + "))"
            };
            search.PageSize = 100000;

            var result = search.FindOne();
            if (result == null)
            {
                entry.Dispose();
                search.Dispose();
                return null;
            }

            var groupEntry = result.GetDirectoryEntry();
            search.Dispose();
            entry.Dispose();
            return groupEntry;
        }

        private DirectoryEntry FindGroupEntryByWildcard(string ouPath, string wildcardPattern)
        {
            if (string.IsNullOrWhiteSpace(wildcardPattern))
                return null;

            Regex wildcardRegex;
            try
            {
                wildcardRegex = new Regex(wildcardPattern, RegexOptions.IgnoreCase);
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                return null;
            }

            var basePath = "LDAP://" + GetLdapServer();
            if (!string.IsNullOrWhiteSpace(ouPath))
                basePath += "/" + ouPath;

            DirectoryEntry entry = new DirectoryEntry
            {
                Path = basePath,
                Username = username,
                Password = new NetworkCredential("", password).Password,
                AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
            };

            DirectorySearcher search = new DirectorySearcher(entry)
            {
                Filter = "(objectClass=group)"
            };
            search.PageSize = 100000;
            search.PropertiesToLoad.Add("sAMAccountName");
            search.PropertiesToLoad.Add("distinguishedName");

            string matchedName = null;
            string matchedDistinguishedName = null;
            var matchCount = 0;

            foreach (SearchResult result in search.FindAll())
            {
                if (!result.Properties.Contains("sAMAccountName") || result.Properties["sAMAccountName"].Count == 0)
                    continue;

                var samAccountName = result.Properties["sAMAccountName"][0]?.ToString();
                if (string.IsNullOrWhiteSpace(samAccountName) || !wildcardRegex.IsMatch(samAccountName))
                    continue;

                matchCount++;
                if (matchCount > 1)
                {
                    DefaultLogger.LogEntry(LogLevels.Warning, $"Multiple AD groups matched wildcard '{wildcardPattern}' in '{basePath}'. Regex-based reuse is skipped.");
                    search.Dispose();
                    entry.Dispose();
                    return null;
                }

                matchedName = samAccountName;
                matchedDistinguishedName = result.Properties.Contains("distinguishedName") && result.Properties["distinguishedName"].Count > 0
                    ? result.Properties["distinguishedName"][0]?.ToString()
                    : null;
            }

            search.Dispose();
            entry.Dispose();

            if (string.IsNullOrWhiteSpace(matchedDistinguishedName))
                return null;

            DefaultLogger.LogEntry(LogLevels.Debug, $"Reusing existing AD group '{matchedName}' via wildcard '{wildcardPattern}'.");
            return new DirectoryEntry("LDAP://" + GetLdapServer() + "/" + matchedDistinguishedName, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
        }

        private DirectoryEntry FindGroupEntryFromFolderAcl(string folderPath, string wildcardPattern)
        {
            if (string.IsNullOrWhiteSpace(folderPath) || string.IsNullOrWhiteSpace(wildcardPattern) || !Directory.Exists(folderPath))
                return null;

            Regex wildcardRegex;
            try
            {
                wildcardRegex = new Regex(wildcardPattern, RegexOptions.IgnoreCase);
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                return null;
            }

            try
            {
                var directory = new DirectoryInfo(folderPath);
                var rules = directory.GetAccessControl(AccessControlSections.Access)
                    .GetAccessRules(true, false, typeof(SecurityIdentifier))
                    .Cast<FileSystemAccessRule>();
                var matchedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

                using (var domainContext = new PrincipalContext(ContextType.Domain, GetLdapServer(), username, new NetworkCredential("", password).Password))
                {
                    foreach (var rule in rules)
                    {
                        if (rule.AccessControlType != AccessControlType.Allow)
                            continue;

                        var sid = rule.IdentityReference?.Value;
                        if (string.IsNullOrWhiteSpace(sid) || sid == "S-1-1-0")
                            continue;

                        using (var group = GroupPrincipal.FindByIdentity(domainContext, IdentityType.Sid, sid))
                        {
                            var samAccountName = group?.SamAccountName;
                            if (string.IsNullOrWhiteSpace(samAccountName) || !wildcardRegex.IsMatch(samAccountName))
                                continue;

                            matchedNames.Add(samAccountName);
                            if (matchedNames.Count > 1)
                            {
                                DefaultLogger.LogEntry(LogLevels.Warning, $"Multiple ACL groups on folder '{folderPath}' matched wildcard '{wildcardPattern}'. ACL-based reuse is skipped.");
                                return null;
                            }
                        }
                    }
                }

                if (matchedNames.Count == 0)
                    return null;

                var matchedName = matchedNames.First();
                DefaultLogger.LogEntry(LogLevels.Debug, $"Reusing ACL-linked AD group '{matchedName}' via wildcard '{wildcardPattern}' on folder '{folderPath}'.");
                return FindGroupEntry(matchedName);
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                return null;
            }
        }

        private void ApplyExistingGroup(IAM_SecurityGroup secGroup, DirectoryEntry existingGroup)
        {
            secGroup.CreatedNewEntry = false;
            secGroup.UID = getSID(existingGroup);

            if (existingGroup.Properties.Contains("sAMAccountName") && existingGroup.Properties["sAMAccountName"].Count > 0)
                secGroup.Name = existingGroup.Properties["sAMAccountName"][0]?.ToString();

            if (existingGroup.Properties.Contains("distinguishedName") && existingGroup.Properties["distinguishedName"].Count > 0)
                secGroup.technicalName = existingGroup.Properties["distinguishedName"][0]?.ToString();
        }

        private static bool HasMember(PropertyValueCollection members, string distinguishedName)
        {
            foreach (var member in members)
            {
                if (string.Equals(member?.ToString(), distinguishedName, StringComparison.OrdinalIgnoreCase))
                    return true;
            }

            return false;
        }

        private void AddMissingMembers(DirectoryEntry group, IAM_SecurityGroup secGroup, List<UserPrincipal> users)
        {
            if (group == null)
                return;

            var changed = false;
            if (users != null && secGroup.Scope == GroupScope.Global)
            {
                foreach (var user in users.Where(u => u != null && !string.IsNullOrWhiteSpace(u.DistinguishedName)))
                {
                    if (HasMember(group.Properties["member"], user.DistinguishedName))
                        continue;

                    DefaultLogger.LogEntry(LogLevels.Debug, $"Adding missing member: {user.DistinguishedName}");
                    group.Properties["member"].Add(user.DistinguishedName);
                    changed = true;
                }
            }

            if (secGroup.Scope == GroupScope.Local)
            {
                foreach (var innerGroup in secGroup.memberGroups.Where(g => g != null && !string.IsNullOrWhiteSpace(g.technicalName)))
                {
                    if (HasMember(group.Properties["member"], innerGroup.technicalName))
                        continue;

                    DefaultLogger.LogEntry(LogLevels.Debug, $"Adding missing nested group: {innerGroup.technicalName}");
                    group.Properties["member"].Add(innerGroup.technicalName);
                    changed = true;
                }
            }

            if (changed)
                group.CommitChanges();
        }

        public DirectoryEntry EnsureADGroup(string ouPath, IAM_SecurityGroup secGroup, List<UserPrincipal> users, string folderPath = null)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            try
            {
                secGroup.CreatedNewEntry = false;
                DirectoryEntry existingGroup = null;
                if (!ForceStrictAdGroupNames)
                    existingGroup = FindGroupEntryFromFolderAcl(folderPath, secGroup.WildcardPattern);

                if (existingGroup == null)
                    existingGroup = FindGroupEntry(secGroup.Name);

                if (existingGroup == null && !ForceStrictAdGroupNames && string.IsNullOrWhiteSpace(folderPath))
                    existingGroup = FindGroupEntryByWildcard(ouPath, secGroup.WildcardPattern);

                if (existingGroup == null)
                    return CreateADGroup(ouPath, secGroup, users);

                AddMissingMembers(existingGroup, secGroup, users);
                ApplyExistingGroup(secGroup, existingGroup);
                return existingGroup;
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
        }

        public DirectoryEntry PreviewADGroup(string ouPath, IAM_SecurityGroup secGroup, string folderPath = null)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            try
            {
                secGroup.CreatedNewEntry = false;
                DirectoryEntry existingGroup = null;
                if (!ForceStrictAdGroupNames)
                    existingGroup = FindGroupEntryFromFolderAcl(folderPath, secGroup.WildcardPattern);

                if (existingGroup == null)
                    existingGroup = FindGroupEntry(secGroup.Name);

                if (existingGroup == null && !ForceStrictAdGroupNames && string.IsNullOrWhiteSpace(folderPath))
                    existingGroup = FindGroupEntryByWildcard(ouPath, secGroup.WildcardPattern);

                if (existingGroup == null)
                    return null;

                ApplyExistingGroup(secGroup, existingGroup);
                return existingGroup;
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
        }

        public DirectoryEntry CreateADGroup(string ouPath, IAM_SecurityGroup secGroup, List<UserPrincipal> users)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            try
            {
                secGroup.CreatedNewEntry = false;
                var groupName = GetConfiguredGroupName(secGroup.Name);
                secGroup.Name = groupName;
                secGroup.technicalName = "CN=" + groupName + "," + ouPath;

                if (!GroupAllreadyExisting(groupName))
                {

                    DirectoryEntry entry = new DirectoryEntry("LDAP://" + GetLdapServer() + "/" + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
                    DefaultLogger.LogEntry(LogLevels.Debug, $"Creating ad entry with CN / sAmAccountName: {groupName}");
                    DirectoryEntry group = entry.Children.Add("CN=" + groupName, "group");
                    group.Properties["sAmAccountName"].Value = groupName;
                    if (users != null && secGroup.Scope == GroupScope.Global)
                    {
                        foreach (var user in users)
                        {
                            DefaultLogger.LogEntry(LogLevels.Debug, $"Adding member: {user.DistinguishedName}");
                            group.Properties["member"].Add(user.DistinguishedName);
                        }
                    }
                    if(!String.IsNullOrEmpty(secGroup.description))
                    {
                        DefaultLogger.LogEntry(LogLevels.Debug, $"Setting description: {secGroup.description}");
                        group.Properties["description"].Value = secGroup.description;
                    }
                    var groupType = secGroup.Scope == GroupScope.Global ? GroupScopeValues.Global : GroupScopeValues.Local;
                    DefaultLogger.LogEntry(LogLevels.Debug, $"Setting groupType to: {groupType}");
                    group.Properties["groupType"].Value = groupType;
                    if (secGroup.Scope == GroupScope.Local)
                        foreach (var iGroup in secGroup.memberGroups)
                        {
                            DefaultLogger.LogEntry(LogLevels.Debug, $"Adding member: {iGroup.technicalName}");
                            group.Properties["member"].Add(iGroup.technicalName);
                        }

                    group.CommitChanges();
                    DirectoryEntry ent = new DirectoryEntry("LDAP://" + GetLdapServer() + "/" + "CN=" + groupName + "," + ouPath, username, new NetworkCredential("", password).Password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);

                    var objectid = SecurityGroups.getSID(ent);
                    DefaultLogger.LogEntry(LogLevels.Debug, $"Security group created in ad: {secGroup.technicalName}");
                    secGroup.UID = objectid;
                    secGroup.CreatedNewEntry = true;
                    return ent;
                }
                else
                {
                    DirectoryEntry e = FindGroupEntry(secGroup.Name);
                    if (e == null)
                        return null;
                    AddMissingMembers(e, secGroup, users);
                    ApplyExistingGroup(secGroup, e);
                    return e;
                }
                return null;
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
        }

        private string GetConfiguredGroupName(string groupName)
        {
            return Helper.ApplyAdGroupNameCasing(groupName, PreserveAdGroupNameCase);
        }
    }
    public enum GroupScopeValues : int
    {
        Global = -2147483646,
        Local = -2147483644
    }
    public class IAM_SecurityGroupTemplate
    {
        private string namingTemplate;
        private string descriptionTemplate;
        private string wildcardTemplate;
        private SecurityGroupType type;
        private GroupScope scope;

        public IAM_SecurityGroupTemplate(string namingTemplate, string descriptionTemplate, string wildcardTemplate, SecurityGroupType type, GroupScope scope)
        {
            NamingTemplate = namingTemplate;
            DescriptionTemplate = descriptionTemplate;
            WildcardTemplate = wildcardTemplate;
            Type = type;
            Scope = scope;
        }

        public string NamingTemplate
        {
            get => namingTemplate; set
            {
                namingTemplate = value == null ? "" : value;
            }
        }
        public string DescriptionTemplate
        {
            get => descriptionTemplate; set
            {
                descriptionTemplate = value == null ? "" : value;
            }
        }
        public string WildcardTemplate
        {
            get => wildcardTemplate; set
            {
                wildcardTemplate = value == null ? "" : value;
            }
        }
        public SecurityGroupType Type { get => type; set => type = value; }
        public GroupScope Scope { get => scope; set => scope = value; }
    }
    public class IAM_SecurityGroup
    {

        public string UID;
        public string Parent = "";
        public string description;
        public string WildcardPattern;
        public bool CreatedNewEntry;
        public List<IAM_SecurityGroup> memberGroups;
        public string Name;
        public string technicalName;
        public SecurityGroupType securityGroupType;
        public int targetTyp;
        public GroupScope Scope;
        public FileSystemRights rights;
        public IAM_SecurityGroup()
        {
            memberGroups = new List<IAM_SecurityGroup>();
        }
    }
    public enum SecurityGroupType
    {
        [XmlEnum(Name = "0")]
        Owner,
        [XmlEnum(Name = "1")]
        Write,
        [XmlEnum(Name = "2")]
        Read,
        [XmlEnum(Name = "3")]
        Traverse
    }


}
