using System;
using System.Collections.Generic;
using System.Linq;
using TdLib;
using static telegram.tgcli;
using static telegram.Util;

namespace telegram {
	public abstract class Command {
		public          string trigger;
		public          string shortcut;
		public          string description;
		public          string syntax;
		public          int    paramCount;
		public abstract void   Handler(List<string> inputParams);

		protected Command(string trigger, string shortcut, string description, string syntax, int paramCount) {
			this.trigger     = trigger;
			this.shortcut    = shortcut;
			this.description = description;
			this.paramCount  = paramCount;
			this.syntax      = syntax;
		}
	}

	public static class CommandManager {
		public static readonly List<Command> Commands = new List<Command> {
			new ClearCommand(),
			new CloseCommand(),
			new EditCommand(),
			new ReplyCommand(),
			new ReplyOffsetCommand(),
			new ReplyDirectCommand(),
			new HistoryCommand(),
			new OpenCommand(),
			new UnreadsCommand(),
			new CloseUnreadCommand(),
			new ListChatsCommand(),
			new NewChatCommand(),
			new ListSecretChatsCommand(),
			new OpenSecretCommand(),
			new OpenSecretDirectCommand(),
			new NewSecretChatCommand(),
			new CloseSecretChatCommand(),
			new SearchUserCommand(),
			//new AddContactCommand(),
			new QuitCommand(),
			new HelpCommand(),
			new LogoutCommand(),
		};

		public static void HandleCommand(string input) {
			var split   = input.Split(" ").ToList();
			var trigger = split.First();
			var command = Commands.Find(p => p.trigger == trigger || p.shortcut == trigger);
			if (command == null) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] Invalid command. Check /help for all available commands.");
				return;
			}

