using NLog;
using System;
using System.Diagnostics;
using System.DirectoryServices.AccountManagement;
using System.IO;
using System.Security;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;

namespace ServerManagerTool.Common.Utils
{
    public static class SecurityUtils
    {
        private const string PasswordChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        private const string GROUP_USERSBUILTIN = @"BUILTIN\Users";

        private static readonly Logger _logger = LogManager.GetCurrentClassLogger();

        private static int callCount = 0;

        public static string GeneratePassword(int count)
        {
            StringBuilder newPassword = new StringBuilder(count);
            Random random;
            unchecked
            {
                random = new Random((int)DateTime.Now.Ticks + callCount);
                callCount++;
            }

            for(int i = 0; i < count; i++)
            {
                newPassword.Append(PasswordChars[random.Next(PasswordChars.Length)]);
            }

            return newPassword.ToString();
        }

        public static bool IsAdministrator()
        {
            WindowsIdentity identity = WindowsIdentity.GetCurrent();
            WindowsPrincipal principal = new WindowsPrincipal(identity);
            return  principal.IsInRole(WindowsBuiltInRole.Administrator);
        }

        public static bool SetDirectoryOwnershipForAllUsers(string directory)
        {
            if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory))
                return true;

            try
            {
                var directoryInfo = new DirectoryInfo(directory);
                var security = directoryInfo.GetAccessControl(AccessControlSections.Access);
                bool result;

                var iFlags = InheritanceFlags.None;

                // *** Add Access Rule to the actual directory itself
                var accessRule = new FileSystemAccessRule(GROUP_USERSBUILTIN, FileSystemRights.FullControl, iFlags, PropagationFlags.NoPropagateInherit, AccessControlType.Allow);
                security.ModifyAccessRule(AccessControlModification.Set, accessRule, out result);

                if (!result)
                    return false;

                // *** Always allow objects to inherit on a directory
                iFlags = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit;

                // *** Add Access rule for the inheritance
                accessRule = new FileSystemAccessRule(GROUP_USERSBUILTIN, FileSystemRights.FullControl, iFlags, PropagationFlags.InheritOnly, AccessControlType.Allow);
                security.ModifyAccessRule(AccessControlModification.Add, accessRule, out result);

                if (!result)
                    return false;

                directoryInfo.SetAccessControl(security);
                return true;
            }
            catch (Exception ex)
            {
                // We give it a best-effort here.  If we aren't running an enterprise OS, this group may not exist (or be needed.)
                _logger.Error($"{nameof(SetDirectoryOwnershipForAllUsers)}. {ex.Message}\r\n{ex.StackTrace}");
                return false;
            }
        }

        public static bool SetFileOwnershipForAllUsers(string file)
        {
            if (string.IsNullOrWhiteSpace(file) || !File.Exists(file))
                return true;

            try
            {
                var fileInfo = new FileInfo(file);
                var security = fileInfo.GetAccessControl(AccessControlSections.Access);
                bool result;

                var iFlags = InheritanceFlags.None;

                // *** Add Access Rule to the actual file itself
                var accessRule = new FileSystemAccessRule(GROUP_USERSBUILTIN, FileSystemRights.FullControl, iFlags, PropagationFlags.NoPropagateInherit, AccessControlType.Allow);
                security.ModifyAccessRule(AccessControlModification.Set, accessRule, out result);

                if (!result)
                    return false;

                fileInfo.SetAccessControl(security);
                return true;
            }
            catch (Exception ex)
            {
                // We give it a best-effort here.  If we aren't running an enterprise OS, this group may not exist (or be needed.)
                _logger.Error($"{nameof(SetFileOwnershipForAllUsers)}. {ex.Message}\r\n{ex.StackTrace}");
                return false;
            }
        }

        public static bool DoesLocalWindowsAccountExist(string username)
        {
            try
            {
                if (MachineUtils.IsThisMachineADomainController())
                    throw new Exception($"This computer reports to be a domain controller.");

                var context = new PrincipalContext(ContextType.Machine, Environment.MachineName);
                if (context == null)
                    context = new PrincipalContext(ContextType.Machine);
                if (context == null)
                    throw new Exception($"Could not create instance of the PrincipalContext ({Environment.MachineName}).");

                var user = UserPrincipal.FindByIdentity(context, IdentityType.Name, username);
                if (user == null || !(user.Enabled.HasValue && user.Enabled.Value) || user.IsAccountLockedOut())
                    return false;

                var usersGroupSID = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
                GroupPrincipal group = GroupPrincipal.FindByIdentity(context, IdentityType.Sid, usersGroupSID.Value);
                if (group == null || !group.Members.Contains(user))
                    return false;

                return true;
            }
            catch (Exception ex)
            {
                _logger.Error($"{nameof(DoesLocalWindowsAccountExist)}. {ex.Message}\r\n{ex.StackTrace}");
                return false;
            }
        }

        public static bool CreateLocalWindowsAccount(string username, string password, string displayName, string description, bool userCannotChangePassword, bool passwordNeverExpires)
        {
            try
            {
                if (MachineUtils.IsThisMachineADomainController())
                    throw new Exception($"This computer reports to be a domain controller. User cannot be created.");

                var context = new PrincipalContext(ContextType.Machine, Environment.MachineName);
                if (context == null)
                    context = new PrincipalContext(ContextType.Machine);
                if (context == null)
                    throw new Exception($"Could not create instance of the PrincipalContext ({Environment.MachineName}).");

                // create the user if it does not exist
                var user = UserPrincipal.FindByIdentity(context, IdentityType.Name, username);
                if (user == null)
                {
                    user = new UserPrincipal(context)
                    {
                        Name = username,
                        DisplayName = displayName,
                        Description = description,
                    };

                    if (user == null)
                        throw new Exception($"Could not create new instance of the UserPrincipal ({username}).");
                }

                user.Enabled = true;
                user.PasswordNeverExpires = passwordNeverExpires;
                user.UserCannotChangePassword = userCannotChangePassword;
                user.SetPassword(password);
                if (user.IsAccountLockedOut())
                    user.UnlockAccount();
                user.Save();

                // now add user to "Users" group
                var usersGroupSID = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
                if (usersGroupSID == null)
                    throw new Exception($"Could not find instance of the SecurityIdentifier (WellKnownSidType.BuiltinUsersSid).");

                GroupPrincipal group = GroupPrincipal.FindByIdentity(context, IdentityType.Sid, usersGroupSID.Value);
                if (group == null)
                    throw new Exception($"Could not find instance of the GroupPrincipal (USERS).");

                if (!group.Members.Contains(user))
                {
                    group.Members.Add(user);
                    group.Save();
                }

                return true;
            }
            catch (Exception ex)
            {
                _logger.Error($"{nameof(CreateLocalWindowsAccount)}. {ex.Message}\r\n{ex.StackTrace}");
                return false;
            }
        }

        public static bool ChangeWindowsAccountPassword(string username, string password)
        {
            try
            {
                if (MachineUtils.IsThisMachineADomainController())
                    throw new Exception($"This computer reports to be a domain controller. User password cannot be changed.");

                var context = new PrincipalContext(ContextType.Machine, Environment.MachineName);
                if (context == null)
                    context = new PrincipalContext(ContextType.Machine);
                if (context == null)
                    throw new Exception($"Could not create instance of the PrincipalContext ({Environment.MachineName}).");

                // create the user if it does not exist
                var user = UserPrincipal.FindByIdentity(context, IdentityType.Name, username);
                if (user == null)
                    throw new Exception($"Could not find instance of the UserPrincipal ({username}).");

                user.SetPassword(password);
                user.Save();

                return true;
            }
            catch (Exception ex)
            {
                _logger.Error($"{nameof(ChangeWindowsAccountPassword)}. {ex.Message}\r\n{ex.StackTrace}");
                return false;
            }
        }

        public static bool DeleteLocalWindowsAccount(string username)
        {
            try
            {
                if (MachineUtils.IsThisMachineADomainController())
                    throw new Exception($"This computer reports to be a domain controller. User cannot be deleted.");

                var context = new PrincipalContext(ContextType.Machine, Environment.MachineName);
                if (context == null)
                    context = new PrincipalContext(ContextType.Machine);
                if (context == null)
                    throw new Exception($"Could not create instance of the PrincipalContext ({Environment.MachineName}).");

                // create the user if it does not exist
                var user = UserPrincipal.FindByIdentity(context, IdentityType.Name, username);
                if (user == null)
                    throw new Exception($"Could not find instance of the UserPrincipal ({username}).");

                // now remove user from "Users" group
                var usersGroupSID = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
                if (usersGroupSID != null)
                {
                    GroupPrincipal group = GroupPrincipal.FindByIdentity(context, IdentityType.Sid, usersGroupSID.Value);
                    if (group != null && group.Members.Contains(user))
                        group.Members.Remove(user);
                }

                user.Delete();

                return true;
            }
            catch (Exception ex)
            {
                _logger.Error($"{nameof(DeleteLocalWindowsAccount)}. {ex.Message}\r\n{ex.StackTrace}");
                return false;
            }
        }

        public static SecureString GetSecureString(string sourceString)
        {
            var secureString = new SecureString();
            foreach (var sourceChar in sourceString)
            {
                secureString.AppendChar(sourceChar);
            }
            return secureString;
        }
    }
}
