using SharpChat.Config;
using SharpChat.Events;
using SharpChat.Snowflake;
using System.Globalization;
using System.Text;

namespace SharpChat.C2SPacketHandlers {
    public class SendMessageC2SPacketHandler(
        RandomSnowflake randomSnowflake,
        CachedValue<int> maxMsgLength
    ) : C2SPacketHandler {
        private readonly CachedValue<int> MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength));

        private List<IChatCommand> Commands { get; } = [];

        public void AddCommand(IChatCommand command) {
            Commands.Add(command ?? throw new ArgumentNullException(nameof(command)));
        }

        public void AddCommands(IEnumerable<IChatCommand> commands) {
            Commands.AddRange(commands ?? throw new ArgumentNullException(nameof(commands)));
        }

        public bool IsMatch(ChatPacketHandlerContext ctx) {
            return ctx.CheckPacketId("2");
        }

        public void Handle(ChatPacketHandlerContext ctx) {
            string[] args = ctx.SplitText(3);

            ChatUser? user = ctx.Connection.User;
            string? messageText = args.ElementAtOrDefault(2);

            if(user == null || !user.Can(ChatUserPermissions.SendMessage) || string.IsNullOrWhiteSpace(messageText))
                return;

            // Extra validation step, not necessary at all but enforces proper formatting in SCv1.
            if(!long.TryParse(args[1], out long mUserId) || user.UserId != mUserId)
                return;

            ctx.Chat.ContextAccess.Wait();
            try {
                if(!ctx.Chat.UserLastChannel.TryGetValue(user.UserId, out ChatChannel? channel)
                    && (channel is null || !ctx.Chat.IsInChannel(user, channel)))
                    return;

                if(user.Status != ChatUserStatus.Online)
                    ctx.Chat.UpdateUser(user, status: ChatUserStatus.Online);

                int maxMsgLength = MaxMessageLength;
                StringInfo messageTextInfo = new(messageText);
                if(Encoding.UTF8.GetByteCount(messageText) > (maxMsgLength * 10)
                    || messageTextInfo.LengthInTextElements > maxMsgLength)
                    messageText = messageTextInfo.SubstringByTextElements(0, Math.Min(messageTextInfo.LengthInTextElements, maxMsgLength));

                messageText = messageText.Trim();

#if DEBUG
                Logger.Write($"<{ctx.Connection.Id} {user.UserName}> {messageText}");
#endif

                if(messageText.StartsWith('/')) {
                    ChatCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel);
                    foreach(IChatCommand cmd in Commands)
                        if(cmd.IsMatch(context)) {
                            cmd.Dispatch(context);
                            return;
                        }
                }

                ctx.Chat.DispatchEvent(new MessageCreateEvent(
                    randomSnowflake.Next(),
                    channel.Name,
                    user.UserId,
                    user.UserName,
                    user.Colour,
                    user.Rank,
                    user.NickName,
                    user.Permissions,
                    DateTimeOffset.Now,
                    messageText,
                    false, false, false
                ));
            } finally {
                ctx.Chat.ContextAccess.Release();
            }
        }
    }
}