using System;
using System.DirectoryServices.AccountManagement;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Ameliorated.ConsoleUtils;

namespace amecs.Actions
{
    public class AutoLogon
    {
        internal static class SafeNativeMethods
        {
            #region Structures

            [StructLayout(LayoutKind.Sequential)]
            public struct LSA_UNICODE_STRING : IDisposable
            {
                public UInt16 Length;
                public UInt16 MaximumLength;
                public IntPtr Buffer;

                public void Dispose()
                {
                    this = new LSA_UNICODE_STRING();
                }
            }

            public struct LSA_OBJECT_ATTRIBUTES
            {
                public int Length;
                public IntPtr RootDirectory;
                public LSA_UNICODE_STRING ObjectName;
                public UInt32 Attributes;
                public IntPtr SecurityDescriptor;
                public IntPtr SecurityQualityOfService;
            }

            public enum LSA_AccessPolicy : long
            {
                POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
                POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
                POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
                POLICY_TRUST_ADMIN = 0x00000008L,
                POLICY_CREATE_ACCOUNT = 0x00000010L,
                POLICY_CREATE_SECRET = 0x00000020L,
                POLICY_CREATE_PRIVILEGE = 0x00000040L,
                POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
                POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
                POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
                POLICY_SERVER_ADMIN = 0x00000400L,
                POLICY_LOOKUP_NAMES = 0x00000800L,
                POLICY_NOTIFICATION = 0x00001000L
            }

            #endregion

            #region DLL Imports

            [DllImport("advapi32")]
            public static extern IntPtr FreeSid(IntPtr pSid);

            [DllImport("advapi32.dll", PreserveSig = true)]
            public static extern UInt32 LsaOpenPolicy(
                ref LSA_UNICODE_STRING SystemName,
                ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
                Int32 DesiredAccess,
                out IntPtr PolicyHandle);

            [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
            public static extern uint LsaStorePrivateData(
                IntPtr PolicyHandle,
                LSA_UNICODE_STRING[] KeyName,
                LSA_UNICODE_STRING[] PrivateData);

            [DllImport("advapi32.dll", PreserveSig = true)]
            public static extern uint LsaRetrievePrivateData(
                IntPtr PolicyHandle,
                LSA_UNICODE_STRING[] KeyName,
                out IntPtr PrivateData);

            [DllImport("advapi32.dll", PreserveSig = true)]
            public static extern uint LsaNtStatusToWinError(uint status);

            [DllImport("advapi32.dll")]
            public static extern uint LsaClose(IntPtr ObjectHandle);

            #endregion
        }
        
        #region Functions

        /// <summary>
        /// Store Encrypted Data
        /// </summary>
        /// <param name="keyName"></param>
        /// <param name="Data"></param>
        /// <returns></returns>
        public static long StoreData(String keyName, String Data)
        {
            long winErrorCode = 0; 
            IntPtr sid = IntPtr.Zero;
            int sidSize = 0;

            //allocate buffers
            sid = Marshal.AllocHGlobal(sidSize);

            //initialize an empty unicode-string
            SafeNativeMethods.LSA_UNICODE_STRING systemName = new SafeNativeMethods.LSA_UNICODE_STRING();
            
            //Set desired access rights (requested rights)
            int access = (int)(SafeNativeMethods.LSA_AccessPolicy.POLICY_CREATE_SECRET); 
            //initialize a pointer for the policy handle
            IntPtr policyHandle = IntPtr.Zero;

            //these attributes are not used, but LsaOpenPolicy wants them to exists
            SafeNativeMethods.LSA_OBJECT_ATTRIBUTES ObjectAttributes = new SafeNativeMethods.LSA_OBJECT_ATTRIBUTES();
            ObjectAttributes.Length = 0;
            ObjectAttributes.RootDirectory = IntPtr.Zero;
            ObjectAttributes.Attributes = 0;
            ObjectAttributes.SecurityDescriptor = IntPtr.Zero;
            ObjectAttributes.SecurityQualityOfService = IntPtr.Zero;

            //get a policy handle
            uint resultPolicy = SafeNativeMethods.LsaOpenPolicy(ref systemName, ref ObjectAttributes, access, out policyHandle);

            winErrorCode = SafeNativeMethods.LsaNtStatusToWinError(resultPolicy);

            if (winErrorCode != 0)
            {
                ConsoleTUI.OpenFrame.WriteCenteredLine("OpenPolicy failed: " + winErrorCode);
            }
            else
            {
                //initialize an unicode-string for the keyName
                SafeNativeMethods.LSA_UNICODE_STRING[] uKeyName = new SafeNativeMethods.LSA_UNICODE_STRING[1];
                uKeyName[0] = new SafeNativeMethods.LSA_UNICODE_STRING();
                uKeyName[0].Buffer = Marshal.StringToHGlobalUni(keyName);
                uKeyName[0].Length = (UInt16)(keyName.Length * UnicodeEncoding.CharSize);
                uKeyName[0].MaximumLength = (UInt16)((keyName.Length + 1) * UnicodeEncoding.CharSize);

                //initialize an unicode-string for the Data to encrypt
                SafeNativeMethods.LSA_UNICODE_STRING[] uData = new SafeNativeMethods.LSA_UNICODE_STRING[1];
                uData[0] = new SafeNativeMethods.LSA_UNICODE_STRING();
                uData[0].Buffer = Marshal.StringToHGlobalUni(Data);
                uData[0].Length = (UInt16)(Data.Length * UnicodeEncoding.CharSize);
                uData[0].MaximumLength = (UInt16)((Data.Length + 1) * UnicodeEncoding.CharSize);

                //Store Encrypted Data:
                SafeNativeMethods.LsaStorePrivateData(policyHandle, uKeyName, uData);

                //winErrorCode = LsaNtStatusToWinError(res);
                if (winErrorCode != 0)
                {
                    ConsoleTUI.OpenFrame.WriteCenteredLine("LsaStorePrivateData failed: " + winErrorCode);
                }

                SafeNativeMethods.LsaClose(policyHandle);
            }
            SafeNativeMethods.FreeSid(sid);
            return winErrorCode;
        }

