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


namespace AyaNova.Biz
{


    internal class DataListViewBiz : BizObject
    {

        internal DataListViewBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles)
        {
            ct = dbcontext;
            UserId = currentUserId;
            UserTranslationId = userTranslationId;
            CurrentUserRoles = UserRoles;
            BizType = AyaType.DataListView;
        }

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



        ////////////////////////////////////////////////////////////////////////////////////////////////
        //EXISTS 
        internal async Task<bool> ExistsAsync(long id)
        {
            return await ct.DataListView.AnyAsync(z => z.Id == id);
        }



        ////////////////////////////////////////////////////////////////////////////////////////////////
        //CREATE        
        internal async Task<DataListView> CreateAsync(DataListView inObj)
        {
            await ValidateAsync(inObj, true);
            if (HasErrors)
                return null;
            else
            {
                //do stuff with datafilter
                DataListView outObj = inObj;
                outObj.UserId = UserId;


                await ct.DataListView.AddAsync(outObj);
                await ct.SaveChangesAsync();

                //Handle child and associated items:

                //EVENT LOG
                await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct);



                return outObj;

            }
        }




        ////////////////////////////////////////////////////////////////////////////////////////////////
        //DUPLICATE
        //

        internal async Task<DataListView> DuplicateAsync(DataListView dbObject)
        {

            DataListView outObj = new DataListView();
            CopyObject.Copy(dbObject, outObj);
            //generate unique name
            string newUniqueName = string.Empty;
            bool NotUnique = true;
            long l = 1;
            do
            {
                newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObject.Name, l++, 255);
                NotUnique = await ct.DataListView.AnyAsync(z => z.Name == newUniqueName);
            } while (NotUnique);
            outObj.Name = newUniqueName;
            outObj.Id = 0;
            outObj.Concurrency = 0;



            await ct.DataListView.AddAsync(outObj);
            await ct.SaveChangesAsync();

            //Handle child and associated items:                
            await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, outObj.Id, BizType, AyaEvent.Created), ct);

            return outObj;

        }

        ////////////////////////////////////////////////////////////////////////////////////////////////
        /// GET

        //Get one
        internal async Task<DataListView> GetAsync(long fetchId, bool logTheGetEvent = true)
        {
            //This is simple so nothing more here, but often will be copying to a different output object or some other ops           
            var ret = await ct.DataListView.SingleOrDefaultAsync(z => z.Id == fetchId && (z.Public == true || z.UserId == UserId));
            if (logTheGetEvent && ret != null)
            {
                //Log
                await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, fetchId, BizType, AyaEvent.Retrieved), ct);
            }
            return ret;
        }




        //get ViewList (NOT PAGED)
        internal async Task<List<NameIdItem>> GetViewListAsync(string listKey)
        {
            List<NameIdItem> items = new List<NameIdItem>();
            if (!string.IsNullOrWhiteSpace(listKey))
            {
                items = await ct.DataListView
                                .AsNoTracking()
                                .Where(z => z.ListKey == listKey && (z.Public == true || z.UserId == UserId))
                                .OrderBy(z => z.Name)
                                 .Select(z => new NameIdItem()
                                 {
                                     Id = z.Id,
                                     Name = z.Name
                                 }).ToListAsync();

            }
            return items;
        }




        ////////////////////////////////////////////////////////////////////////////////////////////////
        //UPDATE
        //

        //put
        internal async Task<bool> PutAsync(DataListView dbObject, DataListView inObj)
        {
            //preserve the owner ID if none was specified
            if (inObj.UserId == 0)
                inObj.UserId = dbObject.UserId;

            //Replace the db object with the PUT object
            CopyObject.Copy(inObj, dbObject, "Id");
            //Set "original" value of concurrency token to input token
            //this will allow EF to check it out
            ct.Entry(dbObject).OriginalValues["Concurrency"] = inObj.Concurrency;

            await ValidateAsync(dbObject, false);
            if (HasErrors)
                return false;
            await ct.SaveChangesAsync();

            //Log modification and save context
            await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct);



            return true;
        }


        ////////////////////////////////////////////////////////////////////////////////////////////////
        //DELETE
        //
        internal async Task<bool> DeleteAsync(DataListView dbObject)
        {
            //Determine if the object can be deleted, do the deletion tentatively
            //Probably also in here deal with tags and associated search text etc

            //FUTURE POSSIBLE NEED
            //ValidateCanDelete(dbObject);

            if (HasErrors)
                return false;
            ct.DataListView.Remove(dbObject);
            await ct.SaveChangesAsync();

            //Delete sibling objects

            //Event log process delete
            await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct);


            //Delete search index
            //Search.ProcessDeletedObjectKeywords(dbObject.Id, BizType);


            return true;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////
        //VALIDATION
        //

        //Can save or update?
        private async Task ValidateAsync(DataListView inObj, bool isNew)
        {

            //UserId required
            if (!isNew)
            {
                if (inObj.UserId == 0)
                    AddError(ApiErrorCode.VALIDATION_REQUIRED, "UserId");
            }



            //Name required
            if (string.IsNullOrWhiteSpace(inObj.Name))
                AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name");

            //Name must be less than 255 characters
            if (inObj.Name.Length > 255)
                AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "Name", "255 max");

            //If name is otherwise OK, check that name is unique
            if (!PropertyHasErrors("Name"))
            {
                //Use Any command is efficient way to check existance, it doesn't return the record, just a true or false
                if (await ct.DataListView.AnyAsync(z => z.Name == inObj.Name && z.Id != inObj.Id))
                {
                    AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name");
                }
            }

            if (string.IsNullOrWhiteSpace(inObj.ListKey))
                AddError(ApiErrorCode.VALIDATION_REQUIRED, "ListKey");


            var DataList = DataListFactory.GetAyaDataList(inObj.ListKey);

            if (DataList == null)
            {
                AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "ListKey", $"ListKey \"{inObj.ListKey}\" DataListKey is not valid");
            }



            if (inObj.ListKey.Length > 255)
                AddError(ApiErrorCode.VALIDATION_LENGTH_EXCEEDED, "ListKey", "255 max");

            //Filter json must parse
            //this is all automated normally so not going to do too much parsing here
            //just ensure it's basically there
            if (!string.IsNullOrWhiteSpace(inObj.ListView))
            {
                try
                {
                    var v = JArray.Parse(inObj.ListView);
                    for (int i = 0; i < v.Count; i++)
                    {
                        var filterItem = v[i];
                        if (filterItem["fld"] == null)
                            AddError(ApiErrorCode.VALIDATION_REQUIRED, "ListView", $"ListView array item {i}, object is missing required \"fld\" property ");
                        else
                        {
                            var fld = filterItem["fld"].Value<string>();
                            if (string.IsNullOrWhiteSpace(fld))
                                AddError(ApiErrorCode.VALIDATION_REQUIRED, "ListView", $"ListView array item {i}, \"fld\" property is empty and required");

                            //validate the field name if we can
                            if (DataList != null)
                            {

                                var TheField = DataList.FieldDefinitions.SingleOrDefault(z => z.FieldKey.ToLowerInvariant() == fld);

                                if (TheField == null)
                                {
                                    AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "ListView", $"ListView array item {i}, fld property value \"{fld}\" is not a valid value for ListKey specified");
                                }

                            }
                        }
                        //This is the old filter validation code but at this point only going to validate that the fields are present and valid as the bare minimum
                        // if (filterItem["op"] == null)
                        //     AddError(ApiErrorCode.VALIDATION_REQUIRED, "Filter", $"Filter array item {i}, object is missing required \"op\" property ");
                        // else
                        // {
                        //     var opType = filterItem["op"].Value<string>();
                        //     if (!DataListFilterComparisonOperator.Operators.Contains(opType))
                        //         AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "Filter", $"Filter array item {i}, \"op\" property value of \"{opType}\" is not a valid FilterComparisonOperator type");
                        // }

                        // if (filterItem["value"] == null)
                        //     AddError(ApiErrorCode.VALIDATION_REQUIRED, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property ");
                        // else
                        // {
                        //     //check if the value is present, not what it is exactly, just that it's present
                        //     //value also could contain relative date tokens, not that it checks them anyway but just noting it here
                        //     if (filterItem["value"].Type == JTokenType.String && string.IsNullOrWhiteSpace(filterItem["value"].Value<string>()))
                        //         AddError(ApiErrorCode.VALIDATION_REQUIRED, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property ");

                        //     if (filterItem["value"].Type == JTokenType.Array && filterItem["value"].Count() == 0)
                        //         AddError(ApiErrorCode.VALIDATION_REQUIRED, "Filter", $"Filter array item {i}, object is missing or is empty the required \"value\" property ARRAY ");
                        // }


                        //NOTE: value of nothing, null or empty is a valid value so no checking for it here
                    }
                }
                catch (Newtonsoft.Json.JsonReaderException ex)
                {
                    AddError(ApiErrorCode.VALIDATION_INVALID_VALUE, "ListView", "ListView is not valid JSON string: " + ex.Message);

                }
            }



            return;
        }



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

    }//eoc


}//eons