			split.RemoveAt(0);
			if (command.paramCount == -1) {
				command.Handler(split);
			}
			else if (split.Count == command.paramCount) {
				command.Handler(split);
			}
			else {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] Invalid command syntax. Check /help for more information.");
			}
		}
	}

	public class OpenCommand : Command {
		public OpenCommand() : base("o", "^O", "opens a chat. queries chat list", "<query>", -1) { }

		public override void Handler(List<string> inputParams) {
			if (inputParams.Count == 0) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] Invalid command syntax. Check /help for more information.");
				return;
			}

			var query  = inputParams.Aggregate((current, param) => current + " " + param).Trim();
			var chatId = SearchChatId(query);
			if (chatId == 0)
				return;

			currentChatId     = 0;
			currentChatUserId = 0;
			currentUserRead   = false;

			var chat = GetChat(chatId);
			if (chat.Type is TdApi.ChatType.ChatTypePrivate privChat) {
				currentChatUserId = privChat.UserId;
			}

			currentChatId = chat.Id;

			chat.Title = TruncateString(chat.Title, 20);

			prefix = $"[{chat.Title}";
			lock (@lock) {
				messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening chat: {chat.Title}");
				messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + $"{(chat.UnreadCount == 1 ? "." : "s.")}");

				if (chat.UnreadCount >= 5) {
					var capped = chat.UnreadCount > 50;
					GetHistory(chatId, capped ? 50 : chat.UnreadCount).ForEach(AddMessageToQueue);
					if (capped)
						messageQueue.Add($"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages.");
				}
				else if (chat.UnreadCount > 0) {
					var unreads = GetHistory(chatId, chat.UnreadCount);
					var rest    = GetHistory(chatId, 5 - unreads.Count, unreads.First().Id);
					rest.ForEach(AddMessageToQueue);
					messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---");
					unreads.ForEach(AddMessageToQueue);
				}
				else {
					GetHistory(chatId).ForEach(AddMessageToQueue);
				}
			}

			var history = GetHistory(currentChatId, 50);
			if (history.Count != 0)
				MarkRead(chat.Id, history.First().Id);
			var last = history.LastOrDefault(p => p.IsOutgoing);
			if (last == null) {
				currentUserRead = true;
				return;
			}

			lastMessage     = last;
			currentUserRead = IsMessageRead(last.ChatId, last.Id);
		}
	}

	public class NewChatCommand : Command {
		public NewChatCommand() : base("n", "", "starts a new chat.", "<username>", 1) { }

		public override void Handler(List<string> inputParams) {
			var chat = GetChatByUsernameGlobal(inputParams[0]);
			if (chat == null) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] User not found. Try /s <query> to find valid usernames.");
				return;
			}

			currentChatId     = 0;
			currentChatUserId = 0;
			currentUserRead   = false;

			if (chat.Type is TdApi.ChatType.ChatTypePrivate privChat) {
				currentChatUserId = privChat.UserId;
			}

			currentChatId = chat.Id;

			chat.Title = TruncateString(chat.Title, 20);

			prefix = $"[{chat.Title}";
			lock (@lock) {
				messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening chat: {chat.Title}");
				messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + $"{(chat.UnreadCount == 1 ? "." : "s.")}");

				if (chat.UnreadCount >= 5) {
					var capped = chat.UnreadCount > 50;
					GetHistory(chat.Id, capped ? 50 : chat.UnreadCount).ForEach(AddMessageToQueue);
					if (capped)
						messageQueue.Add($"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages.");
				}
				else if (chat.UnreadCount > 0) {
					var unreads = GetHistory(chat.Id, chat.UnreadCount);
					var rest    = GetHistory(chat.Id, 5 - unreads.Count, unreads.First().Id);
					rest.ForEach(AddMessageToQueue);
					messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---");
					unreads.ForEach(AddMessageToQueue);
				}
				else {
					GetHistory(chat.Id).ForEach(AddMessageToQueue);
				}
			}

			var history = GetHistory(currentChatId, 50);
			if (history.Count != 0)
				MarkRead(chat.Id, history.First().Id);
			var last = history.LastOrDefault(p => p.IsOutgoing);
			if (last == null) {
				currentUserRead = true;
				return;
			}

			lastMessage     = last;
			currentUserRead = IsMessageRead(last.ChatId, last.Id);
		}
	}

	public class CloseSecretChatCommand : Command {
		public CloseSecretChatCommand() : base("cs", "", "closes a secret chat (permanently)", "", 0) { }

		public override void Handler(List<string> _) {
			if (currentChatId != 0 && GetChat(currentChatId).Type is TdApi.ChatType.ChatTypeSecret type) {
				CloseSecretChat(type.SecretChatId);
				DeleteChatHistory(currentChatId);
				CommandManager.HandleCommand("c");
			}
			else {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] No secret chat selected, cannot continue.");
			}
		}
	}

	public class NewSecretChatCommand : Command {
		public NewSecretChatCommand() : base("ns", "", "creates a new secret chat.", "<username>", 1) { }

		public override void Handler(List<string> inputParams) {
			var userId = GetUserIdByUsername(inputParams[0]);

			if (userId == 0) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] User not found. Try /s <query> to find valid usernames.");
				return;
			}

			if (GetSecretChats().Count(p => ((TdApi.ChatType.ChatTypeSecret) p.Type).UserId == userId) > 0) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] You already have a secret chat with the specified user.");
				return;
			}

			var chat = CreateSecretChat(userId);
			CommandManager.HandleCommand("osd " + chat.Id);
		}
	}

	public class OpenSecretDirectCommand : Command {
		public OpenSecretDirectCommand() : base("osd", "", "opens a secret chat by chat id", "<chat_id>", 1) { }

		public override void Handler(List<string> inputParams) {
			var id = inputParams[0];
			if (!long.TryParse(id, out var chatId)) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] Invalid chat id.");
				return;
			}

			var chat = GetChat(chatId);
			if (chat == null) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] Invalid chat id.");
				return;
			}

			TdApi.SecretChat secChat;
			if (chat.Type is TdApi.ChatType.ChatTypeSecret secretChat) {
				currentChatUserId = secretChat.UserId;
				currentChatId     = chat.Id;
				currentUserRead   = false;
				secChat           = GetSecretChat(secretChat.SecretChatId);
			}
			else {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] The specified chat isn't a secret chat.");
				return;
			}

			chat.Title = TruncateString(chat.Title, 20);

			prefix = $"[{Ansi.Red}sec {Ansi.ResetAll}{chat.Title}]";
			lock (@lock) {
				messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening secret chat: {chat.Title}");
				messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + $"{(chat.UnreadCount == 1 ? "." : "s.")}");
				if (secChat.State is TdApi.SecretChatState.SecretChatStateClosed) {
					messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat has ended. No messages can be sent.");
				}
				else if (secChat.State is TdApi.SecretChatState.SecretChatStatePending) {
					messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat is pending. No messages can be sent.");
				}

				if (chat.UnreadCount >= 5) {
					var capped = chat.UnreadCount > 50;
					GetHistory(chatId, capped ? 50 : chat.UnreadCount, isSecret: true).ForEach(AddMessageToQueue);
					if (capped)
						messageQueue.Add($"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages.");
				}
				else if (chat.UnreadCount > 0) {
					var unreads = GetHistory(chatId, chat.UnreadCount, isSecret: true);
					var rest    = GetHistory(chatId, 5 - unreads.Count, unreads.First().Id, isSecret: true);
					rest.ForEach(AddMessageToQueue);
					messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---");
					unreads.ForEach(AddMessageToQueue);
				}
				else {
					GetHistory(chatId, isSecret: true).ForEach(AddMessageToQueue);
				}
			}

			var history = GetHistory(currentChatId, 50, isSecret: true);
			if (history.Count != 0)
				MarkRead(chat.Id, history.First().Id);
			var last = history.LastOrDefault(p => p.IsOutgoing);
			if (last == null) {
				currentUserRead = true;
				return;
			}

			lastMessage     = last;
			currentUserRead = IsMessageRead(last.ChatId, last.Id);
		}
	}

	public class OpenSecretCommand : Command {
		public OpenSecretCommand() : base("os", "", "opens a secret chat. queries chat list.", "<query>", -1) { }

		public override void Handler(List<string> inputParams) {
			if (inputParams.Count == 0) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] No matching chat found.");
				return;
			}

			var query  = inputParams.Aggregate((current, param) => current + " " + param).Trim();
			var userId = SearchUserInChats(query);
			if (userId == 0 || query.Length == 0) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] No matching chat found.");
				return;
			}

			var chat = GetSecretChats().Find(p => ((TdApi.ChatType.ChatTypeSecret) p.Type).UserId == userId);
			if (chat == null) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] No matching secret chat found.");
				return;
			}

			TdApi.SecretChat secChat;
			if (chat.Type is TdApi.ChatType.ChatTypeSecret secretChat) {
				currentChatUserId = secretChat.UserId;
				currentChatId     = chat.Id;
				currentUserRead   = false;
				secChat           = GetSecretChat(secretChat.SecretChatId);
			}
			else {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] No matching secret chat found. (this error should be impossible to produce)");
				return;
			}

			chat.Title = TruncateString(chat.Title, 20);

			prefix = $"[{Ansi.Red}sec {Ansi.ResetAll}{chat.Title}";
			lock (@lock) {
				messageQueue.Add($"{Ansi.Yellow}[tgcli] Opening secret chat: {chat.Title}");
				messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {chat.UnreadCount} unread message" + $"{(chat.UnreadCount == 1 ? "." : "s.")}");
				if (secChat.State is TdApi.SecretChatState.SecretChatStateClosed) {
					messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat has ended. No messages can be sent.");
				}
				else if (secChat.State is TdApi.SecretChatState.SecretChatStatePending) {
					messageQueue.Add($"{Ansi.Red}[tgcli] Secret chat is pending. No messages can be sent.");
				}

				if (chat.UnreadCount >= 5) {
					var capped = chat.UnreadCount > 50;
					GetHistory(chat.Id, capped ? 50 : chat.UnreadCount, isSecret: true).ForEach(AddMessageToQueue);
					if (capped)
						messageQueue.Add($"{Ansi.Yellow}[tgcli] " + $"Showing 50 of {chat.UnreadCount} unread messages.");
				}
				else if (chat.UnreadCount > 0) {
					var unreads = GetHistory(chat.Id, chat.UnreadCount, isSecret: true);
					var rest    = GetHistory(chat.Id, 5 - unreads.Count, unreads.First().Id, isSecret: true);
					rest.ForEach(AddMessageToQueue);
					messageQueue.Add($"{Ansi.Yellow}[tgcli] ---UNREAD---");
					unreads.ForEach(AddMessageToQueue);
				}
				else {
					GetHistory(chat.Id, isSecret: true).ForEach(AddMessageToQueue);
				}
			}

			var history = GetHistory(currentChatId, 50, isSecret: true);
			if (history.Count != 0)
				MarkRead(chat.Id, history.First().Id);
			var last = history.LastOrDefault(p => p.IsOutgoing);
			if (last == null) {
				currentUserRead = true;
				return;
			}

			lastMessage     = last;
			currentUserRead = IsMessageRead(last.ChatId, last.Id);
		}
	}

	public class CloseUnreadCommand : Command {
		public CloseUnreadCommand() : base("cu", "", "closes a chat, marking it as unread", "", 0) { }

		public override void Handler(List<string> inputParams) {
			if (currentChatId == 0) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue.");
				return;
			}

			MarkUnread(currentChatId);
			CommandManager.HandleCommand("c");
		}
	}

	public class CloseCommand : Command {
		public CloseCommand() : base("c", "^E", "closes a chat", "", 0) { }

		public override void Handler(List<string> inputParams) {
			if (currentChatId == 0) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue.");
				return;
			}

			currentChatId     = 0;
			currentChatUserId = 0;
			currentUserRead   = false;
			lastMessage       = null;
			prefix            = "[tgcli";

			lock (@lock) {
				messageQueue.Add($"{Ansi.Yellow}[tgcli] Closing chat.");
				var count = missedMessages.Count;
				if (count == 0)
					return;

				messageQueue.Add($"{Ansi.Yellow}" + $"[tgcli] You have {count} missed message" + $"{(count == 1 ? "." : "s.")}");
				messageQueue.AddRange(missedMessages);
				missedMessages.Clear();
			}
		}
	}

	public class HistoryCommand : Command {
		public HistoryCommand() : base("h", "", "shows chat history. default limit is 5", "[1-50]", -1) { }

		public override void Handler(List<string> inputParams) {
			if (currentChatId == 0) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue.");
				return;
			}

			List<TdApi.Message> history;

			if (inputParams.Count == 1 && int.TryParse(inputParams[0], out var limit)) {
				history = GetHistory(currentChatId, Math.Min(limit, 50));
				while (limit > 50) {
					limit -= 50;
					history.InsertRange(0, GetHistory(currentChatId, Math.Min(limit, 50), history.First().Id));
				}
			}

			else {
				history = GetHistory(currentChatId);
			}

			lock (@lock) {
				messageQueue.Add($"{Ansi.Yellow}[tgcli] Last {history.Count} messages in " + $"{GetChat(currentChatId).Title}");
			}

			foreach (var msg in history) {
				AddMessageToQueue(msg);
			}
		}
	}

	public class ClearCommand : Command {
		public ClearCommand() : base("cl", "^L", "clears console", "", 0) { }

		public override void Handler(List<string> inputParams) {
			lock (@lock) {
				Console.Clear();
			}
		}
	}

	public class UnreadsCommand : Command {
		public UnreadsCommand() : base("u", "^U", "displays unread chat", "[all]", -1) { }

		public override void Handler(List<string> inputParams) {
			var unreads = GetUnreadChats(inputParams.Count == 1 && inputParams[0].Equals("all"));

			lock (@lock) {
				messageQueue.Add($"{Ansi.Yellow}[tgcli] You have {unreads.Count} unread chats.");
				unreads.ForEach(chat => {
					string line;
					if (chat.UnreadCount == 0)
						line = $"{Ansi.Bold}{Ansi.Yellow}[M] {chat.Title}";
					else if (chat.Type is TdApi.ChatType.ChatTypeSecret)
						line = $"{Ansi.Bold}{Ansi.Red}[{chat.UnreadCount}] [sec] {chat.Title}";
					else
						line = $"{Ansi.Bold}{Ansi.Green}[{chat.UnreadCount}] {chat.Title}";
					messageQueue.Add(line);
				});
			}
		}
	}

	public class ListChatsCommand : Command {
		public ListChatsCommand() : base("lc", "", "lists all chats, optionally filtered", "[query]", -1) { }

		public override void Handler(List<string> inputParams) {
			var chats = GetChats();

			lock (@lock) {
				if (inputParams.Count > 0) {
					var query = inputParams.Aggregate((current, param) => current + " " + param).Trim();
					chats = chats.FindAll(p => p.Title.ToLower().Contains(query.ToLower()));
				}

				messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {chats.Count} chats.");
				chats.ForEach(chat => {
					string line;
					if (chat.UnreadCount == 0)
						line = $"{Ansi.Bold}{Ansi.Blue}[0] {chat.Title}";
					else
						line = $"{Ansi.Bold}{Ansi.Green}[{chat.UnreadCount}] {chat.Title}";
					messageQueue.Add(line);
				});
			}
		}
	}

	public class SearchUserCommand : Command {
		public SearchUserCommand() : base("s", "", "searches for users globally", "<query>", -1) { }

		public override void Handler(List<string> inputParams) {
			if (inputParams.Count == 0) {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] Invalid syntax, check /help for more information.");
				return;
			}

			var query = inputParams.Aggregate((current, param) => current + " " + param).Trim();

			var chats = SearchChatsGlobal(query);
			chats = chats.FindAll(p => p.Type is TdApi.ChatType.ChatTypePrivate);

			lock (@lock) {
				messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {chats.Count} chats.");
				chats.ForEach(chat => {
					string line;
					var    type = (TdApi.ChatType.ChatTypePrivate) chat.Type;
					var    user = GetUser(type.UserId);
					line = $"{Ansi.Bold}{Ansi.Yellow}@{user.Username} {Ansi.Magenta}{chat.Title}";
					messageQueue.Add(line);
				});
			}
		}
	}

	public class AddContactCommand : Command {
		public AddContactCommand() : base("ac", "", "adds user to contact list", "<username>", 1) { }

		public override void Handler(List<string> inputParams) {
			/*
			var query = inputParams[0];

			var chat = GetChatByUsernameGlobal(query);

			if (chat.Type is TdApi.ChatType.ChatTypePrivate type)
			{
				//TODO implement when TDLib 1.6 is released
			}
			else
			{
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] Username does not refer to a user.");
			}
			*/
		}
	}

	public class ListSecretChatsCommand : Command {
		public ListSecretChatsCommand() : base("ls", "", "displays all open secret chats", "", 0) { }

		public override void Handler(List<string> inputParams) {
			var secretChats = GetSecretChats();
			lock (@lock) {
				messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {secretChats.Count} secret chats:");
				secretChats.ForEach(chat => {
					messageQueue.Add($"{Ansi.Bold}{Ansi.Red}[sec] {chat.Title} -> {chat.Id} ({GetSecretChat(((TdApi.ChatType.ChatTypeSecret) chat.Type).SecretChatId).State.DataType})");
				});
			}
		}
	}

	public class HelpCommand : Command {
		public HelpCommand() : base("help", "", "lists all commands", "", 0) { }

		public override void Handler(List<string> inputParams) {
			lock (@lock) {
				messageQueue.Add($"{Ansi.Yellow}[tgcli] Listing {CommandManager.Commands.Count} commands:");
				CommandManager.Commands.ForEach(command => {
					var commandText = $"/{command.trigger}";
					if (!string.IsNullOrWhiteSpace(command.syntax))
						commandText += $" {command.syntax}";
					commandText += $": {command.description}";
					if (!string.IsNullOrWhiteSpace(command.shortcut))
						commandText += $" ({command.shortcut})";

					messageQueue.Add($"{Ansi.Yellow}{commandText}");
				});
			}
		}
	}

	public class QuitCommand : Command {
		public QuitCommand() : base("q", "^D", "quits the program", "", 0) { }

		public override void Handler(List<string> inputParams) {
			quitting = true;
		}
	}

	public class EditCommand : Command {
		public EditCommand() : base("e", "", "edits last message. param empty adds last message to inputline", "[message]", -1) { }

		public override void Handler(List<string> inputParams) {
			try {
				if (inputParams.Count == 0) {
					SetInputLine("/e " + ((TdApi.MessageContent.MessageText) lastMessage?.Content)?.Text?.Text);
					Emojis.ForEach(em => SetInputLine(currentInputLine.Replace(em.Item2, em.Item1)));
					return;
				}

				var message = inputParams.Aggregate((current, param) => current + " " + param).Trim();

				if (currentChatId == 0) {
					lock (@lock)
						messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue.");
					return;
				}

				if (lastMessage == null) {
					//try to find last message
					var history = GetHistory(currentChatId, 50);
					var last    = history.LastOrDefault(p => p.IsOutgoing);
					if (last == null) {
						lock (@lock)
							messageQueue.Add($"{Ansi.Red}[tgcli] No message to edit found, cannot continue.");
						return;
					}

					lastMessage = last;
				}

				if (string.IsNullOrWhiteSpace(message)) {
					lock (@lock)
						messageQueue.Add($"{Ansi.Red}[tgcli] No message specified, cannot continue.");
					return;
				}

				EditMessage(message, lastMessage);
			}
			catch {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] Unknown error editing message.");
			}
		}
	}

	public class ReplyCommand : Command {
		public ReplyCommand() : base("r", "", "matches message in history to reply to", "<message query>", -1) { }

		public override void Handler(List<string> inputParams) {
			try {
				if (inputParams.Count == 0) {
					lock (@lock)
						messageQueue.Add($"{Ansi.Red}[tgcli] Invalid syntax, check /help for more information.");
					return;
				}

				if (currentChatId == 0) {
					lock (@lock)
						messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue.");
					return;
				}

				var history = GetHistory(currentChatId, 50);
				history.Reverse();
				var query = inputParams.Aggregate((current, param) => current + " " + param).Trim();

				var result = history.Where(m => m.Content is TdApi.MessageContent.MessageText)
									.FirstOrDefault(m => ((TdApi.MessageContent.MessageText) m.Content)
														 .Text.Text.ToLowerInvariant()
														 .Contains(query.ToLower()));

				if (result == null) {
					lock (@lock)
						messageQueue.Add($"{Ansi.Red}[tgcli] No match found.");
					return;
				}

				SetInputLine($"/rd {result.Id} ");
			}
			catch {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] Unknown error searching for message.");
			}
		}
	}

	public class ReplyOffsetCommand : Command {
		public ReplyOffsetCommand() : base("ro", "", "replies to message", "<offset> <message>", -1) { }

		public override void Handler(List<string> inputParams) {
			try {
				if (inputParams.Count < 2) {
					lock (@lock)
						messageQueue.Add($"{Ansi.Red}[tgcli] Invalid syntax, check /help for more information.");
					return;
				}

				if (currentChatId == 0) {
					lock (@lock)
						messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue.");
					return;
				}

				var history = GetHistory(currentChatId, 50);
				var parsed  = int.TryParse(inputParams[0], out var offset);
				inputParams.RemoveAt(0);
				history.Reverse();
				var message = inputParams.Aggregate((current, param) => current + " " + param).Trim();

				if (!parsed || string.IsNullOrWhiteSpace(message) || history.Count < offset) {
					lock (@lock)
						messageQueue.Add($"{Ansi.Red}Invalid syntax, check /help for more information.");
					return;
				}

				var replyMessage = history[offset - 1];

				SendMessage(message, currentChatId, replyMessage.Id);
			}
			catch {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] Unknown error sending message.");
			}
		}
	}

	public class ReplyDirectCommand : Command {
		public ReplyDirectCommand() : base("rd", "", "replies to message by id", "<id> <message>", -1) { }

		public override void Handler(List<string> inputParams) {
			try {
				if (inputParams.Count < 2) {
					lock (@lock)
						messageQueue.Add($"{Ansi.Red}[tgcli] Invalid syntax, check /help for more information.");
					return;
				}

				if (currentChatId == 0) {
					lock (@lock)
						messageQueue.Add($"{Ansi.Red}[tgcli] No open chat, cannot continue.");
					return;
				}

				var parsed = long.TryParse(inputParams[0], out var replyId);
				inputParams.RemoveAt(0);
				var message = inputParams.Aggregate((current, param) => current + " " + param).Trim();

				if (!parsed || string.IsNullOrWhiteSpace(message)) {
					lock (@lock)
						messageQueue.Add($"{Ansi.Red}Invalid syntax, check /help for more information.");
					return;
				}

				SendMessage(message, currentChatId, replyId);
			}
			catch {
				lock (@lock)
					messageQueue.Add($"{Ansi.Red}[tgcli] Unknown error sending message.");
			}
		}
	}

	public class LogoutCommand : Command {
		public LogoutCommand() : base("logout", "", "log out this session (destroys all local data)", "", 0) { }

		public override void Handler(List<string> inputParams) {
			LogOut();
		}
	}
}