using HRD.LDAPService.Ldap;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.Linq;

namespace HRD.LDAPService
{
    public class LdapManager
    {
        private readonly LdapOptions _ldapOptions;

        public LdapManager(IOptions<LdapOptions> ldapOptions)
        {
            _ldapOptions = ldapOptions.Value;
        }

        public bool AD_AddUserloginToGroup(string userLogin, string group4User)
        {
            string groupName = GetFullGroupName(group4User);

            try
            {
                using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, _ldapOptions.LDAP_DOMAIN))
                {
                    UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, userLogin);
                    if (userPrincipal == default)
                    {
                        throw new Exception($". Can't find the UserPrincipal by userId:{userLogin}");
                    }
                    List<Principal> userGroupList = userPrincipal.GetGroups().ToList(); // all groups of which the user is a direct member
                    if (userGroupList == default)
                    {
                        throw new Exception($". Can't find the userGroupList; userId:{userLogin}");
                    }

                    GroupPrincipal userGroup = userGroupList.Find(x => string.Equals(x.Name, groupName, StringComparison.OrdinalIgnoreCase)) as GroupPrincipal;
                    if (userGroup != default)
                    {
                        return true; //is already here
                    }
                    else
                    {
                        GroupPrincipal group = GroupPrincipal.FindByIdentity(principalContext, IdentityType.Name, groupName);
                        if (group == default) //try to create a group
                        {
                            if (CreateAdGroup(groupName))
                            {
                                group = GroupPrincipal.FindByIdentity(principalContext, IdentityType.Name, groupName);
                            }
                        }

                        if (group == default)
                        {
                            throw new Exception($". Can't create the AD-group: \"{groupName}\"");
                        }
                        group.Members.Add(principalContext, IdentityType.SamAccountName, userLogin);
                        group.Save();
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        public bool CheckAndCreateAdGroup(string adGroupName)
        {
            try
            {
                // set up domain context and binding to the OU=Web-Apps
                var adPath = AD_GroupPath(adGroupName);
                using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, _ldapOptions.LDAP_DOMAIN, adPath))
                {
                    var group = GroupPrincipal.FindByIdentity(ctx, IdentityType.Name, adGroupName);
                    if (group != null)
                    {
                        return true;
                    }

                    // create a new group principal, give it a name
                    GroupPrincipal newGroup = new GroupPrincipal(ctx, adGroupName);

                    // save the group
                    newGroup.Save();
                    return true;
                }
            }
            catch (Exception ex)
            {
                return false;
                //throw;
            }
        }

        public List<string> GetAdUserLoginList4AdGroups(List<string> adGroupNames)
        {
            if (adGroupNames == null) { return default; }
            List<string> result = new List<string>();

            foreach (var adGroupName in adGroupNames)
            {
                var list = GetAdUserLoginList4AdGroup(adGroupName);
                result.AddRange(list);
            }

            return result;
        }