        /// <summary>
        /// Retrieve Encrypted Data
        /// </summary>
        /// <param name="keyName"></param>
        /// <returns></returns>
        public static string RetrieveData(String keyName)
        {
            string sout = "";
            long winErrorCode = 0;
            IntPtr sid = IntPtr.Zero;
            int sidSize = 0;

            //allocate buffers
            sid = Marshal.AllocHGlobal(sidSize);

            //initialize an empty unicode-string
            SafeNativeMethods.LSA_UNICODE_STRING systemName = new SafeNativeMethods.LSA_UNICODE_STRING();

            //Set desired access rights (requested rights)
            int access = (int)(SafeNativeMethods.LSA_AccessPolicy.POLICY_CREATE_SECRET);
            //initialize a pointer for the policy handle
            IntPtr policyHandle = IntPtr.Zero;

            //these attributes are not used, but LsaOpenPolicy wants them to exists
            SafeNativeMethods.LSA_OBJECT_ATTRIBUTES ObjectAttributes = new SafeNativeMethods.LSA_OBJECT_ATTRIBUTES();
            ObjectAttributes.Length = 0;
            ObjectAttributes.RootDirectory = IntPtr.Zero;
            ObjectAttributes.Attributes = 0;
            ObjectAttributes.SecurityDescriptor = IntPtr.Zero;
            ObjectAttributes.SecurityQualityOfService = IntPtr.Zero;

            //get a policy handle
            uint resultPolicy = SafeNativeMethods.LsaOpenPolicy(ref systemName, ref ObjectAttributes, access, out policyHandle);

            winErrorCode = SafeNativeMethods.LsaNtStatusToWinError(resultPolicy);

            if (winErrorCode != 0)
            {
                ConsoleTUI.OpenFrame.WriteCenteredLine("OpenPolicy failed: " + winErrorCode);
            }
            else
            {
                //initialize an unicode-string for the keyName
                SafeNativeMethods.LSA_UNICODE_STRING[] uKeyName = new SafeNativeMethods.LSA_UNICODE_STRING[1];
                uKeyName[0] = new SafeNativeMethods.LSA_UNICODE_STRING();
                uKeyName[0].Buffer = Marshal.StringToHGlobalUni(keyName);
                uKeyName[0].Length = (UInt16)(keyName.Length * UnicodeEncoding.CharSize);
                uKeyName[0].MaximumLength = (UInt16)((keyName.Length + 1) * UnicodeEncoding.CharSize);

                //Store Encrypted Data:
                IntPtr pData;
                long result = SafeNativeMethods.LsaRetrievePrivateData(policyHandle, uKeyName, out pData);

                //winErrorCode = LsaNtStatusToWinError(res);
                if (winErrorCode != 0)
                {
                    ConsoleTUI.OpenFrame.WriteCenteredLine("LsaStorePrivateData failed: " + winErrorCode);
                }
                SafeNativeMethods.LSA_UNICODE_STRING ss = (SafeNativeMethods.LSA_UNICODE_STRING)Marshal.PtrToStructure(pData, typeof(SafeNativeMethods.LSA_UNICODE_STRING));
                sout = Marshal.PtrToStringAuto(ss.Buffer);

                SafeNativeMethods.LsaClose(policyHandle);
            }
            SafeNativeMethods.FreeSid(sid);


            return sout;
        }

