using System;
using System.Security.Cryptography.X509Certificates;
using Microsoft.EntityFrameworkCore;
using sodoff.Model;
using sodoff.Schema;
using sodoff.Util;
using ZstdSharp.Unsafe;

namespace sodoff.Services;

public class MessagingService
{
    private readonly DBContext ctx;
    private readonly MMOClientService mMOClientService;
    public MessagingService(DBContext ctx, MMOClientService mMOClientService)
    {
        this.ctx = ctx;
        this.mMOClientService = mMOClientService;
    }

    public Model.Message AddMessageToViking(Viking? viking, Viking toViking, MessageType messageType, MessageTypeID messageTypeID, MessageLevel messageLevel, string data, string memberMessage = "", string nonMemberMessage = "", bool IsNew = true, bool IsDeleted = false, bool isReply = false, bool isPrivate = false, int parentMessageId = 0)
    {
        // get execution UTC timestamp
        DateTime now = DateTime.UtcNow;

        // for generating ConversationId and QueueId
        Random rnd = new Random();

        // construct message
        Model.Message message = new Model.Message
        {
            ToViking = toViking,
            ToVikingId = toViking.Id,
            QueueID = rnd.Next(1000, 9999),
            ConversationID = rnd.Next(1000, 9999),
            MessageType = messageType,
            MessageTypeID = messageTypeID,
            MessageLevel = messageLevel,
            Data = data,
            MemberMessage = memberMessage,
            NonMemberMessage = nonMemberMessage,
            CreatedAt = now,
            LastUpdatedAt = now,
            IsDeleted = IsDeleted,
            IsNew = IsNew,
            IsPrivate = isPrivate
        };

        if (viking == null)
        {
            message.Viking = null;
            message.VikingId = null;
        } else 
        {
            message.Viking = viking;
            message.VikingId = viking.Id;
        }

        if (isReply && parentMessageId > 0)
        {
            // get message this is in reply to
            Model.Message? messageToReplyTo = ctx.Messages.FirstOrDefault(e => e.Id == parentMessageId);

            if (messageToReplyTo != null)
            {
                message.ParentMessage = messageToReplyTo;
                message.ParentMessageId = messageToReplyTo.Id;
                message.ConversationID = messageToReplyTo.ConversationID;

                // add a message to the replier's board saying a thread update has occured (if reply isn't to self)
                if (message.VikingId != message.ToVikingId) AddMessageToViking(message.Viking, message.ToViking, MessageType.Data, MessageTypeID.ThreadUpdate, MessageLevel.WhiteList, "[[Line1]]=[[{{BuddyUserName}} Replied To Your Message In {{OwnerUserName}}'s Message Board!]]", "[[Line1]]=[[{{BuddyUserName}} Replied To Your Message In {{OwnerUserName}}'s Message Board!]]", "[[Line1]]=[[{{BuddyUserName}} Replied To Your Message In {{OwnerUserName}}'s Message Board!]]", isPrivate: true);
            } else throw new InvalidOperationException("Tried To Reply To A Message That Doesn't Exist");
        }

        // add message to database
        ctx.Messages.Add(message);
        ctx.SaveChanges();

        // update receiving users messages
        if (messageTypeID != MessageTypeID.BuddyList && viking != null && viking.Id != toViking.Id) // probably shouldn't recheck message queue for buddylist messages
        {
            mMOClientService.SendCommandToUser(toViking.Uid.ToString(), "SPMN", new string[] { "SPMN" });
            mMOClientService.SendCommandToUser(toViking.Uid.ToString(), "NMP", new string[] { "NMP", "-1", "1", "1" });
        }

        // return constructed message
        return message;
    }

    public void SendInvite(Viking viking, Viking buddyViking)
    {
        if(buddyViking.Online ?? false) // TODO - also account for LIMBO
        {
            mMOClientService.SendCommandToUser(buddyViking.Uid.ToString(), "SPI", ["SPI", "-1", viking.Uid.ToString(), "1", "5"]);
        }
    }

    public void RemoveMessage(int id)
    {
        // find message
        Model.Message? message = ctx.Messages.FirstOrDefault(e => e.Id == id);

        if (message != null)
        {
            // remove it
            ctx.Messages.Remove(message);
            ctx.SaveChanges();
        } else throw new InvalidOperationException("Tried To Delete A Message That Doesn't Exist");
    }

