using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using AyaNova.Util;
using AyaNova.Api.ControllerHelpers;
using AyaNova.Models;
using System.Collections.Generic;
using System;
using System.Linq;


namespace AyaNova.Biz
{

    internal class TagBiz : BizObject//, IJobObject
    {
        internal TagBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles)
        {
            ct = dbcontext;
            UserId = currentUserId;
            UserTranslationId = userTranslationId;
            CurrentUserRoles = UserRoles;
            BizType = AyaType.NoType;
        }

        internal static TagBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null)
        {
            if (httpContext != null)
                return new TagBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items));
            else
                return new TagBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdminFull);
        }


        #region Utilities
        /////////////////////////////////////
        //UTILITIES
        //

        //clean up tags from client submission
        //remove dupes, substitute dashes for spaces, lowercase and shorten if exceed 255 chars
        //and sorts before returning to ensure consistent ordering
        public static List<string> NormalizeTags(List<string> inTags)
        {
            if (inTags == null || inTags.Count == 0) return inTags;

            List<string> outTags = new List<string>();
            foreach (var tag in inTags)
                outTags.Add(NormalizeTag(tag));
            outTags.Sort();
            return outTags.Distinct().ToList();
        }


        public static string NormalizeTag(string inObj)
        {
            if (string.IsNullOrWhiteSpace(inObj)) return null;
            //Must be lowercase per rules 
            //This may be naive when we get international cust omers but for now supporting utf-8 and it appears it's safe to do this with unicode
            inObj = inObj.ToLowerInvariant();
            //No spaces in tags, replace with dashes
            inObj = inObj.Replace(" ", "-");
            //Remove multiple dash sequences
            inObj = System.Text.RegularExpressions.Regex.Replace(inObj, "-+", "-");
            //Ensure doesn't start or end with a dash
            inObj = inObj.Trim('-');
            //No longer than 255 characters
            inObj = StringUtil.MaxLength(inObj, 255);
            return inObj;
        }



        public static async Task ProcessDeleteTagsInRepositoryAsync(AyContext ct, List<string> deleteTags)
        {
            if (deleteTags.Count == 0) return;
            foreach (string s in deleteTags)
            {
                bool bDone = false;
                //Keep on trying until there is success
                //this allows for concurrency issues
                //I considered a circuit breaker / timeout here, but theoretically it should not be an issue
                //at some point it should not be a concurrency issue anymore
                //And this is not critical functionality requiring a transaction and locking 

                //TODO: Move this into a stored procedure just like search keyword processing for efficiency
                //it's likely not going to be an issue but still, it's not right to do it this way
                do
                {
                    //START: Get tag word and concurrency token and count
                    var ExistingTag = await ct.Tag.FirstOrDefaultAsync(z => z.Name == s);
                    //if not present, then nothing to do
                    if (ExistingTag != null)
                    {
                        //No longer needed?
                        if (ExistingTag.RefCount == 1)
                        {
                            ct.Remove(ExistingTag);

                        }
                        else
                        {
                            //Decrement the refcount
                            ExistingTag.RefCount -= 1;
                        }

                        try
                        {
                            await ct.SaveChangesAsync();
                            bDone = true;
                        }
                        catch (Exception ex) when (ex is DbUpdateConcurrencyException)//allow for possible other types
                        {
                            //allow others to flow past
                            // string sss = ex.ToString();
                        }
                    }
                } while (bDone == false);
            }
        }

        public static async Task ProcessUpdateTagsInRepositoryAsync(AyContext ct, List<string> newTags, List<string> originalTags = null)
        {
            //just in case no new tags are present which could mean a user removed all tags from a record so this
            //needs to proceed with the code below even if newTags is null as long as originalTags isn't also null
            if (newTags == null) newTags = new List<string>();
            if (originalTags == null) originalTags = new List<string>();

            if (newTags.Count == 0 && originalTags.Count == 0) return;

            List<string> deleteTags = new List<string>();
            List<string> addTags = new List<string>();

            if (originalTags != null)
            {
                //Update
                //This logic to only come up with CHANGES, if the item is in both lists then it will disappear and not need to be dealt with as it's refcount is unchanged in this operation
                //testing will validate it
                deleteTags = originalTags.Except(newTags).ToList();
                addTags = newTags.Except(originalTags).ToList();
            }
            else
            {
                //Add
                addTags = newTags;
            }


            //ADD / INCREMENT TAGS
            foreach (string s in addTags)
            {
                bool bDone = false;
                //Keep on trying until there is success
                //this allows for concurrency issues
                //I considered a circuit breaker / timeout here, but theoretically it should not be an issue
                //at some point it should not be a concurrency issue anymore
                do
                {
                    //START: Get tag word and concurrency token and count
                    var ExistingTag = await ct.Tag.FirstOrDefaultAsync(z => z.Name == s);
                    //if not present, then add it with a count of 0
                    if (ExistingTag == null)
                    {
                        await ct.Tag.AddAsync(new Tag() { Name = s, RefCount = 1 });
                    }
                    else
                    {
                        //Update the refcount
                        ExistingTag.RefCount += 1;
                    }
                    try
                    {
                        await ct.SaveChangesAsync();
                        bDone = true;
                    }
                    catch (Exception ex) when (ex is DbUpdateConcurrencyException)//this allows for other types
                    {
                        //allow others to flow past
                        //string sss = ex.ToString();
                    }
                } while (bDone == false);
            }

            //DELETE TAGS
            await ProcessDeleteTagsInRepositoryAsync(ct, deleteTags);
        }



        //Pick list for driving pick list route
        //going with contains for now as I think it's more useful in the long run and still captures startswith intent by user
        public static async Task<List<string>> TagListFilteredAsync(AyContext ct, string q)
        {
            //This path is intended for internal use and accepts that there may not be a filter specified
            //however the client will always require a filter to display a tag list for choosing from
            if (string.IsNullOrWhiteSpace(q))
            {
                return await ct.Tag.OrderBy(z => z.Name)
                .Select(z => z.Name)
                .AsNoTracking()
                .ToListAsync();
            }
            else
            {
                q = NormalizeTag(q);
                return await ct.Tag
                .Where(z => z.Name.Contains(q))
                .OrderBy(z => z.Name)
                .Select(z => z.Name)
                .Take(25)
                .AsNoTracking()
                .ToListAsync();
            }
        }

        //Cloud list
        public static async Task<List<TagCloudItem>> CloudListFilteredAsync(AyContext ct, string q)
        {
            //This path is intended for internal use and accepts that there may not be a filter specified
            //however the client will always require a filter to display a tag list for choosing from
            if (string.IsNullOrWhiteSpace(q))
            {
                return await ct.Tag.OrderByDescending(z => z.RefCount).Select(z => new TagCloudItem() { Name = z.Name, RefCount = z.RefCount }).AsNoTracking().ToListAsync();
            }
            else
            {
                q = NormalizeTag(q);
                //TODO: Use the EF CORE TAKE method to restrict the results to a maximum limit
                //however need to ensure it doesn't balk when the limit is higher than the number of results (probably not but test that)
                return await ct.Tag
                .Where(z => z.Name.Contains(q))
                .OrderByDescending(z => z.RefCount)
                .Select(z => new TagCloudItem() { Name = z.Name, RefCount = z.RefCount })
                .AsNoTracking()
                .ToListAsync();
            }
        }
        public class TagCloudItem
        {
            public long RefCount { get; set; }
            public string Name { get; set; }
        }




        /// <summary>
        /// Process bulk tag operation
        /// </summary>       
        /// <returns>true if object needs to be saved or false if no changes were made</returns>
        internal static bool ProcessBulkTagOperation(List<string> tagCollection, string tag, string toTag, JobSubType subType)
        {
            switch (subType)
            {
                case JobSubType.TagAddAny:
                case JobSubType.TagAdd:
                    if (!tagCollection.Contains(tag))
                    {
                        tagCollection.Add(tag);
                        return true;
                    }
                    return false;
                case JobSubType.TagRemoveAny:
                case JobSubType.TagRemove:
                    return tagCollection.Remove(tag);
                case JobSubType.TagReplaceAny:
                case JobSubType.TagReplace:
                    int index = tagCollection.IndexOf(tag);
                    if (index != -1)
                    {
                        tagCollection[index] = toTag;
                        return true;
                    }
                    return false;
                default:
                    throw new System.ArgumentOutOfRangeException($"ProcessBulkTagOperation -> Invalid job Subtype{subType}");
            }
        }


        #endregion utilities




        ////////////////////////////////////////////////////////////////////////////////////////////////
        //JOB / OPERATIONS
        //



        //Other job handlers here...


        /////////////////////////////////////////////////////////////////////

    }//eoc


}//eons

