using System;
using System.Data;
using System.Security;
using Microsoft.Extensions.Options;
using sodoff.Configuration;
using sodoff.Model;
using sodoff.Schema;
using sodoff.Util;

namespace sodoff.Services;

public class BuddyService
{
    private char[] BuddyCodeCharacterList = new char[] 
        { 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','1','2','3','4','5','6','7','8','9','0' };

    private readonly DBContext ctx;
    private readonly MessagingService messagingService;
    private readonly MMOClientService mMOClientService;
    private readonly IOptions<ApiServerConfig> config;

    public BuddyService(DBContext ctx, MessagingService messagingService, MMOClientService mMOClientService, IOptions<ApiServerConfig> config)
    {
        this.ctx = ctx;
        this.messagingService = messagingService;
        this.mMOClientService = mMOClientService;
        this.config = config;
    }

    public BuddyActionResult CreateBuddyRelation(Viking viking1, Viking viking2, BuddyStatus buddyStatus1 = BuddyStatus.PendingApprovalFromOther, BuddyStatus buddyStatus2 = BuddyStatus.PendingApprovalFromSelf)
    {
        // get execution UTC timestamp
        DateTime now = DateTime.UtcNow;

        // construct buddy
        Model.Buddy buddy = new Model.Buddy
        {
            Viking = viking1,
            VikingId = viking1.Id,
            BuddyViking = viking2,
            BuddyVikingId = viking2.Id,
            BuddyStatus1 = buddyStatus1,
            BuddyStatus2 = buddyStatus2,
            IsBestFriend1 = false,
            IsBestFriend2 = false,
            CreatedAt = now
        };

        // do not add if viking1 is on a different game version than viking2
        if (viking1.GameVersion < viking2.GameVersion) return new BuddyActionResult { Result = BuddyActionResultType.InvalidFriendCode };

        // do not add if relationship already exists
        Model.Buddy? existingBuddy = ctx.Buddies.FirstOrDefault(e => e.VikingId == buddy.VikingId && e.BuddyVikingId == buddy.BuddyVikingId);
        if (existingBuddy != null) return new BuddyActionResult { Result = BuddyActionResultType.AlreadyInList };

        // add relationship to database
        ctx.Buddies.Add(buddy);
        ctx.SaveChanges();

        // if this is a buddy relationship, use MessagingService to send buddy request message to viking2
        if (buddyStatus1 == BuddyStatus.PendingApprovalFromOther && buddyStatus2 == BuddyStatus.PendingApprovalFromSelf)
        {
            messagingService.AddMessageToViking(viking1, viking2, MessageType.Data, MessageTypeID.BuddyList, MessageLevel.WhiteList, "[[Line1]]=[[{{BuddyUserName}} Wants To Add You As A Buddy!]]", "[[Line1]]=[[{{BuddyUserName}} Wants To Add You As A Buddy!]]", "[[Line1]]=[[{{BuddyUserName}} Wants To Add You As A Buddy!]]");
            if (viking2.GameVersion != ClientVersion.MB) mMOClientService.SendCommandToUser(viking2.Uid.ToString(), "SBE", new string[] { "SBE", "-1", viking1.Uid.ToString(), viking2.Uid.ToString(), "1" }); // don't send SBE in MB due to potential client bug
        }
            
        // return result
        return new BuddyActionResult
        {
            Result = BuddyActionResultType.Success,
            Status = buddy.BuddyStatus1,
            BuddyUserID = viking2.Uid.ToString()
        };
    }

    public bool UpdateBuddyRelation(Viking viking, Viking buddyViking, BuddyStatus buddyStatus1, BuddyStatus buddyStatus2)
    {
        // find relation
        Model.Buddy? buddy = ctx.Buddies.Where(e => e.VikingId == viking.Id || e.BuddyVikingId == viking.Id)
            .FirstOrDefault(e => e.BuddyVikingId == buddyViking.Id || e.VikingId == buddyViking.Id);
        
        if (buddy != null)
        {
            // update it
            buddy.BuddyStatus1 = buddyStatus1;
            buddy.BuddyStatus2 = buddyStatus2;
            ctx.SaveChanges();

            if ((buddyStatus1 & buddyStatus2) == BuddyStatus.Approved)
                mMOClientService.SendCommandToUser(viking.Uid.ToString(), "SBE", new string[] { "SBE", "-1", buddyViking.Uid.ToString(), viking.Uid.ToString(), "4" });

            // return result
            return true;
        } else return false;
    }

    public bool UpdateBestBuddy(Viking viking, Viking buddyViking, bool bestBuddy)
    {
        // find relation
        Model.Buddy? buddy = ctx.Buddies.Where(e => e.VikingId == viking.Id || e.BuddyVikingId == viking.Id)
            .FirstOrDefault(e => e.BuddyVikingId == buddyViking.Id || e.VikingId == buddyViking.Id);

        if (buddy != null)
        {
            if (buddy.VikingId == viking.Id)
                buddy.IsBestFriend2 = bestBuddy;
            else buddy.IsBestFriend1 = bestBuddy;

            ctx.SaveChanges();

            return true;
        } else return false;
    }

    public string GetOrSetBuddyCode(Viking viking)
    {
        if (viking.BuddyCode != null) return viking.BuddyCode;
        else
        {
            // generate a buddy code and set it
            Random rnd = new Random();
            string code = "";
            do
            {
                for( int i = 0; i < 5; i++ )
                {
                    code += BuddyCodeCharacterList[rnd.Next(0, BuddyCodeCharacterList.Length)];
                }
            } while (ctx.Vikings.FirstOrDefault(e => e.BuddyCode == code) != null);

            // set code on viking
            viking.BuddyCode = code;
            ctx.SaveChanges();

            // return it
            return viking.BuddyCode;
        }
    }

    public bool RemoveBuddy(Viking viking, Viking buddyViking)
    {
        // find relation
        Model.Buddy? buddy = ctx.Buddies.Where(e => e.VikingId == viking.Id || e.BuddyVikingId == viking.Id)
            .FirstOrDefault(e => e.BuddyVikingId == buddyViking.Id || e.VikingId == buddyViking.Id);
            
        if (buddy != null)
        {
            // blocks that are of both need to be handled correctly
            if (buddy.BuddyStatus1 == BuddyStatus.BlockedByBoth && buddy.BuddyStatus2 == BuddyStatus.BlockedByBoth)
            {
                // instead of removing, update the relationship correctly
                if (buddy.VikingId == viking.Id)
                {
                    // the relationship should be switched (this probably ain't the best way to do this but here we are)
                    buddy.BuddyVikingId = viking.Id;
                    buddy.BuddyStatus1 = BuddyStatus.BlockedByOther; 
                    buddy.VikingId = buddyViking.Id;
                    buddy.BuddyStatus2 = BuddyStatus.BlockedBySelf;
                }
                else
                {
                    buddy.BuddyVikingId = buddyViking.Id;
                    buddy.BuddyStatus1 = BuddyStatus.BlockedBySelf;
                    buddy.VikingId = viking.Id;
                    buddy.BuddyStatus2 = BuddyStatus.BlockedByOther;
                }
                
                ctx.SaveChanges();
                return true;
            }

            // remove it
            ctx.Buddies.Remove(buddy);
            ctx.SaveChanges();

            mMOClientService.SendCommandToUser(buddyViking.Uid.ToString(), "SBE", new string[] { "SBE", "-1", viking.Uid.ToString(), buddyViking.Uid.ToString(), "2" });

            return true;
        }
        else return false;
    }

    public BuddyLocation GetLocationOfBuddy(Viking viking)
    {
        if (viking.CurrentRoomId != null && viking.CurrentZone != null)
        {
            // construct
            BuddyLocation buddyLocation = new()
            {
                UserID = viking.Uid.ToString(),
                Room = viking.CurrentRoomId.Value.ToString(),
                Server = config.Value.MMOAdress,
                ServerPort = config.Value.MMOPort,
                ServerVersion = "S2X",
                Zone = viking.CurrentZone,
                MultiplayerID = viking.CurrentRoomId.Value
            };

            if (viking.CurrentZone.StartsWith("ShipWreck"))
                buddyLocation.Zone = "ShipWreckLagoon"; // very stupid fix to get around room and zone not being seperated mmo side

            if (viking.GameVersion <= ClientVersion.WoJS) buddyLocation.AppName = "JSMain";
            else if (viking.GameVersion <= ClientVersion.MB) buddyLocation.AppName = "MBMain";

            // return
            return buddyLocation;
        }
        else return new BuddyLocation();
    }

    public BuddyList ConstructBuddyList(Viking viking)
    {
        // get all relationships viking has made
        List<Model.Buddy> buddies = ctx.Buddies.Where(e => e.VikingId == viking.Id || e.BuddyVikingId == viking.Id)
            .ToList();
        List<Schema.Buddy> schemaBuddies = new();
        foreach (var buddy in buddies)
        {

            Schema.Buddy schemaBuddy = new Schema.Buddy();

            // show differently depending on requester
            if (buddy.VikingId == viking.Id)
                schemaBuddy = new Schema.Buddy
                {
                    UserID = buddy.BuddyViking.Uid.ToString(),
                    DisplayName = XmlUtil.DeserializeXml<AvatarData>(buddy.BuddyViking.AvatarSerialized).DisplayName,
                    Status = buddy.BuddyStatus2,
                    CreateDate = buddy.CreatedAt,
                    Online = buddy.BuddyViking.Online ?? false,
                    OnMobile = false,
                    BestBuddy = buddy.IsBestFriend2
                };
            else
                schemaBuddy = new Schema.Buddy
                {
                    UserID = buddy.Viking.Uid.ToString(),
                    DisplayName = XmlUtil.DeserializeXml<AvatarData>(buddy.Viking.AvatarSerialized).DisplayName,
                    Status = buddy.BuddyStatus1,
                    CreateDate = buddy.CreatedAt,
                    Online = buddy.Viking.Online ?? false,
                    OnMobile = false,
                    BestBuddy = buddy.IsBestFriend1
                };

            // add buddy
            schemaBuddies.Add(schemaBuddy);
        }

        // return buddy list
        return new BuddyList { Buddy = schemaBuddies.ToArray() };
    }
}
