using SharpChat.Configuration;
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<ClientCommand> Commands { get; } = [];

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

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

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

    public async Task Handle(C2SPacketHandlerContext ctx) {
        string[] args = ctx.SplitText(3);

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

        if(user == null || !user.Can(UserPermissions.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.ToString())
            return;

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

            if(user.Status != UserStatus.Online)
                await ctx.Chat.UpdateUser(user, status: UserStatus.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('/')) {
                ClientCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel);
                foreach(ClientCommand cmd in Commands)
                    if(cmd.IsMatch(context)) {
                        await cmd.Dispatch(context);
                        return;
                    }
            }

            await 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();
        }
    }
}