using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Vanara.PInvoke;
using static Vanara.PInvoke.AdvApi32;

namespace Vanara.Security.AccessControl
{
	/// <summary>Privilege determining the type of system operations that can be performed.</summary>
	[TypeConverter(typeof(SystemPrivilegeTypeConverter))]
	public enum SystemPrivilege
	{
		/// <summary>Required to logon interactively.</summary>
		InteractiveLogon = 0x00000001,

		/// <summary>Required for an account to log on using the network logon type.</summary>
		NetworkLogon = 0x00000002,

		/// <summary>Required for an account to log on using the batch logon type.</summary>
		BatchLogon = 0x00000004,

		/// <summary>Required for an account to log on using the service logon type.</summary>
		ServiceLogon = 0x00000010,

		/// <summary>Explicitly denies an account the right to log on using the interactive logon type.</summary>
		DenyInteractiveLogon = 0x00000040,

		/// <summary>Explicitly denies an account the right to log on using the network logon type.</summary>
		DenyNetworkLogon = 0x00000080,

		/// <summary>Explicitly denies an account the right to log on using the batch logon type.</summary>
		DenyBatchLogon = 0x00000100,

		/// <summary>Explicitly denies an account the right to log on using the service logon type.</summary>
		DenyServiceLogon = 0x00000200,

		/// <summary>Remote interactive logon</summary>
		RemoteInteractiveLogon = 0x00000400,

		/// <summary>Explicitly denies an account the right to log on remotely using the interactive logon type.</summary>
		DenyRemoteInteractiveLogon = 0x00000800,

		/// <summary>Privilege to replace a process-level token.</summary>
		AssignPrimaryToken = 0x00001000,

		/// <summary>Privilege to generate security audits.</summary>
		Audit,

		/// <summary>Privilege to backup files and directories.</summary>
		Backup,

		/// <summary>Privilege to bypass traverse checking.</summary>
		ChangeNotify,

		/// <summary>Privilege to create global objects.</summary>
		CreateGlobal,

		/// <summary>Privilege to create a pagefile.</summary>
		CreatePageFile,

		/// <summary>Privilege to create permanent shared objects.</summary>
		CreatePermanent,

		/// <summary>Privilege to create symbolic links.</summary>
		CreateSymbolicLink,

		/// <summary>Privilege to create a token object.</summary>
		CreateToken,

		/// <summary>Privilege to debug programs.</summary>
		Debug,

		/// <summary>Privilege to delegate session using user impersonation.</summary>
		DelegateSessionUserImpersonate,

		/// <summary>Privilege to enable computer and user accounts to be trusted for delegation.</summary>
		EnableDelegation,

		/// <summary>Privilege to impersonate a client after authentication.</summary>
		Impersonate,

		/// <summary>Privilege to increase scheduling priority.</summary>
		IncreaseBasePriority,

		/// <summary>Privilege to adjust memory quotas for a process.</summary>
		IncreaseQuota,

		/// <summary>Privilege to increase a process working set.</summary>
		IncreaseWorkingSet,

		/// <summary>Privilege to load and unload device drivers.</summary>
		LoadDriver,

		/// <summary>Privilege to lock pages in memory.</summary>
		LockMemory,

		/// <summary>Privilege to add workstations to domain.</summary>
		MachineAccount,

		/// <summary>Privilege to manage the files on a volume.</summary>
		ManageVolume,

		/// <summary>Privilege to profile single process.</summary>
		ProfileSingleProcess,

		/// <summary>Privilege to modify an object label.</summary>
		Relabel,

		/// <summary>Privilege to force shutdown from a remote system.</summary>
		RemoteShutdown,

		/// <summary>Privilege to restore files and directories.</summary>
		Restore,

		/// <summary>Privilege to manage auditing and security log.</summary>
		Security,

		/// <summary>Privilege to shut down the system.</summary>
		Shutdown,

		/// <summary>Privilege to synchronize directory service data.</summary>
		SyncAgent,

		/// <summary>Privilege to modify firmware environment values.</summary>
		SystemEnvironment,