    public Model.Message? UpdateMessageByQueueID(int queueId, bool isNew, bool IsDeleted)
    {
        // get execution UTC timestamp
        DateTime now = DateTime.UtcNow;

        // find message
        Model.Message? message = ctx.Messages.FirstOrDefault(e => e.QueueID == queueId);

        if (message != null)
        {
            // set params (TODO - figure out what else the clients update in messages)
            message.IsNew = isNew;
            message.IsDeleted = IsDeleted;
            message.LastUpdatedAt = now;

            ctx.SaveChanges();

            return message;
        } else return null;
    }

    public ArrayOfCombinedListMessage ConstructCombinedMessageArray(Viking viking, Viking publicViking)
    {
        // get now timestamp
        DateTime now = DateTime.UtcNow;

        // get all messages in viking board
        List<Model.Message> messages = ctx.Messages.Where(e => e.ToVikingId == publicViking.Id).ToList();

        List<CombinedListMessage> combinedListMessages = new List<CombinedListMessage>();
        ArrayOfCombinedListMessage response = new ArrayOfCombinedListMessage();

        foreach(var message in messages)
        {
            if (message.IsDeleted)
            {
                ctx.Messages.Remove(message);
                continue;
            }

            if (message.IsPrivate && (viking.Id != publicViking.Id) || message.VikingId == -1) continue;

            Viking? msgAuthor = ctx.Vikings.FirstOrDefault(e => e.Id == message.VikingId) ?? new Viking();

            // construct a CombinedListMessage based on Model.Message
            CombinedListMessage clm = new CombinedListMessage
            {
                MessageType = (int?)message.MessageType ?? 0,
                MessageDate = message.CreatedAt
            };

            // if type id is messaging, use Schema.Message, otherwise treat it as Schema.MessageInfo
            if (message.MessageTypeID == MessageTypeID.Messaging)
            {
                clm.MessageBody = XmlUtil.SerializeXml(new Schema.Message 
                {
                    MessageID = message.Id,
                    ConversationID = message.ConversationID ?? 0,
                    ReplyToMessageID = message.ParentMessageId,
                    Creator = msgAuthor.Uid.ToString() ?? new Guid().ToString(),
                    CreateTime = message.CreatedAt,
                    UpdateDate = message.LastUpdatedAt,
                    MessageType = message.MessageType.Value,
                    MessageLevel = message.MessageLevel,
                    Content = message.Data ?? "No Data Found In This Message. This Might Be An Error.",
                    DisplayAttribute = "C=White" // still having it always white :)
                });
            } else if (message.Data.StartsWith("<?xml"))
            {
                // xml data needs to be parsed and put in the right format for response
                ArrayOfKeyValuePairOfStringString arrayOfKVP = XmlUtil.DeserializeXml<ArrayOfKeyValuePairOfStringString>(message.Data);
                string data = "";
                List<KeyValuePairOfStringString> pairList = new List<KeyValuePairOfStringString>();

                for(int i = 0; i < arrayOfKVP.KeyValuePairOfStringString.Length; i++)
                {
                    pairList.Add(arrayOfKVP.KeyValuePairOfStringString[i]);
                }

                foreach(KeyValuePairOfStringString pair in pairList)
                {
                    data = data + $"[[{pair.Key}]]=[[{pair.Value}]]";
                }

                clm.MessageBody = XmlUtil.SerializeXml(new MessageInfo 
                {
                    UserMessageQueueID = message.QueueID,
                    MessageID = message.Id,
                    MessageTypeID = (int?)message.MessageTypeID,
                    FromUserID = msgAuthor.Uid.ToString() ?? new Guid().ToString(),
                    MemberMessage = message.MemberMessage ?? "NoData",
                    NonMemberMessage = message.NonMemberMessage ?? "NoData",
                    Data = data
                });

            } else
            {
                clm.MessageBody = XmlUtil.SerializeXml(new MessageInfo 
                {
                    UserMessageQueueID = message.QueueID,
                    MessageID = message.Id,
                    MessageTypeID = (int?)message.MessageTypeID,
                    FromUserID = msgAuthor.Uid.ToString() ?? new Guid().ToString(),
                    MemberMessage = message.MemberMessage ?? "NoData",
                    NonMemberMessage = message.NonMemberMessage ?? "NoData",
                    Data = message.Data
                });
            }

            // add clm to list
            combinedListMessages.Add(clm);

            // ensure all replies are included in response
            foreach(var reply in message.Replies)
            {
                Viking? replyAuthor = ctx.Vikings.FirstOrDefault(e => e.Id == reply.VikingId) ?? new Viking();

                CombinedListMessage clmReply = new CombinedListMessage
                {
                    MessageType = (int)reply.MessageType,
                    MessageBody = XmlUtil.SerializeXml(new Schema.Message
                    {
                        MessageID = reply.Id,
                        ConversationID = reply.ConversationID ?? 0,
                        ReplyToMessageID = reply.ParentMessageId,
                        Creator = replyAuthor.Uid.ToString() ?? new Guid().ToString(),
                        CreateTime = reply.CreatedAt,
                        UpdateDate = reply.LastUpdatedAt,
                        MessageType = reply.MessageType.Value,
                        MessageLevel = reply.MessageLevel,
                        Content = reply.Data ?? "No Data Found In This Message. This Might Be An Error.",
                        DisplayAttribute = "C=White" // still having it always white :)
                    }),
                    MessageDate = reply.CreatedAt
                };

                combinedListMessages.Add(clmReply);
            }
        }

        // add announcements all the time
        Announcement[] announcements = new Announcement[0];

        if (viking.GameVersion <= ClientVersion.Max_OldJS && (viking.GameVersion & ClientVersion.WoJS) != 0)
            announcements = XmlUtil.DeserializeXml<AnnouncementList>(XmlUtil.ReadResourceXmlString("announcements_wojs")).Announcements;
        else if (viking.GameVersion == ClientVersion.SS)
            announcements = XmlUtil.DeserializeXml<AnnouncementList>(XmlUtil.ReadResourceXmlString("announcements_ss")).Announcements;

        foreach (var announcement in announcements)
        {
            if(announcement.Type == AnnouncementType.Text)
            {
                CombinedListMessage announcementMsg = new CombinedListMessage
                {
                    MessageType = 5,
                    MessageBody = XmlUtil.SerializeXml(announcement),
                    MessageDate = now
                };
                combinedListMessages.Add(announcementMsg);
            }
        }

        // save any changes made to db
        ctx.SaveChanges();

        // reverse list
        combinedListMessages.Reverse();

        // add list as array to response
        response.CombinedListMessage = combinedListMessages.ToArray();

        // return response
        return response;
    }

