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

namespace AyaNova.Biz
{
    internal class ProjectBiz : BizObject, ISearchAbleObject
    {
        internal ProjectBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles)
        {
            ct = dbcontext;
            UserId = currentUserId;
            UserTranslationId = userTranslationId;
            CurrentUserRoles = UserRoles;
            BizType = AyaType.Project;
        }

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

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

        ////////////////////////////////////////////////////////////////////////////////////////////////
        //CREATE   
        //
        internal async Task<Project> CreateAsync(Project newObject)
        {
            await ValidateAsync(newObject, null);
            if (HasErrors)
                return null;
            else
            {
                newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);
                newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields);
                await ct.Project.AddAsync(newObject);
                await ct.SaveChangesAsync();
                await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct);
                await SearchIndexAsync(newObject, true);
                await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
                return newObject;
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////
        //DUPLICATE
        //
        internal async Task<Project> DuplicateAsync(long id)
        {
            Project dbObject = await GetAsync(id, false);
            if (dbObject == null)
            {
                AddError(ApiErrorCode.NOT_FOUND, "id");
                return null;
            }
            Project newObject = new Project();
            CopyObject.Copy(dbObject, newObject, "Wiki");
            string newUniqueName = string.Empty;
            bool NotUnique = true;
            long l = 1;
            do
            {
                newUniqueName = Util.StringUtil.UniqueNameBuilder(dbObject.Name, l++, 255);
                NotUnique = await ct.Project.AnyAsync(m => m.Name == newUniqueName);
            } while (NotUnique);
            newObject.Name = newUniqueName;
            newObject.Id = 0;
            newObject.Concurrency = 0;
            await ct.Project.AddAsync(newObject);
            await ct.SaveChangesAsync();
            await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct);
            await SearchIndexAsync(newObject, true);
            await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
            return newObject;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////
        //GET 
        //      
        internal async Task<Project> GetAsync(long id, bool logTheGetEvent = true)
        {
            var ret = await ct.Project.SingleOrDefaultAsync(m => m.Id == id);
            if (logTheGetEvent && ret != null)
                await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, AyaEvent.Retrieved), ct);
            return ret;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////
        //UPDATE
        //
        internal async Task<Project> PutAsync(Project putObject)
        {
            Project dbObject = await ct.Project.SingleOrDefaultAsync(m => m.Id == putObject.Id);
            if (dbObject == null)
            {
                AddError(ApiErrorCode.NOT_FOUND, "id");
                return null;
            }
            Project SnapshotOfOriginalDBObj = new Project();
            CopyObject.Copy(dbObject, SnapshotOfOriginalDBObj);
            CopyObject.Copy(putObject, dbObject, "Id");
            dbObject.Tags = TagBiz.NormalizeTags(dbObject.Tags);
            dbObject.CustomFields = JsonUtil.CompactJson(dbObject.CustomFields);
            ct.Entry(dbObject).OriginalValues["Concurrency"] = putObject.Concurrency;
            await ValidateAsync(dbObject, SnapshotOfOriginalDBObj);
            if (HasErrors) return null;
            try
            {
                await ct.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!await ExistsAsync(putObject.Id))
                    AddError(ApiErrorCode.NOT_FOUND);
                else
                    AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
                return null;
            }
            await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct);
            await SearchIndexAsync(dbObject, false);
            await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, dbObject.Tags, SnapshotOfOriginalDBObj.Tags);
            return dbObject;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////
        //DELETE
        //
        internal async Task<bool> DeleteAsync(long id)
        {
            using (var transaction = await ct.Database.BeginTransactionAsync())
            {
                try
                {
                    Project dbObject = await ct.Project.SingleOrDefaultAsync(m => m.Id == id);
                    ValidateCanDelete(dbObject);
                    if (HasErrors)
                        return false;
                    if (HasErrors)
                        return false;
                    ct.Project.Remove(dbObject);
                    await ct.SaveChangesAsync();

                    //Log event 
                    await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct);
                    await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType);
                    await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
                    //all good do the commit
                    await transaction.CommitAsync();
                }
                catch
                {
                    //Just re-throw for now, let exception handler deal, but in future may want to deal with this more here
                    throw;
                }
                return true;
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////
        //SEARCH
        //
        private async Task SearchIndexAsync(Project obj, bool isNew)
        {
            var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType);
            SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
            if (isNew)
                await Search.ProcessNewObjectKeywordsAsync(SearchParams);
            else
                await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
        }

        public async Task<Search.SearchIndexProcessObjectParameters> GetSearchResultSummary(long id)
        {
            var obj = await ct.Project.SingleOrDefaultAsync(m => m.Id == id);
            var SearchParams = new Search.SearchIndexProcessObjectParameters();
            if (obj != null)
                SearchParams.AddText(obj.Notes).AddText(obj.Name).AddText(obj.Wiki).AddText(obj.Tags).AddCustomFields(obj.CustomFields);
            return SearchParams;
        }


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

        private async Task ValidateAsync(Project proposedObj, Project currentObj)
        {

            bool isNew = currentObj == null;

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

            //Name must be less than 255 characters
            if (proposedObj.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.Project.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id))
                {
                    AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name");
                }
            }


            //Any form customizations to validate?
            var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(x => x.FormKey == AyaType.Project.ToString());
            if (FormCustomization != null)
            {
                //Yeppers, do the validation, there are two, the custom fields and the regular fields that might be set to required

                //validate users choices for required non custom fields
                RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);

                //validate custom fields
                CustomFieldsValidator.Validate(this, FormCustomization, proposedObj.CustomFields);
            }

        }

        private void ValidateCanDelete(Project inObj)
        {
            //whatever needs to be check to delete this object
        }


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


        //Other job handlers here...


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

    }//eoc


}//eons