		/// <summary>Privilege to profile system performance.</summary>
		SystemProfile,

		/// <summary>Privilege to change the system time.</summary>
		SystemTime,

		/// <summary>Privilege to take ownership of files or other objects.</summary>
		TakeOwnership,

		/// <summary>Privilege to act as part of the operating system.</summary>
		TrustedComputerBase,

		/// <summary>Privilege to change the time zone.</summary>
		TimeZone,

		/// <summary>Privilege to access Credential Manager as a trusted caller.</summary>
		TrustedCredentialManagerAccess,

		/// <summary>Privilege to remove computer from docking station.</summary>
		Undock,

		/// <summary>Privilege to read unsolicited input from a terminal device.</summary>
		UnsolicitedInput
	}

	/// <summary>Extension methods for <see cref="SafeHTOKEN"/> for working with privileges.</summary>
	public static class PrivilegeExtension
	{
		/// <summary>Adjusts a specified privilege on a security token to have the supplied attributes.</summary>
		/// <param name="hObj">The security token object.</param>
		/// <param name="priv">The privilege to adjust.</param>
		/// <param name="attr">The attribute to change.</param>
		/// <returns>The privious privileges.</returns>
		public static TOKEN_PRIVILEGES AdjustPrivilege(this SafeHTOKEN hObj, SystemPrivilege priv, PrivilegeAttributes attr) => AdjustPrivileges(hObj, new TOKEN_PRIVILEGES(priv.GetLUID(), attr));

		/// <summary>Adjusts the specified privileges on a security token to have the supplied attributes.</summary>
		/// <param name="hObj">The security token object.</param>
		/// <param name="privileges">The privileges to change.</param>
		/// <returns>The privious privileges.</returns>
		public static TOKEN_PRIVILEGES AdjustPrivileges(this SafeHTOKEN hObj, params PrivilegeAndAttributes[] privileges)
		{
			if (privileges == null || privileges.Length == 0) throw new ArgumentNullException(nameof(privileges));
			return AdjustPrivileges(hObj, new TOKEN_PRIVILEGES(privileges.Select(pa => new LUID_AND_ATTRIBUTES(pa.Privilege.GetLUID(), pa.Attributes)).ToArray()));
		}

		/// <summary>Adjusts the specified privileges on a security token to have the supplied attributes.</summary>
		/// <param name="hObj">The security token object.</param>
		/// <param name="privileges">The privileges to change.</param>
		/// <returns>The privious privileges.</returns>
		public static TOKEN_PRIVILEGES AdjustPrivileges(this SafeHTOKEN hObj, in TOKEN_PRIVILEGES privileges) { AdjustTokenPrivileges(hObj, false, privileges, out var ret).ThrowIfFailed(); return ret; }

		/// <summary>Gets the <see cref="LUID"/> for this <see cref="SystemPrivilege"/>.</summary>
		/// <param name="systemPrivilege">The system privilege to lookup.</param>
		/// <param name="systemName">
		/// Name of the system on which to check for the LUID value. A <see langword="null"/> will use the local system.
		/// </param>
		/// <returns>The associated LUID.</returns>
		public static LUID GetLUID(this SystemPrivilege systemPrivilege, string systemName = null) => SystemPrivilegeTypeConverter.GetLUID(systemPrivilege, systemName);

		/// <summary>Gets the <see cref="SystemPrivilege"/> value for this <see cref="LUID"/>.</summary>
		/// <param name="luid">The luid.</param>
		/// <param name="systemName">
		/// Name of the system on which to check for the LUID value. A <see langword="null"/> will use the local system.
		/// </param>
		/// <returns>The associated <see cref="SystemPrivilege"/>.</returns>
		public static SystemPrivilege GetPrivilege(this LUID luid, string systemName = null) => SystemPrivilegeTypeConverter.GetPrivilege(luid, systemName);

		/// <summary>Enumerates all the privileges held by a security token.</summary>
		/// <param name="hObj">The security token object.</param>
		/// <returns>A sequence of held privileges.</returns>
		public static IEnumerable<LUID_AND_ATTRIBUTES> GetPrivileges(this SafeHTOKEN hObj) =>
			hObj.GetInfo<TOKEN_PRIVILEGES>(TOKEN_INFORMATION_CLASS.TokenPrivileges).Privileges;

