using System;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using Webserver.WebAPI;

namespace Webserver.UrlHandlers {
	public class ApiHandler : AbsHandler {
		private readonly Dictionary<string, AbsWebAPI> apis = new CaseInsensitiveStringDictionary<AbsWebAPI> ();

		public ApiHandler () : base (null) {
		}
		
		private static readonly Type[] apiWithParentCtorTypes = { typeof (Web) };
		private static readonly object[] apiWithParentCtorArgs = new object[1];
		private static readonly Type[] apiEmptyCtorTypes = { };
		private static readonly object[] apiEmptyCtorArgs = { };

		public override void SetBasePathAndParent (Web _parent, string _relativePath) {
			base.SetBasePathAndParent (_parent, _relativePath);

			apiWithParentCtorArgs[0] = _parent;

			ReflectionHelpers.FindTypesImplementingBase (typeof (AbsWebAPI), apiFoundCallback);

			// Permissions that don't map to a real API
			addApi (new Null ("viewallclaims"));
			addApi (new Null ("viewallplayers"));
		}

		private void apiFoundCallback (Type _type) {
			ConstructorInfo ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, (Binder)null,
				apiWithParentCtorTypes, (ParameterModifier[])null);
			if (ctor != null) {
				AbsWebAPI apiInstance = (AbsWebAPI)ctor.Invoke (apiWithParentCtorArgs);
				addApi (apiInstance);
				return;
			}

			ctor = _type.GetConstructor (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, (Binder)null,
				apiEmptyCtorTypes, (ParameterModifier[])null);
			if (ctor != null) {
				AbsWebAPI apiInstance = (AbsWebAPI)ctor.Invoke (apiEmptyCtorArgs);
				addApi (apiInstance);
			}
		}

		private void addApi (AbsWebAPI _api) {
			apis.Add (_api.Name, _api);
			parent.OpenApiHelpers.LoadOpenApiSpec (_api);
		}

		private static readonly UnityEngine.Profiling.CustomSampler apiHandlerSampler = UnityEngine.Profiling.CustomSampler.Create ("API_Handler");

		public override void HandleRequest (RequestContext _context) {

			string apiName;
			string subPath = null;

			int pathSeparatorIndex = _context.RequestPath.IndexOf ('/', urlBasePath.Length);
			if (pathSeparatorIndex >= 0) {
				apiName = _context.RequestPath.Substring (urlBasePath.Length, pathSeparatorIndex - urlBasePath.Length);
				subPath = _context.RequestPath.Substring (pathSeparatorIndex + 1);
			} else {
				apiName = _context.RequestPath.Substring (urlBasePath.Length);
			}
			
			if (!apis.TryGetValue (apiName, out AbsWebAPI api)) {
				Log.Warning ($"[Web] In {nameof(ApiHandler)}.HandleRequest(): No handler found for API \"{apiName}\"");
				_context.Response.StatusCode = (int) HttpStatusCode.NotFound;
				return;
			}

			_context.RequestPath = subPath;

			if (!api.Authorized (_context)) {
				_context.Response.StatusCode = (int) HttpStatusCode.Forbidden;
				if (_context.Connection != null) {
					//Log.Out ($"{nameof(ApiHandler)}: user '{user.SteamID}' not allowed to execute '{apiName}'");
				}

				return;
			}

			try {
				apiHandlerSampler.Begin ();
				api.HandleRequest (_context);
				apiHandlerSampler.End ();
			} catch (Exception e) {
				Log.Error ($"[Web] In {nameof(ApiHandler)}.HandleRequest(): Handler {api.Name} threw an exception:");
				Log.Exception (e);
				_context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
			}
		}
	}
}