using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using NeoSmart.Unicode;
using static TdLib.TdApi;
using static telegram.tgcli;

namespace telegram {
	public class Util {
		public static class Ansi {
			public const string ResetAll = "\x1B[0m";
			public const string Red      = "\x1b[31m";
			public const string Green    = "\x1b[32m";
			public const string Yellow   = "\x1b[33m";
			public const string Blue     = "\x1b[34m";
			public const string Magenta  = "\x1b[35m";
			public const string Cyan     = "\x1b[36m";
			public const string Bold     = "\x1b[1m";
			public const string BoldOff  = "\x1b[22m";
		}

		public static User GetUser(int uid) {
			try {
				var uinfo = client.ExecuteAsync(new GetUser {UserId = uid}).Result;
				return uinfo;
			}
			catch {
				var user = new User();
				user.FirstName = "null";
				user.LastName  = "null";
				return user;
			}
		}

		public static Chat GetChat(long chatId) {
			try {
				return client.ExecuteAsync(new GetChat {ChatId = chatId}).Result;
			}
			catch {
				return null;
			}
		}

		public static User GetMe() {
			return client.ExecuteAsync(new GetMe()).Result;
		}

		public static Message GetMessage(long chatId, long messageId) {
			return client.ExecuteAsync(new GetMessage {ChatId = chatId, MessageId = messageId}).Result;
		}

		public static int GetTotalMessages(long chatId) {
			try {
				var response = client.ExecuteAsync(new SearchChatMessages {ChatId = chatId, Query = "+", Limit = 1});
				return response.Result.TotalCount;
			}
			catch {
				return 9999;
			}
		}

		public static List<Message> GetHistory(long chatId, int limit = 5, long fromMessageId = 0, int offset = 0, bool isSecret = false,
											   bool skipTotal = false) {
			var history = new List<Message>();
			var total   = GetTotalMessages(chatId);
			var chat    = GetChat(chatId);
			if (chat.Type is ChatType.ChatTypeSupergroup || isSecret)
				skipTotal = true;
			if (limit > total && !skipTotal)
				limit = total;

			for (var i = 5; i > 0; i--) {
				if (limit <= 0) {
					if (total == 0)
						return history;

					lock (@lock)
						messageQueue.Add($"{Ansi.Red}[tgcli] " + $"Limit cannot be less than one. Usage: /history <count>");
					return history;
				}

				var response = client.ExecuteAsync(new GetChatHistory {
										 ChatId        = chatId,
										 FromMessageId = fromMessageId,
										 Limit         = limit,
										 Offset        = offset,
										 OnlyLocal     = false
									 })
									 .Result;

				if (response.Messages_.Length < limit && i > 1 && !isSecret) {
					Thread.Sleep(100);
					continue;
				}

				history.AddRange(response.Messages_);
				history.Reverse();
				return history;
			}

			return history;
		}

		public static List<Chat> GetUnreadChats(bool all = false) {
			var output = new List<Chat>();
			var offset = 0L;
			while (true) {
				if (offset == 0) {
					var response = client.ExecuteAsync(new GetChats {OffsetOrder = long.MaxValue, Limit = int.MaxValue}).Result;
					offset = GetChat(response.ChatIds.Last()).Order;
					output.AddRange(all
										? response.ChatIds.Select(GetChat).Where(c => c.UnreadCount > 0 || c.IsMarkedAsUnread).ToList()
										: response.ChatIds.Select(GetChat)
												  .Where(c => (c.UnreadCount > 0 || c.IsMarkedAsUnread) && c.NotificationSettings.MuteFor == 0)
												  .ToList());
				}
				else {
					var response = client.ExecuteAsync(new GetChats {OffsetOrder = offset, Limit = int.MaxValue}).Result;
					if (response.ChatIds.Length == 0)
						break;

					offset = GetChat(response.ChatIds.Last()).Order;
					output.AddRange(all
										? response.ChatIds.Select(GetChat).Where(c => c.UnreadCount > 0 || c.IsMarkedAsUnread).ToList()
										: response.ChatIds.Select(GetChat)
												  .Where(c => (c.UnreadCount > 0 || c.IsMarkedAsUnread) && c.NotificationSettings.MuteFor == 0)
												  .ToList());
				}
			}

			return output;
		}

		public static List<Chat> GetChats() {
			var response = client.ExecuteAsync(new GetChats {OffsetOrder = long.MaxValue, Limit = int.MaxValue}).Result;
			return response.ChatIds.Select(GetChat).ToList();
		}

		public static List<Chat> SearchChatsGlobal(string query) {
			if (query.TrimStart('@').Length < 5) {
				return new List<Chat>();
			}

			var response = client.ExecuteAsync(new SearchPublicChats {Query = query}).Result;

			var chats = response.ChatIds.Select(GetChat).ToList();

			chats.AddRange(client.ExecuteAsync(new SearchChats {Query = query, Limit = int.MaxValue}).Result.ChatIds.Select(GetChat));

			return chats;
		}

