using System;
using System.Collections.Generic;
using System.Linq;
using NeoSmart.Unicode;
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)
        {
            Trigger = trigger;
            Shortcut = shortcut;
            Description = description;
            ParamCount = paramCount;
            Syntax = syntax;
        }
    }

    public static class CommandManager
    {
        public static List<Command> Commands = new List<Command>
        {
            new ClearCommand(),
            new CloseCommand(),
            new EditCommand(),
            new ReplyCommand(),
            new HistoryCommand(),
            new OpenCommand(),
            new UnreadsCommand(),
            new CloseUnreadCommand(),
            new ListSecretChatsCommand(),
            new OpenSecretCommand(),
            new OpenSecretDirectCommand(),
            new NewSecretChatCommand(),
            new CloseSecretChatCommand(),
            new QuitCommand(),
            new HelpCommand()
        };

        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)
            {
                //TODO Console.WriteLine("do something");
                return;
            }

            split.RemoveAt(0);
            if (command.ParamCount == -1)
            {
                command.Handler(split);
            }
            else if (split.Count == command.ParamCount)
            {
                command.Handler(split);
            }
            else
            {
                //TODO Console.WriteLine("do something");
            }
        }
    }

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

        public override void Handler(List<string> inputParams)
        {
            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);
                    Console.WriteLine(unreads.Count);
                    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 CloseSecretChatCommand : Command
    {
        public CloseSecretChatCommand() : base("cs", "", "closes a secret chat (permanently)", "", 0)
        {
        }

        public override void Handler(List<string> _)
        {
            if (currentChatId == 0)
                return; //TODO do something
            if (GetChat(currentChatId).Type is TdApi.ChatType.ChatTypeSecret type)
            {
                CloseSecretChat(type.SecretChatId);
                DeleteChatHistory(currentChatId);
                CommandManager.HandleCommand("c");
            }
            else
            {
                //TODO do something
            }
        }
    }

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

        public override void Handler(List<string> inputParams)
        {
            var query = inputParams.Aggregate((current, param) => current + " " + param).Trim();
            var userId = SearchContacts(query);

            if (userId == 0)
                return;
            if (GetSecretChats().Count(p => ((TdApi.ChatType.ChatTypeSecret) p.Type).UserId == userId) > 0)
                return; //TODO do something

            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))
            {
                return; //todo do something
            }

            var chat = GetChat(chatId);
            if (chat == null)
                return; //TODO do something

            TdApi.SecretChat secChat;
            if (chat.Type is TdApi.ChatType.ChatTypeSecret secretChat)
            {
                currentChatUserId = secretChat.UserId;
                currentChatId = chat.Id;
                currentUserRead = false;
                secChat = GetSecretChat(secretChat.SecretChatId);
            }
            else
            {
                return; //TODO do something
            }

            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 contacts.", "<query>", -1)
        {
        }

        public override void Handler(List<string> inputParams)
        {
            var query = inputParams.Aggregate((current, param) => current + " " + param).Trim();
            var userId = SearchContacts(query);
            if (userId == 0 || query.Length == 0)
                return; //TODO do something;
            var chat = GetSecretChats().Find(p => ((TdApi.ChatType.ChatTypeSecret) p.Type).UserId == userId);
            if (chat == null)
            {
                return; //TODO do something
            }

            TdApi.SecretChat secChat;
            if (chat.Type is TdApi.ChatType.ChatTypeSecret secretChat)
            {
                currentChatUserId = secretChat.UserId;
                currentChatId = chat.Id;
                currentUserRead = false;
                secChat = GetSecretChat(secretChat.SecretChatId);
            }
            else
            {
                return; //TODO do something
            }

            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) return; //TODO: do something
            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) return; //TODO: do something

            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) return; //TODO: do something

            var history = inputParams.Count == 1 && int.TryParse(inputParams[0], out var limit)
                ? GetHistory(currentChatId, limit)
                : 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 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}");
                });
            }
        }
    }

    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)
                {
                    currentInputLine = "/e " + ((TdApi.MessageContent.MessageText) lastMessage?.Content)?.Text?.Text;
                    return;
                }

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

                if (currentChatId == 0) return; //TODO do something

                if (lastMessage == null)
                {
                    //try to find last message
                    var history = GetHistory(currentChatId, 50);
                    var last = history.LastOrDefault(p => p.IsOutgoing);
                    if (last == null) return; //TODO do something
                    lastMessage = last;
                }

                if (string.IsNullOrWhiteSpace(message))
                    return;
                EditMessage(message, lastMessage);
            }
            catch
            {
                //TODO do something
            }
        }
    }

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

        public override void Handler(List<string> inputParams)
        {
            try
            {
                if (inputParams.Count < 2)
                {
                    return; //TODO do something
                }

                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)
                {
                    return; //TODO do something
                }

                var replyMessage = history[offset - 1];

                if (currentChatId == 0) return; //TODO do something

                SendMessage(message, currentChatId, replyMessage.Id);
            }
            catch
            {
                //TODO do something
            }
        }
    }
}