using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Utf8Json;
using Webserver.Permissions;

namespace Webserver.WebAPI {
	public abstract class AbsRestApi : AbsWebAPI {
		private static readonly UnityEngine.Profiling.CustomSampler jsonDeserializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Deserialize");

		protected AbsRestApi (string _name = null) : this(null, _name) {
		}

		protected AbsRestApi (Web _parentWeb, string _name = null) : base(_parentWeb, _name) {
		}

		protected override void RegisterPermissions () {
			AdminWebModules.Instance.AddKnownModule (new AdminWebModules.WebModule (CachedApiModuleName, DefaultPermissionLevel (),
				DefaultMethodPermissionLevels (), true));
		}

		public sealed override void HandleRequest (RequestContext _context) {
			IDictionary<string, object> inputJson = null;
			byte[] jsonInputData = null;
			
			if (_context.Request.HasEntityBody) {
				Stream requestInputStream = _context.Request.InputStream;
				
				jsonInputData = new byte[_context.Request.ContentLength64];
				requestInputStream.Read (jsonInputData, 0, (int)_context.Request.ContentLength64);
				
				try {
					jsonDeserializeSampler.Begin ();
					inputJson = JsonSerializer.Deserialize<IDictionary<string, object>> (jsonInputData);
					
					// Log.Out ("JSON body:");
					// foreach ((string key, object value) in inputJson) {
					// 	Log.Out ($" - {key} = {value} ({value.GetType ()})");
					// }
					
					jsonDeserializeSampler.End ();
				} catch (Exception e) {
					jsonDeserializeSampler.End ();

					SendErrorResult (_context, HttpStatusCode.BadRequest, null, "INVALID_BODY", e);
					return;
				}
			}

			try {
				switch (_context.Method) {
					case ERequestMethod.GET:
						if (inputJson != null) {
							SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "GET_WITH_BODY");
							return;
						}

						HandleRestGet (_context);
						return;
					case ERequestMethod.POST:
						if (!string.IsNullOrEmpty (_context.RequestPath)) {
							SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "POST_WITH_ID");
							return;
						}

						if (inputJson == null) {
							SendErrorResult (_context, HttpStatusCode.BadRequest, null, "POST_WITHOUT_BODY");
							return;
						}

						HandleRestPost (_context, inputJson, jsonInputData);
						return;
					case ERequestMethod.PUT:
						if (string.IsNullOrEmpty (_context.RequestPath)) {
							SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "PUT_WITHOUT_ID");
							return;
						}

						if (inputJson == null) {
							SendErrorResult (_context, HttpStatusCode.BadRequest, null, "PUT_WITHOUT_BODY");
							return;
						}

						HandleRestPut (_context, inputJson, jsonInputData);
						return;
					case ERequestMethod.DELETE:
						if (string.IsNullOrEmpty (_context.RequestPath)) {
							SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "DELETE_WITHOUT_ID");
							return;
						}

						if (inputJson != null) {
							SendErrorResult (_context, HttpStatusCode.BadRequest, null, "DELETE_WITH_BODY");
							return;
						}

						HandleRestDelete (_context);
						return;
					default:
						SendErrorResult (_context, HttpStatusCode.BadRequest, null, "INVALID_METHOD");
						return;
				}
			} catch (Exception e) {
				SendErrorResult (_context, HttpStatusCode.InternalServerError, jsonInputData, "ERROR_PROCESSING", e);
			}
		}

		protected virtual void HandleRestGet (RequestContext _context) {
			SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported");
		}

		protected virtual void HandleRestPost (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
			SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported");
		}

		protected virtual void HandleRestPut (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
			SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported");
		}

		protected virtual void HandleRestDelete (RequestContext _context) {
			SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported");
		}

		public override bool Authorized (RequestContext _context) {
			AdminWebModules.WebModule module = AdminWebModules.Instance.GetModule (CachedApiModuleName);

			if (module.LevelPerMethod != null) {
				int perMethodLevel = module.LevelPerMethod [(int)_context.Method];
				if (perMethodLevel == AdminWebModules.MethodLevelNotSupported) {
					return false;
				}

				if (perMethodLevel != AdminWebModules.MethodLevelInheritGlobal) {
					return perMethodLevel >= _context.PermissionLevel;
				}
			}

			return module.LevelGlobal >= _context.PermissionLevel;
		}

		/// <summary>
		/// Define default permission levels per HTTP method as an array of levels. See <see cref="ERequestMethod"/> for the order of entries.
		/// </summary>
		/// <returns>Default permission levels for supported methods. See <see cref="AdminWebModules.MethodLevelNotSupported"/> and <see cref="AdminWebModules.MethodLevelInheritGlobal"/>.</returns>
		public virtual int[] DefaultMethodPermissionLevels () => new[] {
			AdminWebModules.MethodLevelNotSupported,
			AdminWebModules.MethodLevelInheritGlobal,
			AdminWebModules.MethodLevelInheritGlobal,
			AdminWebModules.MethodLevelInheritGlobal,
			AdminWebModules.MethodLevelInheritGlobal
		};

#region Helpers

		protected static readonly byte[] JsonEmptyData;
		
		static AbsRestApi () {
			JsonWriter writer = new JsonWriter ();
			writer.WriteBeginArray ();
			writer.WriteEndArray ();
			JsonEmptyData = writer.ToUtf8ByteArray ();
		}

		protected static void PrepareEnvelopedResult (out JsonWriter _writer) {
			WebUtils.PrepareEnvelopedResult (out _writer);
		}

		protected static void SendEnvelopedResult (RequestContext _context, ref JsonWriter _writer, HttpStatusCode _statusCode = HttpStatusCode.OK,
			byte[] _jsonInputData = null, string _errorCode = null, Exception _exception = null) {
			
			WebUtils.SendEnvelopedResult (_context, ref _writer, _statusCode, _jsonInputData, _errorCode, _exception);
		}

		protected static void SendErrorResult (RequestContext _context, HttpStatusCode _statusCode, byte[] _jsonInputData = null, string _errorCode = null, Exception _exception = null) {
			PrepareEnvelopedResult (out JsonWriter writer);
			writer.WriteRaw (JsonEmptyData);
			SendEnvelopedResult (_context, ref writer, _statusCode, _jsonInputData, _errorCode, _exception);
		}

		protected static bool TryGetJsonField (IDictionary<string, object> _jsonObject, string _fieldName, out int _value) {
			_value = default;
			
			if (!_jsonObject.TryGetValue (_fieldName, out object fieldNode)) {
				return false;
			}

			if (fieldNode is not double value) {
				return false;
			}

			try {
				_value = (int)value;
				return true;
			} catch (Exception) {
				return false;
			}
		}

		protected static bool TryGetJsonField (IDictionary<string, object> _jsonObject, string _fieldName, out double _value) {
			_value = default;
			
			if (!_jsonObject.TryGetValue (_fieldName, out object fieldNode)) {
				return false;
			}

			if (fieldNode is not double value) {
				return false;
			}

			try {
				_value = value;
				return true;
			} catch (Exception) {
				return false;
			}
		}

		protected static bool TryGetJsonField (IDictionary<string, object> _jsonObject, string _fieldName, out string _value) {
			_value = default;
			
			if (!_jsonObject.TryGetValue (_fieldName, out object fieldNode)) {
				return false;
			}

			if (fieldNode is not string value) {
				return false;
			}

			try {
				_value = value;
				return true;
			} catch (Exception) {
				return false;
			}
		}
		

#endregion
	}
}