    public ArrayOfMessageInfo ConstructUserMessageInfoArray(Viking viking, bool showOldMessages, bool showDeletedMessages)
    {
        // get execution UTC timestamp
        DateTime now = DateTime.UtcNow;

        // get all recent messages
        List<Model.Message> messages = ctx.Messages.Where(e => e.ToVikingId == viking.Id)
            .OrderBy(e => e.CreatedAt)
            .OrderBy(e => e.QueueID)
            .ToList();

        List<MessageInfo> messageInfos = new List<MessageInfo>();
        ArrayOfMessageInfo response = new ArrayOfMessageInfo();

        foreach(var message in messages)
        {
            Viking? msgAuthor = ctx.Vikings.FirstOrDefault(e => e.Id == message.VikingId) ?? new Viking();

            if(message.IsDeleted && !showDeletedMessages) { ctx.Messages.Remove(message); continue; }
            if(DateTime.Compare(now, message.CreatedAt.AddMinutes(5)) > 0 && !showOldMessages)  { message.IsNew = false; continue; } // sometimes clients won't set IsNew flag when updating messages, so do not add messages more than 30 minutes old to response
            if(!message.IsNew && !showOldMessages) continue;

            MessageInfo messageInfo = new MessageInfo
            {
                MessageID = message.Id,
                UserMessageQueueID = message.QueueID,
                FromUserID = msgAuthor.Uid.ToString() ?? new Guid().ToString(),
                MessageTypeID = (int?)message.MessageTypeID,
                Data = message.Data ?? "NoData",
                MemberMessage = message.MemberMessage ?? "NoMessage",
                NonMemberMessage = message.NonMemberMessage ?? "NoMessage",
                MemberAudioUrl = "",
                NonMemberAudioUrl = "",
                MemberImageUrl = "",
                NonMemberImageUrl = ""
            };
            
            messageInfos.Add(messageInfo);
        }

        // save any database changes
        ctx.SaveChanges();

        // add list as array to response
        response.MessageInfo = messageInfos.ToArray();

        // return response
        return response;
    }
}
