using System;
using System.Collections.Generic;
using System.Net;
using JetBrains.Annotations;
using Utf8Json;
using Webserver.Permissions;

namespace Webserver.WebAPI.APIs.WorldState {
	[UsedImplicitly]
	public class Player : AbsRestApi {
		private static readonly byte[] jsonPlayersKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("players");

		protected override void HandleRestGet (RequestContext _context) {
			string id = _context.RequestPath;
			int permissionLevel = _context.PermissionLevel;
			
 			bool bViewAll = PermissionUtils.CanViewAllPlayers (permissionLevel);
            PlatformUserIdentifierAbs nativeUserId = _context.Connection?.UserId;

			// string qs;
			// if ((qs = _context.Request.QueryString ["count"]) == null || !int.TryParse (qs, out int count)) {
			// 	count = 50;
			// }

			PrepareEnvelopedResult (out JsonWriter writer);
			
			writer.WriteRaw (jsonPlayersKey);
			writer.WriteBeginArray ();

			ClientInfo ci;
			int written = 0;

			if (string.IsNullOrEmpty (id)) {
				for (int i = 0; i < ConnectionManager.Instance.Clients.List.Count; i++) {
					ClientInfo clientInfo = ConnectionManager.Instance.Clients.List [i];

					writePlayerJson (ref writer, ref written, clientInfo.PlatformId, bViewAll, nativeUserId);
				}
			} else if (int.TryParse (id, out int entityId) && (ci = ConnectionManager.Instance.Clients.ForEntityId (entityId)) != null) {
				// TODO: Allow finding offline players, also for search other than by EntityId
				writePlayerJson (ref writer, ref written, ci.PlatformId, bViewAll, nativeUserId);
			} else {
				writer.WriteEndArray ();
				writer.WriteEndObject ();
				SendEnvelopedResult (_context, ref writer, HttpStatusCode.NotFound);
				return;
			}

			writer.WriteEndArray ();
			writer.WriteEndObject ();

			SendEnvelopedResult (_context, ref writer);
		}


#region JSON keys for player result

		private static readonly byte[] jsonEntityIdKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("entityId");
		private static readonly byte[] jsonNameKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("name");
		private static readonly byte[] jsonPlatformIdKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("platformId");
		private static readonly byte[] jsonCrossplatformIdKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("crossplatformId");
		private static readonly byte[] jsonTotalPlayTimeKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("totalPlayTimeSeconds");
		private static readonly byte[] jsonLastOnlineKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("lastOnline");
		private static readonly byte[] jsonOnlineKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("online");
		private static readonly byte[] jsonIpKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("ip");
		private static readonly byte[] jsonPingKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("ping");
		private static readonly byte[] jsonPositionKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("position");
		private static readonly byte[] jsonLevelKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("level");
		private static readonly byte[] jsonHealthKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("health");
		private static readonly byte[] jsonStaminaKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("stamina");
		private static readonly byte[] jsonScoreKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("score");
		private static readonly byte[] jsonDeathsKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("deaths");
		
		private static readonly byte[] jsonKillsKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("kills");
		private static readonly byte[] jsonKillsZombiesKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("zombies");
		private static readonly byte[] jsonKillsPlayersKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("players");
		