		/// <summary>Determines whether the specified security token has a privilege.</summary>
		/// <param name="hObj">The security token object.</param>
		/// <param name="priv">The privilege to assess.</param>
		/// <returns><see langword="true"/> if the specified token has the supplied privilege; otherwise, <see langword="false"/>.</returns>
		public static bool HasPrivilege(this SafeHTOKEN hObj, SystemPrivilege priv) => HasPrivileges(hObj, true, priv);

		/// <summary>Determines whether the specified security token has multiple privileges.</summary>
		/// <param name="hObj">The security token object.</param>
		/// <param name="requireAll">
		/// if set to <see langword="true"/> this method will only return <see langword="true"/> if all supplied privileges all held.
		/// </param>
		/// <param name="privs">The privileges to check.</param>
		/// <returns><see langword="true"/> if the specified token has the supplied privileges; otherwise, <see langword="false"/>.</returns>
		public static bool HasPrivileges(this SafeHTOKEN hObj, bool requireAll, params SystemPrivilege[] privs)
		{
			if (!PrivilegeCheck(hObj, new PRIVILEGE_SET((PrivilegeSetControl)(requireAll ? 1 : 0), privs.Select(p => new LUID_AND_ATTRIBUTES(p.GetLUID(), PrivilegeAttributes.SE_PRIVILEGE_ENABLED)).ToArray()), out var ret))
				Win32Error.ThrowLastError();
			return ret;
		}
	}

	internal class SystemPrivilegeTypeConverter : TypeConverter
	{
		internal static readonly Dictionary<SystemPrivilege, string> PrivLookup =
			new Dictionary<SystemPrivilege, string>(35)
		{
			{ SystemPrivilege.AssignPrimaryToken, "SeAssignPrimaryTokenPrivilege" },
			{ SystemPrivilege.Audit, "SeAuditPrivilege" },
			{ SystemPrivilege.Backup, "SeBackupPrivilege" },
			{ SystemPrivilege.ChangeNotify, "SeChangeNotifyPrivilege" },
			{ SystemPrivilege.CreateGlobal, "SeCreateGlobalPrivilege" },
			{ SystemPrivilege.CreatePageFile, "SeCreatePagefilePrivilege" },
			{ SystemPrivilege.CreatePermanent, "SeCreatePermanentPrivilege" },
			{ SystemPrivilege.CreateSymbolicLink, "SeCreateSymbolicLinkPrivilege" },
			{ SystemPrivilege.CreateToken, "SeCreateTokenPrivilege" },
			{ SystemPrivilege.Debug, "SeDebugPrivilege" },
			{ SystemPrivilege.DelegateSessionUserImpersonate, "SeDelegateSessionUserImpersonatePrivilege" },
			{ SystemPrivilege.EnableDelegation, "SeEnableDelegationPrivilege" },
			{ SystemPrivilege.Impersonate, "SeImpersonatePrivilege" },
			{ SystemPrivilege.IncreaseBasePriority, "SeIncreaseBasePriorityPrivilege" },
			{ SystemPrivilege.IncreaseQuota, "SeIncreaseQuotaPrivilege" },
			{ SystemPrivilege.IncreaseWorkingSet, "SeIncreaseWorkingSetPrivilege" },
			{ SystemPrivilege.LoadDriver, "SeLoadDriverPrivilege" },
			{ SystemPrivilege.LockMemory, "SeLockMemoryPrivilege" },
			{ SystemPrivilege.MachineAccount, "SeMachineAccountPrivilege" },
			{ SystemPrivilege.ManageVolume, "SeManageVolumePrivilege" },
			{ SystemPrivilege.ProfileSingleProcess, "SeProfileSingleProcessPrivilege" },
			{ SystemPrivilege.Relabel, "SeRelabelPrivilege" },
			{ SystemPrivilege.RemoteShutdown, "SeRemoteShutdownPrivilege" },
			{ SystemPrivilege.Restore, "SeRestorePrivilege" },
			{ SystemPrivilege.Security, "SeSecurityPrivilege" },
			{ SystemPrivilege.Shutdown, "SeShutdownPrivilege" },
			{ SystemPrivilege.SyncAgent, "SeSyncAgentPrivilege" },
			{ SystemPrivilege.SystemEnvironment, "SeSystemEnvironmentPrivilege" },
			{ SystemPrivilege.SystemProfile, "SeSystemProfilePrivilege" },
			{ SystemPrivilege.SystemTime, "SeSystemTimePrivilege" },
			{ SystemPrivilege.TakeOwnership, "SeTakeOwnershipPrivilege" },
			{ SystemPrivilege.TrustedComputerBase, "SeTcbPrivilege" },
			{ SystemPrivilege.TimeZone, "SeTimeZonePrivilege" },
			{ SystemPrivilege.TrustedCredentialManagerAccess, "SeTrustedCredManAccessPrivilege" },
			{ SystemPrivilege.Undock, "SeUndockPrivilege" },
			{ SystemPrivilege.UnsolicitedInput, "SeUnsolicitedInputPrivilege" },
			{ SystemPrivilege.InteractiveLogon, "SeInteractiveLogonRight" },
			{ SystemPrivilege.NetworkLogon, "SeNetworkLogonRight" },
			{ SystemPrivilege.BatchLogon, "SeBatchLogonRight" },
			{ SystemPrivilege.ServiceLogon, "SeServiceLogonRight" },
			{ SystemPrivilege.DenyInteractiveLogon, "SeDenyInteractiveLogonRight" },
			{ SystemPrivilege.DenyNetworkLogon, "SeDenyNetworkLogonRight" },
			{ SystemPrivilege.DenyBatchLogon, "SeDenyBatchLogonRight" },
			{ SystemPrivilege.DenyServiceLogon, "SeDenyServiceLogonRight" },
			{ SystemPrivilege.RemoteInteractiveLogon, "SeRemoteInteractiveLogonRight" },
			{ SystemPrivilege.DenyRemoteInteractiveLogon, "SeDenyRemoteInteractiveLogonRight" },
		};