        #endregion

        
        
        private const string LogonKey = @"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon";

        public static bool Disable() => amecs.RunBasicAction("Disabling AutoLogon", "AutoLogon disabled successfully", new Action(() =>
        {
            new Reg.Value() { KeyName = LogonKey, ValueName = "DefaultUserName", Data = "", Type = Reg.RegistryValueType.REG_SZ}.Apply();
            new Reg.Value() { KeyName = LogonKey, ValueName = "AutoAdminLogon", Operation = Reg.RegistryValueOperation.Delete}.Apply();
            new Reg.Value() { KeyName = LogonKey, ValueName = "AutoLogonCount", Operation = Reg.RegistryValueOperation.Delete}.Apply();
            new Reg.Value() { KeyName = LogonKey, ValueName = "ForceAutoLogon", Operation = Reg.RegistryValueOperation.Delete}.Apply();
            new Reg.Value() { KeyName = LogonKey, ValueName = "DisableCAD", Operation = Reg.RegistryValueOperation.Delete}.Apply();
            new Reg.Value() { KeyName = LogonKey, ValueName = "DefaultPassword", Operation = Reg.RegistryValueOperation.Delete}.Apply();
            StoreData("DefaultPassword", "");
            
            Thread.Sleep(1700);
        }));
        
        public static bool Enable()
        {
            PrincipalContext context = new PrincipalContext(ContextType.Machine);

            string password = "";
            while (true)
            {
                password = new InputPrompt() { MaskInput = true, Text = "Enter your password, or press escape to quit: " }.Start();
                if (password == null)
                    return true;

                if (String.IsNullOrEmpty(password))
                {
                    try
                    {
                        Globals.User.ChangePassword("", "");
                        break;
                    } catch {}
                }
                else if (context.ValidateCredentials(Globals.Username, password))
                    break;
                
                ConsoleTUI.OpenFrame.WriteLine("Incorrect password.");
                Console.WriteLine();
            }
            try
            {
                ConsoleTUI.OpenFrame.WriteCentered("\r\nEnabling AutoLogon");
                using (new ConsoleUtils.LoadingIndicator(true))
                {
                    new Reg.Value() { KeyName = LogonKey, ValueName = "DefaultUserName", Data = Globals.Username, Type = Reg.RegistryValueType.REG_SZ}.Apply();
                    new Reg.Value() { KeyName = LogonKey, ValueName = "DefaultDomainName", Data = Environment.MachineName, Type = Reg.RegistryValueType.REG_SZ}.Apply();
                    new Reg.Value() { KeyName = LogonKey, ValueName = "AutoAdminLogon", Data = 1, Type = Reg.RegistryValueType.REG_DWORD}.Apply();
                    new Reg.Value() { KeyName = LogonKey, ValueName = "AutoLogonCount", Operation = Reg.RegistryValueOperation.Delete}.Apply();
                    new Reg.Value() { KeyName = LogonKey, ValueName = "DisableCAD", Data = 1, Type = Reg.RegistryValueType.REG_DWORD}.Apply();
                    new Reg.Value() { KeyName = LogonKey, ValueName = "DefaultPassword", Operation = Reg.RegistryValueOperation.Delete}.Apply();
                    StoreData("DefaultPassword", password);
            
                    Thread.Sleep(1700);
                }
            } catch (Exception e)
            {
                Console.WriteLine();
                ConsoleTUI.OpenFrame.Close("Error: " + e.Message.TrimEnd('\n').TrimEnd('\r'), ConsoleColor.Red, Console.BackgroundColor, new ChoicePrompt()
                {
                    AnyKey = true,
                    Text = "Press any key to return to the Menu: "
                });
                return false;
            }

            Console.WriteLine();
            ConsoleTUI.OpenFrame.Close($"AutoLogon enabled successfully", ConsoleColor.Green, Console.BackgroundColor, new ChoicePrompt()
            {
                AnyKey = true,
                Text = "Press any key to return to the Menu: "
            });
            return true;
            
        }
    }
}