		private static readonly byte[] jsonBannedKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("banned");
		private static readonly byte[] jsonBanActiveKey = JsonWriter.GetEncodedPropertyNameWithBeginObject ("banActive");
		private static readonly byte[] jsonBanReasonKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("reason");
		private static readonly byte[] jsonBanUntilKey = JsonWriter.GetEncodedPropertyNameWithPrefixValueSeparator ("until");

#endregion

		
		private void writePlayerJson (ref JsonWriter _writer, ref int _written, PlatformUserIdentifierAbs _nativeUserId,
			bool _allowViewAll, PlatformUserIdentifierAbs _requesterNativeUserId) {
			
			if (!_allowViewAll && (_requesterNativeUserId == null || !_requesterNativeUserId.Equals (_nativeUserId) )) {
				return;
			}
			
			ClientInfo ci = ConnectionManager.Instance.Clients.ForUserId (_nativeUserId);
			if (ci == null) {
				Log.Warning ($"[Web] Player.GET: ClientInfo null");
				return;
			}

			int entityId = ci.entityId;
			GameManager.Instance.World.Players.dict.TryGetValue (entityId, out EntityPlayer entity);

			if (entity == null) {
				Log.Warning ($"[Web] Player.GET: EntityPlayer null");
				return;
			}

			if (_written > 0) {
				_writer.WriteValueSeparator ();
			}
			_written++;

			bool online = true; // TODO

			_writer.WriteRaw (jsonEntityIdKey);
			_writer.WriteInt32 (entityId);
			
			_writer.WriteRaw (jsonNameKey);
			_writer.WriteString (ci.playerName);
			
			_writer.WriteRaw (jsonPlatformIdKey);
			JsonCommons.WritePlatformUserIdentifier (ref _writer, _nativeUserId);
			
			_writer.WriteRaw (jsonCrossplatformIdKey);
			JsonCommons.WritePlatformUserIdentifier (ref _writer, ci.CrossplatformId);
			
			_writer.WriteRaw (jsonTotalPlayTimeKey);
			//_writer.WriteLong (player.TotalPlayTime); TODO
			_writer.WriteNull ();
			
			_writer.WriteRaw (jsonLastOnlineKey);
			//JsonCommons.WriteDateTime (ref _writer, player.LastOnline); TODO
			_writer.WriteNull ();
			
			_writer.WriteRaw (jsonOnlineKey);
			_writer.WriteBoolean (online);
			
			_writer.WriteRaw (jsonIpKey);
			if (online) {
				_writer.WriteString (ci.ip);
				// TODO: Possibly show last used IP?
			} else {
				_writer.WriteNull ();
			}
			
			_writer.WriteRaw (jsonPingKey);
			if (online) {
				_writer.WriteInt32 (ci.ping);
			} else {
				_writer.WriteNull ();
			}
			
			_writer.WriteRaw (jsonPositionKey);
			if (online) {
				JsonCommons.WriteVector3 (ref _writer, entity.GetPosition ());
				// TODO: Possibly show last position?
			} else {
				_writer.WriteNull ();
			}

			_writer.WriteRaw (jsonLevelKey);
			_writer.WriteNull (); // TODO

			_writer.WriteRaw (jsonHealthKey);
			_writer.WriteInt32 (entity.Health);

			_writer.WriteRaw (jsonStaminaKey);
			_writer.WriteSingle (entity.Stamina);

			_writer.WriteRaw (jsonScoreKey);
			_writer.WriteInt32 (entity.Score);

			_writer.WriteRaw (jsonDeathsKey);
			_writer.WriteInt32 (entity.Died);

			
			_writer.WriteRaw (jsonKillsKey);
			
			_writer.WriteRaw (jsonKillsZombiesKey);
			_writer.WriteInt32 (entity.KilledZombies);

			_writer.WriteRaw (jsonKillsPlayersKey);
			_writer.WriteInt32 (entity.KilledPlayers);
			
			_writer.WriteEndObject (); // End of jsonKillsKey
			
			
			_writer.WriteRaw (jsonBannedKey);

			bool banned = GameManager.Instance.adminTools.Blacklist.IsBanned (_nativeUserId, out DateTime bannedUntil, out string banReason);
			if (!banned && ci.CrossplatformId != null) {
				banned = GameManager.Instance.adminTools.Blacklist.IsBanned (ci.CrossplatformId, out bannedUntil, out banReason);
			}

			_writer.WriteRaw (jsonBanActiveKey);
			_writer.WriteBoolean (banned);

			_writer.WriteRaw (jsonBanReasonKey);
			if (banned) {
				_writer.WriteString (banReason);
			} else {
				_writer.WriteNull ();
			}

			_writer.WriteRaw (jsonBanUntilKey);
			if (banned) {
				JsonCommons.WriteDateTime (ref _writer, bannedUntil);
			} else {
				_writer.WriteNull ();
			}

			_writer.WriteEndObject (); // End of jsonBannedKeys
			

			_writer.WriteEndObject (); // End of jsonEntityIdKey
		}

		protected override void HandleRestPost (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
			if (!JsonCommons.TryGetJsonField (_jsonInput, "command", out string commandString)) {
				SendEmptyResponse (_context, HttpStatusCode.BadRequest, _jsonInputData, "NO_COMMAND");
				return;
			}

			WebCommandResult.ResultType responseType = WebCommandResult.ResultType.Full;

			if (JsonCommons.TryGetJsonField (_jsonInput, "format", out string formatString)) {
				if (formatString.EqualsCaseInsensitive ("raw")) {
					responseType = WebCommandResult.ResultType.Raw;
				} else if (formatString.EqualsCaseInsensitive ("simple")) {
					responseType = WebCommandResult.ResultType.ResultOnly;
				}
			}

			int commandSepIndex = commandString.IndexOf (' ');
			string commandPart = commandSepIndex > 0 ? commandString.Substring (0, commandSepIndex) : commandString;
			string argumentsPart = commandSepIndex > 0
				? commandString.Substring (commandPart.Length + 1)
				: "";

			IConsoleCommand command = SdtdConsole.Instance.GetCommand (commandPart, true);

			if (command == null) {
				SendEmptyResponse (_context, HttpStatusCode.NotFound, _jsonInputData, "UNKNOWN_COMMAND");
				return;
			}

			int commandPermissionLevel = GameManager.Instance.adminTools.Commands.GetCommandPermissionLevel (command.GetCommands ());

			if (_context.PermissionLevel > commandPermissionLevel) {
				SendEmptyResponse (_context, HttpStatusCode.Forbidden, _jsonInputData, "NO_PERMISSION");
				return;
			}

			_context.Response.SendChunked = true;
			WebCommandResult wcr = new WebCommandResult (commandPart, argumentsPart, responseType, _context);
			SdtdConsole.Instance.ExecuteAsync (commandString, wcr);
		}

		public override int DefaultPermissionLevel () => AdminWebModules.PermissionLevelGuest;
	}
}