		private static readonly Dictionary<SystemPrivilege, LUID> luidLookup = new Dictionary<SystemPrivilege, LUID>();

		public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) =>
			sourceType == typeof(string) || sourceType == typeof(LUID) || base.CanConvertFrom(context, sourceType);

		public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) =>
			destinationType == typeof(string) || destinationType == typeof(LUID) || base.CanConvertTo(context, destinationType);

		public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
		{
			if (value is string s)
			{
				try { var val = (SystemPrivilege)Enum.Parse(typeof(SystemPrivilege), s, true); return val; } catch { }
				try { return ConvertKnownString(s); } catch { }
			}
			if (value is LUID luid)
				return GetPrivilege(luid);
			return base.ConvertFrom(context, culture, value);
		}

		public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
		{
			if (destinationType == typeof(string))
				return PrivLookup[(SystemPrivilege)value];
			if (destinationType == typeof(LUID))
				return GetLUID((SystemPrivilege)value);
			return base.ConvertTo(context, culture, value, destinationType);
		}

		internal static SystemPrivilege ConvertKnownString(string s)
		{
			try { return PrivLookup.First(v => string.Equals(s, v.Value, StringComparison.OrdinalIgnoreCase)).Key; }
			catch { throw new ArgumentOutOfRangeException(nameof(s), "Unrecognized privilege string."); }
		}

		internal static LUID GetLUID(SystemPrivilege systemPrivilege, string systemName = null)
		{
			LUID val;
			lock (luidLookup)
			{
				if (!luidLookup.TryGetValue(systemPrivilege, out val))
				{
					val = LUID.FromName(PrivLookup[systemPrivilege], systemName);
					luidLookup.Add(systemPrivilege, val);
				}
			}
			return val;
		}

		internal static SystemPrivilege GetPrivilege(LUID luid, string systemName = null) => ConvertKnownString(luid.GetName(systemName));
	}
}