		public static Chat GetChatByUsernameGlobal(string username) {
			try {
				var response = client.ExecuteAsync(new SearchPublicChat {Username = username}).Result;
				return response;
			}
			catch {
				return null;
			}
		}

		public static int GetUserIdByUsername(string username) {
			try {
				var response = client.ExecuteAsync(new SearchPublicChat {Username = username}).Result;

				if (response.Type is ChatType.ChatTypePrivate priv)
					return priv.UserId;

				return 0;
			}
			catch {
				return 0;
			}
		}

		public static void AddUserToContacts(int userId, string name) {
			//TODO implement when TDLib 1.6 is released
		}

		public static List<Chat> GetSecretChats() {
			var response = client.ExecuteAsync(new GetChats {OffsetOrder = long.MaxValue, Limit = int.MaxValue}).Result;
			return response.ChatIds.Select(GetChat).Where(c => c.Type is ChatType.ChatTypeSecret).ToList();
		}

		public static void CloseSecretChat(int secretChatId) {
			client.ExecuteAsync(new CloseSecretChat() {SecretChatId = secretChatId}).Wait();
		}

		public static Chat CreateSecretChat(int userId) {
			return client.ExecuteAsync(new CreateNewSecretChat {UserId = userId}).Result;
		}

		public static void DeleteChatHistory(long chatId) {
			client.ExecuteAsync(new DeleteChatHistory {ChatId = chatId, RemoveFromChatList = true, Revoke = true}).Wait();
		}

		public static SecretChat GetSecretChat(int secretChatId) {
			var response = client.ExecuteAsync(new GetSecretChat {SecretChatId = secretChatId}).Result;
			return response;
		}

		public static void ClearCurrentConsoleLine() {
			Console.Write("\u001b[2K\r");

			//Console.SetCursorPosition(0, Console.WindowHeight);
			//Console.Write(new string(' ', Console.WindowWidth));
			//Console.SetCursorPosition(0, Console.WindowHeight);
		}

		public static string ReadConsolePassword() {
			var pass = "";
			do {
				var key = Console.ReadKey(true);
				if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter) {
					pass += key.KeyChar;
					Console.Write("*");
				}
				else {
					if (key.Key == ConsoleKey.Backspace && pass.Length > 0) {
						pass = pass.Substring(0, (pass.Length - 1));
						Console.Write("\b \b");
					}
					else if (key.Key == ConsoleKey.Enter) {
						break;
					}
				}
			} while (true);

			Console.WriteLine();
			return pass;
		}

		public static void SendMessage(string message, long chatId, long replyTo = 0) {
			if (string.IsNullOrWhiteSpace(message))
				return;

			Emojis.ForEach(em => message = message.Replace(em.Item1, em.Item2));
			client.ExecuteAsync(new SendMessage {
				ChatId              = chatId,
				InputMessageContent = new InputMessageContent.InputMessageText {Text = new FormattedText() {Text = message}},
				ReplyToMessageId    = replyTo,
			});
			currentUserRead = false;
		}

		public static Message EditMessage(string newText, Message message) {
			Emojis.ForEach(em => newText = newText.Replace(em.Item1, em.Item2));

			var msg = client.ExecuteAsync(new EditMessageText {
								ChatId              = message.ChatId,
								MessageId           = message.Id,
								InputMessageContent = new InputMessageContent.InputMessageText {Text = new FormattedText() {Text = newText}}
							})
							.Result;

			return msg;
		}

		public static void MarkRead(long chatId, long messageId) {
			client.ExecuteAsync(new ViewMessages {ChatId = chatId, MessageIds = new[] {messageId}, ForceRead = true});
		}

		public static void MarkUnread(long chatId) {
			client.ExecuteAsync(new ToggleChatIsMarkedAsUnread {ChatId = chatId, IsMarkedAsUnread = true,});
		}

