using System.Collections.Generic;
using System.Xml;
using UnityEngine;

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

		public AdminWebModules (AdminTools _parent) : base (_parent, "webmodules") {
			Instance = this;
		}

		private readonly Dictionary<string, WebModule> modules = new CaseInsensitiveStringDictionary<WebModule> ();


#region IO

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

		protected override void ParseElement (XmlElement _childElement) {
			if (WebModule.TryParse (_childElement, out WebModule webModule)) {
				modules [webModule.Name] = webModule;
			}
		}

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

			foreach ((string _, WebModule module) in modules) {
				module.ToXml (modulesElement);
			}
		}

#endregion


#region Runtime interaction


		public void AddModule (WebModule _module) {
			lock (this) {
				allModulesList.Clear ();
				
				modules [_module.Name] = _module;
				Parent.Save ();
			}
		}

		public bool RemoveModule (string _module) {
			lock (this) {
				allModulesList.Clear ();
				
				bool removed = modules.Remove (_module);
				if (removed) {
					Parent.Save ();
				}

				return removed;
			}
		}

		public List<WebModule> GetModules () {
			lock (this) {
				if (allModulesList.Count != 0) {
					return allModulesList;
				}

				foreach ((string moduleName, WebModule moduleDefaultPerm) in knownModules) {
					allModulesList.Add (modules.TryGetValue (moduleName, out WebModule modulePermission)
						? modulePermission
						: moduleDefaultPerm);
				}

				return allModulesList;
			}
		}

#endregion
		
		public struct WebModule {
			public string Name;
			public int LevelGlobal;
			public int[] LevelPerMethod;
			public bool IsDefault;

			public WebModule (string _name, int _level, bool _isDefault = false) {
				LevelPerMethod = null;
				
				Name = _name;
				LevelGlobal = _level;
				IsDefault = _isDefault;
			}

			public WebModule (string _name, int _levelGlobal, int[] _levelPerMethod, bool _isDefault = false) {
				if (_levelPerMethod == null || _levelPerMethod.Length != (int)ERequestMethod.Count) {
					LevelPerMethod = createDefaultPerMethodArray ();

					for (int i = 0; i < (int)ERequestMethod.Count; i++) {
						if (_levelPerMethod != null && i < _levelPerMethod.Length) {
							LevelPerMethod [i] = _levelPerMethod [i];
						}
					}
				} else {
					LevelPerMethod = _levelPerMethod;
				}

				Name = _name;
				LevelGlobal = _levelGlobal;
				IsDefault = _isDefault;
			}
			
			public void ToXml (XmlElement _parent) {
				bool hasPerMethodLevels = LevelPerMethod != null;

				XmlElement permissionElement = _parent.AddXmlElement ("module")
					.SetAttrib ("name", Name)
					.SetAttrib ("permission_level", LevelGlobal.ToString ());
				
				if (!hasPerMethodLevels) {
					return;
				}

				for (int i = 0; i < LevelPerMethod.Length; i++) {
					ERequestMethod method = (ERequestMethod)i;
					int level = LevelPerMethod [i];
					
					if (level == MethodLevelNotSupported) {
						continue;
					}

					permissionElement.AddXmlElement ("method")
						.SetAttrib ("name", method.ToStringCached ())
						.SetAttrib ("permission_level", level.ToString ());
				}
			}

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


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

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

				if (!int.TryParse (permissionLevelString, out int permissionLevel)) {
					Log.Warning (
						$"[Web] [Perms] Ignoring module-entry because of invalid (non-numeric) value for 'permission_level' attribute: {_element.OuterXml}");
					return false;
				}

				int[] perMethodLevels = null;
				
				foreach (XmlNode child in _element.ChildNodes) {
					if (child.NodeType != XmlNodeType.Element) {
						continue;
					}

					XmlElement childElem = (XmlElement)child;
					if (childElem.Name != "method") {
						Log.Warning ($"[Web] [Perms] Ignoring module child element, invalid element name: {childElem.OuterXml}");
						continue;
					}

					if (!childElem.TryGetAttribute ("name", out string methodName)) {
						Log.Warning ($"[Web] [Perms] Ignoring module child element, missing 'name' attribute: {childElem.OuterXml}");
						continue;
					}

					if (!EnumUtils.TryParse (methodName, out ERequestMethod method, true)) {
						Log.Warning (
							$"[Web] [Perms] Ignoring module child element, unknown method name in 'name' attribute: {childElem.OuterXml}");
						continue;
					}

					if (method >= ERequestMethod.Count) {
						Log.Warning (
							$"[Web] [Perms] Ignoring module child element, invalid method name in 'name' attribute: {childElem.OuterXml}");
						continue;
					}
					
					if (!childElem.TryGetAttribute ("permission_level", out permissionLevelString)) {
						Log.Warning ($"[Web] [Perms] Ignoring module child element, missing 'permission_level' attribute: {childElem.OuterXml}");
						continue;
					}

					if (!int.TryParse (permissionLevelString, out int methodPermissionLevel)) {
						Log.Warning (
							$"[Web] [Perms] Ignoring module child element, invalid (non-numeric) value for 'permission_level' attribute: {childElem.OuterXml}");
						continue;
					}

					perMethodLevels ??= createDefaultPerMethodArray ();
					perMethodLevels [(int)method] = methodPermissionLevel;
				}

				_result = perMethodLevels != null ? new WebModule (name, permissionLevel, perMethodLevels) : new WebModule (name, permissionLevel);

				return true;
			}

			private static int[] createDefaultPerMethodArray () {
				int[] result = new int[(int)ERequestMethod.Count];

				for (int i = 0; i < (int)ERequestMethod.Count; i++) {
					result [i] = MethodLevelNotSupported;
				}

				return result;
			}
		}


#region Specials

		/// <summary>
		/// Use global (API based) permission level for the method
		/// </summary>
		public const int MethodLevelInheritGlobal = int.MinValue;
		
		/// <summary>
		/// Method not supported
		/// </summary>
		public const int MethodLevelNotSupported = int.MinValue + 1;

		public const int PermissionLevelUser = 1000;
		public const int PermissionLevelGuest = 2000;

		/// <summary>
		/// Contains all registered modules and their default permission
		/// </summary>
		private readonly Dictionary<string, WebModule> knownModules = new CaseInsensitiveStringDictionary<WebModule> ();

		/// <summary>
		/// Public list of all modules, both those with custom permissions as well as those that do not with their default permission
		/// </summary>
		private readonly List<WebModule> allModulesList = new List<WebModule> ();

		public void AddKnownModule (WebModule _module) {
			if (string.IsNullOrEmpty (_module.Name)) {
				return;
			}

			_module.IsDefault = true;
			
			lock (this) {
				allModulesList.Clear ();
				knownModules [_module.Name] = _module;
			}
		}

		public bool IsKnownModule (string _module) {
			if (string.IsNullOrEmpty (_module)) {
				return false;
			}

			lock (this) {
				return knownModules.ContainsKey (_module);
			}
		}

		public bool ModuleAllowedWithLevel (string _module, int _level) {
			return GetModule (_module).LevelGlobal >= _level;
		}

		public WebModule GetModule (string _module) {
			if (modules.TryGetValue (_module, out WebModule result)) {
				return result;
			}

			return knownModules.TryGetValue (_module, out result) ? result : defaultModulePermission;
		}

		private readonly WebModule defaultModulePermission = new WebModule ("", 0, true);
		
#endregion
	}
}