using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace Webserver.Permissions {
	public class AdminWebUsers : AdminSectionAbs {
		public static AdminWebUsers Instance { get; private set; }

		public AdminWebUsers (AdminTools _parent) : base (_parent, "webusers") {
			Instance = this;
		}

		private readonly Dictionary<string, WebUser> users = new CaseInsensitiveStringDictionary<WebUser> ();


#region IO

		public override void Clear () {
			users.Clear ();
		}

		public override void ParseElement (XmlElement _childElement) {
			if (WebUser.TryParse (_childElement, out WebUser webUser)) {
				users [webUser.Name] = webUser;
			}
		}

		public override void Save (XmlElement _root) {
			XmlElement usersElement = _root.AddXmlElement (SectionTypeName);
			// modulesElement.AddXmlComment (" <module name=\"adminuser1\" secret=\"supersecrettoken\" permission_level=\"0\" /> ");

			foreach ((string _, WebUser module) in users) {
				module.ToXml (usersElement);
			}
		}

#endregion


#region Runtime interaction


		public void AddUser (string _name, string _password, PlatformUserIdentifierAbs _userIdentifier, PlatformUserIdentifierAbs _crossPlatformIdentifier) {
			lock (Parent) {
				WebUser p = new WebUser (_name, _password, _userIdentifier, _crossPlatformIdentifier);

				// TODO: Check if another name exists with the same (crossplatform)identifier, remove that
				users[_name] = p;

				Parent.Save ();
			}
		}

		public bool RemoveUser (string _name) {
			lock (Parent) {
				bool removed = users.Remove (_name);
				if (removed) {
					Parent.Save ();
				}

				return removed;
			}
		}

		public Dictionary<string, WebUser> GetUsers () {
			lock (Parent) {
				return users;
			}
		}

#endregion

		public readonly struct WebUser {
			public readonly string Name;
			public readonly byte[] PasswordHash;
			public readonly PlatformUserIdentifierAbs PlatformUser;
			public readonly PlatformUserIdentifierAbs CrossPlatformUser;

			public WebUser (string _name, byte[] _passwordHash, PlatformUserIdentifierAbs _platformUser, PlatformUserIdentifierAbs _crossPlatformUser) {
				Name = _name;
				PasswordHash = _passwordHash;
				PlatformUser = _platformUser;
				CrossPlatformUser = _crossPlatformUser;
			}
			
			public WebUser (string _name, string _password, PlatformUserIdentifierAbs _platformUser, PlatformUserIdentifierAbs _crossPlatformUser) {
				Name = _name;
				PasswordHash = Hash (_password);
				PlatformUser = _platformUser;
				CrossPlatformUser = _crossPlatformUser;
			}

			public void ToXml (XmlElement _parent) {
				XmlElement elem = _parent.AddXmlElement ("user");
				elem.SetAttrib ("name", Name)
					.SetAttrib ("pass", Convert.ToBase64String(PasswordHash));
				PlatformUser.ToXml (elem);
				CrossPlatformUser?.ToXml (elem, "cross");
			}

			public static bool TryParse (XmlElement _element, out WebUser _result) {
				_result = default;

				if (!_element.TryGetAttribute ("name", out string name)) {
					Log.Warning ($"[Web] [Perms] Ignoring user-entry because of missing 'name' attribute: {_element.OuterXml}");
					return false;
				}

				if (!_element.TryGetAttribute ("pass", out string passHashString)) {
					Log.Warning ($"[Web] [Perms] Ignoring user-entry because of missing 'pass' attribute: {_element.OuterXml}");
					return false;
				}

				PlatformUserIdentifierAbs userIdentifier = PlatformUserIdentifierAbs.FromXml (_element, false);
				if (userIdentifier == null) {
					Log.Warning ($"[Web] [Perms] Ignoring user-entry because of missing 'platform' or 'userid' attribute: {_element.OuterXml}");
					return false;
				}

				PlatformUserIdentifierAbs crossIdentifier = PlatformUserIdentifierAbs.FromXml (_element, false, "cross");

				byte[] passHash = Convert.FromBase64String (passHashString);

				_result = new WebUser (name, passHash, userIdentifier, crossIdentifier);
				return true;
			}

			public bool ValidatePassword (string _password) {
				byte[] input = Hash (_password);
				return Utils.ArrayEquals (input, PasswordHash);
			}
		}

		private static byte[] Hash (string _input) {
			System.Security.Cryptography.MD5Cng hasherMD5 = new System.Security.Cryptography.MD5Cng();

			return hasherMD5.ComputeHash (Encoding.UTF8.GetBytes (_input));
		}

		public bool TryGetUser (string _name, string _password, out WebUser _result) {
			lock (Parent) {
				if (users.TryGetValue (_name, out _result) && _result.ValidatePassword (_password)) {
					return true;
				}

				_result = default;
				return false;
			}
		}

		public bool HasUser (PlatformUserIdentifierAbs _platformUser, PlatformUserIdentifierAbs _crossPlatformUser, out WebUser _result) {
			lock (Parent) {
				_result = default;

				foreach ((string _, WebUser webUser) in users) {
					if (!Equals (webUser.PlatformUser, _platformUser) ||
					    !Equals (webUser.CrossPlatformUser, _crossPlatformUser)) {
						continue;
					}

					_result = webUser;
					return true;
				}

				return false;
			}
		}

	}
}