		public static long SearchChatId(string query) {
			try {
				var results = client.ExecuteAsync(new SearchChats {Query = query, Limit = 5}).Result;

				if (query.StartsWith("@"))
					return results.ChatIds.First(p => GetChat(p).Type is ChatType.ChatTypePrivate type && GetUser(type.UserId).Username == query.Substring(1));

				return results.ChatIds.First(p => !(GetChat(p).Type is ChatType.ChatTypeSecret));
			}
			catch {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] No results found.");
				return 0;
			}
		}

		public static int SearchUserInChats(string query) {
			var results = client.ExecuteAsync(new SearchChatsOnServer {Query = query, Limit = 5}).Result;
			if (results.ChatIds.Length == 0)
				return 0;

			var output = results.ChatIds.Select(GetChat).Where(p => p.Type is ChatType.ChatTypePrivate).Select(p => ((ChatType.ChatTypePrivate) p.Type).UserId);
			return output.Any() ? output.First() : 0;
		}

		public static int SearchContacts(string query) {
			//TODO implement when TDLib 1.6 is released
			try {
				var results = client.ExecuteAsync(new SearchContacts {Query = query, Limit = 5}).Result;

				return query.StartsWith("@") ? results.UserIds.First(p => GetUser(p).Username == query.Substring(1)) : results.UserIds.First();
			}
			catch {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] No results found.");
				return 0;
			}
		}

		public static void LogOut() {
			lock (@lock)
				messageQueue.Add($"{Ansi.Yellow}[tgcli] Logging out...");
			client.ExecuteAsync(new LogOut()).Wait();
		}

		public static string GetFormattedUsername(User sender) {
			var username = sender.Username;
			if (string.IsNullOrWhiteSpace(username))
				username = sender.FirstName + " " + sender.LastName;
			else
				username = "@" + username;

			return username;
		}

		public static string FormatTime(long unix) {
			var time        = DateTimeOffset.FromUnixTimeSeconds(unix).DateTime.ToLocalTime();
			var currentTime = DateTime.Now.ToLocalTime();
			return time.ToString(time.Date.Ticks == currentTime.Date.Ticks ? "HH:mm" : "yyyy-MM-dd HH:mm");
		}

		public static bool IsMessageRead(long chatId, long messageId) {
			var chat = GetChat(chatId);
			return chat.LastReadOutboxMessageId >= messageId;
		}

		public static int GetActualStringWidth(string input) {
			input = input.Replace(Ansi.Blue, "");
			input = input.Replace(Ansi.Bold, "");
			input = input.Replace(Ansi.Cyan, "");
			input = input.Replace(Ansi.Green, "");
			input = input.Replace(Ansi.Magenta, "");
			input = input.Replace(Ansi.Red, "");
			input = input.Replace(Ansi.Yellow, "");
			input = input.Replace(Ansi.BoldOff, "");
			input = input.Replace(Ansi.ResetAll, "");
			return input.Length;
		}

		public static string GetFormattedStatus(bool isRead) {
			var output = " ";
			output += (isRead ? Ansi.Green : Ansi.Red) + "r";
			return output + $"{Ansi.ResetAll}]";
		}

		public static string TruncateString(string input, int maxLen) {
			if (maxLen < 2)
				maxLen = 2;
			return input.Length <= maxLen ? input : input.Substring(0, maxLen - 1) + "~";
		}

		public static string TruncateMessageStart(string input, int maxLen) {
			if (maxLen < 2)
				maxLen = 2;
			if (input.Contains("⏎ "))
				input = "⏎ " + input.Split("⏎ ").Last();
			return input.Length < maxLen ? input : "<" + input.Substring(input.Length - maxLen + 2);
		}

		public static readonly List<Tuple<string, string>> Emojis = new List<Tuple<string, string>> {
			new Tuple<string, string>("⏎ ", "\n"),
			new Tuple<string, string>(":xd:", Emoji.FaceWithTearsOfJoy.Sequence.AsString),
			new Tuple<string, string>(":check:", Emoji.WhiteHeavyCheckMark.Sequence.AsString),
			new Tuple<string, string>(":thinking:", Emoji.ThinkingFace.Sequence.AsString),
			new Tuple<string, string>(":eyes:", Emoji.Eyes.Sequence.AsString),
			new Tuple<string, string>(":heart:", Emoji.RedHeart.Sequence.AsString),
			new Tuple<string, string>(":shrug:", Emoji.PersonShrugging.Sequence.AsString),
			new Tuple<string, string>(":shrugf:", Emoji.WomanShrugging.Sequence.AsString),
			new Tuple<string, string>(":shrugm:", Emoji.ManShrugging.Sequence.AsString)
		};

		public static void InsertToInputLine(string strToInsert) {
			var part1 = currentInputLine.Substring(0, currentInputPos);
			var part2 = currentInputLine.Substring(currentInputPos);
			currentInputLine =  part1 + strToInsert + part2;
			currentInputPos  += strToInsert.Length;
		}

		public static void SetInputLine(string newInputLine) {
			currentInputLine = newInputLine;
			currentInputPos  = newInputLine.Length;
		}

		public static void RemoveFromInputLine(bool word = false) {
			var part1  = currentInputLine.Substring(0, currentInputPos);
			var oldlen = part1.Length;
			var part2  = currentInputLine.Substring(currentInputPos);
			if (word) {
				var lastIndex = part1.TrimEnd().LastIndexOf(" ", StringComparison.Ordinal);
				if (lastIndex < 0)
					lastIndex = 0;
				part1 = part1.Substring(0, lastIndex);
				if (lastIndex != 0)
					part1 += " ";
				//if (part1.EndsWith("⏎"))
				//	part1 = part1.Remove(part1.Length - 1);

				var newlen = part1.Length;

				currentInputLine =  part1 + part2;
				currentInputPos  -= oldlen - newlen;
				return;
			}

			currentInputLine = part1.Substring(0, part1.Length - 1) + part2;
			currentInputPos--;
		}

		public static void RemoveFromInputLineForward(bool word = false) {
			var part1 = currentInputLine.Substring(0, currentInputPos);
			var part2 = currentInputLine.Substring(currentInputPos);
			if (word) {
				var index = part2.IndexOf(" ", StringComparison.Ordinal);
				if (index < 0)
					index = part2.Length - 1;
				part2 = part2.Substring(0, index);
				if (index != 0)
					part2 = " " + part2;
				//if (part2.StartsWith("⏎"))
				//	part2 = part2.Remove(part1.Length - 1);

				currentInputLine = part1 + part2;
				return;
			}

			currentInputLine = part1 + part2.Substring(1);
		}

		public static readonly List<ConsoleKey> SpecialKeys = new List<ConsoleKey> {
			ConsoleKey.Backspace,
			ConsoleKey.Tab,
			ConsoleKey.Clear,
			ConsoleKey.Enter,
			ConsoleKey.Pause,
			ConsoleKey.Escape,
			ConsoleKey.PageUp,
			ConsoleKey.PageDown,
			ConsoleKey.End,
			ConsoleKey.Home,
			ConsoleKey.LeftArrow,
			ConsoleKey.UpArrow,
			ConsoleKey.RightArrow,
			ConsoleKey.DownArrow,
			ConsoleKey.Select,
			ConsoleKey.Print,
			ConsoleKey.Execute,
			ConsoleKey.PrintScreen,
			ConsoleKey.Insert,
			ConsoleKey.Delete,
			ConsoleKey.Help,
			ConsoleKey.LeftWindows,
			ConsoleKey.RightWindows,
			ConsoleKey.Applications,
			ConsoleKey.Sleep,
			ConsoleKey.F1,
			ConsoleKey.F2,
			ConsoleKey.F3,
			ConsoleKey.F4,
			ConsoleKey.F5,
			ConsoleKey.F6,
			ConsoleKey.F7,
			ConsoleKey.F8,
			ConsoleKey.F9,
			ConsoleKey.F10,
			ConsoleKey.F11,
			ConsoleKey.F12,
			ConsoleKey.F13,
			ConsoleKey.F14,
			ConsoleKey.F15,
			ConsoleKey.F16,
			ConsoleKey.F17,
			ConsoleKey.F18,
			ConsoleKey.F19,
			ConsoleKey.F20,
			ConsoleKey.F21,
			ConsoleKey.F22,
			ConsoleKey.F23,
			ConsoleKey.F24,
			ConsoleKey.BrowserBack,
			ConsoleKey.BrowserForward,
			ConsoleKey.BrowserRefresh,
			ConsoleKey.BrowserStop,
			ConsoleKey.BrowserSearch,
			ConsoleKey.BrowserFavorites,
			ConsoleKey.BrowserHome,
			ConsoleKey.VolumeMute,
			ConsoleKey.VolumeDown,
			ConsoleKey.VolumeUp,
			ConsoleKey.MediaNext,
			ConsoleKey.MediaPrevious,
			ConsoleKey.MediaStop,
			ConsoleKey.MediaPlay,
			ConsoleKey.LaunchMail,
			ConsoleKey.LaunchMediaSelect,
			ConsoleKey.LaunchApp1,
			ConsoleKey.LaunchApp2,
			ConsoleKey.Oem1,
			ConsoleKey.OemPlus,
			ConsoleKey.OemComma,
			ConsoleKey.OemMinus,
			ConsoleKey.OemPeriod,
			ConsoleKey.Oem2,
			ConsoleKey.Oem3,
			ConsoleKey.Oem4,
			ConsoleKey.Oem5,
			ConsoleKey.Oem6,
			ConsoleKey.Oem7,
			ConsoleKey.Oem8,
			ConsoleKey.Oem102,
			ConsoleKey.Process,
			ConsoleKey.Packet,
			ConsoleKey.Attention,
			ConsoleKey.CrSel,
			ConsoleKey.ExSel,
			ConsoleKey.EraseEndOfFile,
			ConsoleKey.Play,
			ConsoleKey.Zoom,
			ConsoleKey.NoName,
			ConsoleKey.Pa1,
			ConsoleKey.OemClear
		};
	}
}