        public List<string> GetAdUserLoginList4AdGroup(string adGroupName)
        {
            try
            {
                List<string> result = new List<string>();
                // set up domain context and binding to the OU=Web-Apps
                var adPath = AD_GroupPath(adGroupName);
                using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, _ldapOptions.LDAP_DOMAIN, adPath))
                {
                    var group = GroupPrincipal.FindByIdentity(ctx, IdentityType.Name, adGroupName);
                    if (group == null)
                    {
                        throw new Exception($". Can't find the AD-group: \"{adGroupName}\"");
                    }

                    result = group.Members.Select(x => x.SamAccountName).ToList();
                    return result;
                }
            }
            catch (Exception ex)
            {
                //_logger.LogException(ex, $"An error occurred while getting user for the AD-group {adGroupName}");
                return default;
                //throw;
            }
        }

        public bool CheckAndCreateAdGroups(List<string> adGroupNames)
        {
            if (adGroupNames == null) { return false; }

            foreach (var adGroupName in adGroupNames)
            {
                if (!CheckAndCreateAdGroup(adGroupName))
                {
                    return false;
                }
            }

            return true;
        }

        public bool CreateAdGroup(string adGroupName)
        {
            try
            {
                // set up domain context and binding to the OU=TechWriters organizational unit in your company
                var adPath = AD_GroupPath(adGroupName);
                using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, _ldapOptions.LDAP_DOMAIN, adPath))
                {
                    // create a new group principal, give it a name
                    using (GroupPrincipal group = new GroupPrincipal(ctx, adGroupName))
                    {
                        // optionally set additional properties on the newly created group here....

                        // save the group
                        group.Save();
                    }
                    return true;
                }
            }
            catch (Exception)
            {
                return false;
                //throw;
            }
        }

        public bool IsWindreamADGroup(string adGroupName)
        {
            return adGroupName.StartsWith(_ldapOptions.LDAP_EDM_Prefix, StringComparison.InvariantCultureIgnoreCase);
        }

        public bool IsWindreamSuffixGroup(string suffixGroupName)
        {
            return suffixGroupName.StartsWith(_ldapOptions.LDAP_WINDREAM, StringComparison.InvariantCultureIgnoreCase);
        }

        public bool IsWindreamAdminGroup(string suffixGroupName)
        {
            return suffixGroupName.StartsWith(_ldapOptions.LDAP_WINDREAM + "Admin", StringComparison.InvariantCultureIgnoreCase);
        }

        public bool IsWindreamUserGroup(string suffixGroupName)
        {
            return suffixGroupName.StartsWith(_ldapOptions.LDAP_WINDREAM + "User", StringComparison.InvariantCultureIgnoreCase);
        }

        public string AD_GroupPath(string adGroupName)
        {
            if (IsWindreamADGroup(adGroupName))
            {
                return _ldapOptions.LDAP_PATH_EDM;
            }
            else
            {
                return _ldapOptions.LDAP_PATH_WEBAPPS;
            }
        }

        public string GetFullGroupName(string groupNameSuffix)
        {
            //Mapping Windream_User => GG_EDMUser_Group_Live or GG_EDM__Test_User_Group
            //Mapping Windream_Admin => GG_EDMAdmin_Group or GG_EDM__Test_Admin_Group
            //Mapping Windream_Technik => GG_EDM_Technik or GG_EDM__Test_Technik

            var testPrefix = !_ldapOptions.LDAP_WebAppGroup_Is_Live ? _ldapOptions.LDAP_Prefix_Test : "";
            if (IsWindreamAdminGroup(groupNameSuffix))
            {
                return _ldapOptions.LDAP_EDMAdmin_Prefix + testPrefix;
            }
            else if (IsWindreamUserGroup(groupNameSuffix))
            {
                return _ldapOptions.LDAP_EDMUser_Prefix + testPrefix;
            }
            else
            {
                return (IsWindreamSuffixGroup(groupNameSuffix) ? _ldapOptions.LDAP_EDM_Prefix : _ldapOptions.LDAP_WebAppp_Prefix) + testPrefix + "_" + groupNameSuffix.Replace(_ldapOptions.LDAP_WINDREAM, "");
            }
        }

        public bool AD_RemoveUserFromGroup(string userId, string group4User)
        {
            string groupName = GetFullGroupName(group4User);
            // secure that no windream user or admin can be deleted
            if (groupName.Equals(_ldapOptions.LDAP_EDMUser_Prefix, StringComparison.CurrentCultureIgnoreCase)
                || groupName.Equals(_ldapOptions.LDAP_EDMAdmin_Prefix, StringComparison.CurrentCultureIgnoreCase))
            {
                return true;
            }
            try
            {
                using var principalContext = new PrincipalContext(ContextType.Domain, _ldapOptions.LDAP_DOMAIN);
                UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, userId);
                if (userPrincipal == default)
                {
                    throw new Exception($". Can't find the UserPrincipal by userId: {userId}");
                }
                List<Principal> userGroupList = userPrincipal.GetGroups().ToList(); // all groups of which the user is a direct member
                if (userGroupList == default)
                {
                    throw new Exception($". Can't find the userGroupList; userId: {userId}");
                }

                GroupPrincipal group = userGroupList.Find(x => x.Name == groupName) as GroupPrincipal;
                if (group == default)
                {
                    return true; //the user is not in the group - nothing to do more
                    //throw new Exception($". Can't find the AD-group: \"{groupName}\"");
                }
                group.Members.Remove(principalContext, IdentityType.SamAccountName, userId);
                group.Save();
                return true;
            }
            catch (Exception ex)
            {
                throw;
            }
        }
    }
}