using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Linq;
using Sockeye.Util;
using Sockeye.Api.ControllerHelpers;
using Sockeye.Models;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Sockeye.Biz
{
    internal class TrialLicenseRequestBiz : BizObject, IJobObject, ISearchAbleObject, IReportAbleObject, IExportAbleObject, INotifiableObject
    {
        internal TrialLicenseRequestBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles)
        {
            ct = dbcontext;
            UserId = currentUserId;
            UserTranslationId = userTranslationId;
            CurrentUserRoles = UserRoles;
            BizType = SockType.TrialLicenseRequest;
        }

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

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

        ////////////////////////////////////////////////////////////////////////////////////////////////
        //CREATE   
        //
        internal async Task<TrialLicenseRequest> CreateAsync(TrialLicenseRequest newObject, bool importingDoNotNotify = false)
        {
            await ValidateAsync(newObject, null);
            if (HasErrors)
                return null;
            else
            {
                newObject.Tags = TagBiz.NormalizeTags(newObject.Tags);

                //Process a new request / Generate an email confirm code
                newObject.EmailConfirmCode = StringUtil.GenFetchCode();
                newObject.EmailValidated = false;
                newObject.Status = TrialRequestStatus.AwaitingEmailValidation;

                await ct.TrialLicenseRequest.AddAsync(newObject);
                await ct.SaveChangesAsync();
                await EventLogProcessor.LogEventToDatabaseAsync(new Event(1, newObject.Id, BizType, SockEvent.Created), ct);
                await SearchIndexAsync(newObject, true);
                await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);


                if (!importingDoNotNotify)
                {
                    //## ------------------ DEFAULT NOTIFICATIONS TO CUSTOMER ----------------
                    //ValidateEmail request message,RavenTrialApproved (which sends pending manual generation message) and RavenTrialRejected messages are sent in this block
                    //
                    //Send verification request
                    var verifyUrl = ServerGlobalOpsSettingsCache.Notify.SockeyeServerURL.Trim().TrimEnd('/') + $"/rvr/verify/{newObject.EmailConfirmCode}";
                    var body = ServerGlobalBizSettings.Cache.ValidateEmail.Replace("{verifyUrl}", verifyUrl);//$"Please verify your email address by clicking the link below or copy and pasting into a browser\r\n{verifyUrl}\r\nOnce your email is verified the request will be processed manually during business hours.\r\n(If you did not request this you can ignore this message)";

                    var notifyDirectSMTP = new Sockeye.Api.Controllers.NotifyController.NotifyDirectSMTP()
                    {
                        ToAddress = newObject.Email,
                        Subject = "AyaNova trial request email verification",
                        TextBody = body
                    };


                    IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer;
                    try
                    {
                        await m.SendEmailAsync(notifyDirectSMTP.ToAddress, notifyDirectSMTP.Subject, notifyDirectSMTP.TextBody, ServerGlobalOpsSettingsCache.Notify, null, null, null);
                        await EventLogProcessor.LogEventToDatabaseAsync(new Event(1, notifyDirectSMTP.ObjectId, notifyDirectSMTP.SockType, SockEvent.DirectSMTP, $"\"{notifyDirectSMTP.Subject}\"->{notifyDirectSMTP.ToAddress}"), ct);
                    }
                    catch (Exception ex)
                    {
                        var err = "TrialLicenseRequest sending email confirmation request: SMTP direct message failed";
                        await NotifyEventHelper.AddOpsProblemEvent(err, ex);
                        AddError(ApiErrorCode.API_SERVER_ERROR, null, err + ExceptionUtil.ExtractAllExceptionMessages(ex));
                        return null;
                    }
                }

                await HandlePotentialNotificationEvent(SockEvent.Created, newObject);
                return newObject;
            }
        }



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

        ////////////////////////////////////////////////////////////////////////////////////////////////
        //UPDATE
        //
        internal async Task<TrialLicenseRequest> PutAsync(TrialLicenseRequest putObject)
        {
            var dbObject = await GetAsync(putObject.Id, false);
            if (dbObject == null)
            {
                AddError(ApiErrorCode.NOT_FOUND, "id");
                return null;
            }
            if (dbObject.Concurrency != putObject.Concurrency)
            {
                AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
                return null;
            }

            putObject.Tags = TagBiz.NormalizeTags(putObject.Tags);
            await ValidateAsync(putObject, dbObject);
            if (HasErrors) return null;


            //IF WE HAVE APPROVED A REQUEST THEN GENERATE TRIAL LICENSE, NOTIFY USER     
            //Notice to future coders who might see this or anything else in Sockeye that seems a bit ugly and spaghetti coded:
            //I'm writing this in early January of 2023 in the time I get to work on it while my wife of 25 years sleeps off her palliative chemotherapy for terminal pancreatic cancer
            //I'm fully aware this code is bodged together and just don't care right now, if it works it works.
            //I need this up and running so I can spend more time with my wife and less time managing the detailed fuckery of the business
            //that can be automated away while still keeping things going for now  
            if (dbObject.Status == TrialRequestStatus.AwaitingApproval && putObject.Status == TrialRequestStatus.Approved)
            {
                //APPROVED, generate and save key to be approved for release by us
                putObject.Processed = DateTime.UtcNow;
                License l = new License();
                l.Active = true;//Released for pickup
                l.DbId = putObject.DbId;
                l.PGroup = putObject.PGroup;
                l.TrialMode = true;
                l.RegTo = putObject.CompanyName;
                //perpet and sub both same number of users, covers seeded data values
                l.Users = RavenKeyFactory.TRIAL_KEY_USERS;

                if (putObject.PGroup == ProductGroup.RavenPerpetual)
                {
                    l.LicenseExpire = l.MaintenanceExpire = DateTime.UtcNow.AddDays(RavenKeyFactory.TRIAL_KEY_PERPETUAL_PERIOD_DAYS);
                }
                if (putObject.PGroup == ProductGroup.RavenSubscription)
                {
                    l.LicenseExpire = l.MaintenanceExpire = DateTime.UtcNow.AddDays(RavenKeyFactory.TRIAL_KEY_SUBSCRIPTION_PERIOD_DAYS);
                    l.CustomerUsers = RavenKeyFactory.TRIAL_KEY_SUBSCRIPTION_CUSTOMER_USERS;
                    l.MaxDataGB = RavenKeyFactory.TRIAL_KEY_SUBSCRIPTION_MAX_DATA_GB;
                }

                LicenseBiz licenseBiz = LicenseBiz.GetBiz(ct);
                var newLicense = await licenseBiz.CreateAsync(l);
                if (newLicense == null)
                {
                    //need to alert on error here
                    AddError(ApiErrorCode.INVALID_OPERATION, "generalerror", $"ERROR creating trial license on approved trial request:{licenseBiz.GetErrorsAsString}");
                    return null;
                }

                //all is well, new license was created
                putObject.LicenseId = newLicense.Id;

                //Notify User
                /*
                 var body = $"Your trial license request has been approved.\r\nThe license will fetch and install automatically shortly or you can fetch it now in the License form menu.";
           //send confirmation email
           RfMail.SendMessage("support@ayanova.com", trial.Email, "AyaNova trial request approved", body, false);

                */
                var notifyDirectSMTP = new Sockeye.Api.Controllers.NotifyController.NotifyDirectSMTP()
                {
                    ToAddress = putObject.Email,
                    Subject = "AyaNova trial request approved",//todo move to global settings
                    TextBody = ServerGlobalBizSettings.Cache.RavenTrialApproved
                };

                IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer;
                try
                {
                    await m.SendEmailAsync(notifyDirectSMTP.ToAddress, notifyDirectSMTP.Subject, notifyDirectSMTP.TextBody, ServerGlobalOpsSettingsCache.Notify, null, null, null);
                    await EventLogProcessor.LogEventToDatabaseAsync(new Event(1, notifyDirectSMTP.ObjectId, notifyDirectSMTP.SockType, SockEvent.DirectSMTP, $"\"{notifyDirectSMTP.Subject}\"->{notifyDirectSMTP.ToAddress}"), ct);
                }
                catch (Exception ex)
                {
                    var err = "TrialLicenseRequest sending approved email: SMTP direct message failed";
                    await NotifyEventHelper.AddOpsProblemEvent(err, ex);
                    AddError(ApiErrorCode.API_SERVER_ERROR, null, err + ExceptionUtil.ExtractAllExceptionMessages(ex));
                    return null;
                }
            }

            if (dbObject.Status == TrialRequestStatus.AwaitingApproval && putObject.Status == TrialRequestStatus.Rejected)
            {
                //REJECTED, send email
                putObject.Processed = DateTime.UtcNow;

                /*
                 string reason = string.Empty;
                if (!string.IsNullOrWhiteSpace(rejectReason))
                {
                    reason = $"The request was rejected due to:\r\n{rejectReason}";
                }
                var body = $"Your trial license request was not approved.\r\n{reason}";
                //send confirmation email
                RfMail.SendMessage("support@ayanova.com", trial.Email, "AyaNova trial request not approved", body, false);
                */

                string reason = string.Empty;
                if (!string.IsNullOrWhiteSpace(putObject.RejectReason))
                {
                    reason = $"The request was rejected due to:\r\n{putObject.RejectReason}";
                }
                var notifyDirectSMTP = new Sockeye.Api.Controllers.NotifyController.NotifyDirectSMTP()
                {
                    ToAddress = putObject.Email,
                    Subject = "AyaNova trial request not approved",//todo move to global settings
                    TextBody = ServerGlobalBizSettings.Cache.RavenTrialRejected.Replace("{reason}", reason)
                };

                IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer;
                try
                {
                    await m.SendEmailAsync(notifyDirectSMTP.ToAddress, notifyDirectSMTP.Subject, notifyDirectSMTP.TextBody, ServerGlobalOpsSettingsCache.Notify, null, null, null);
                    await EventLogProcessor.LogEventToDatabaseAsync(new Event(1, notifyDirectSMTP.ObjectId, notifyDirectSMTP.SockType, SockEvent.DirectSMTP, $"\"{notifyDirectSMTP.Subject}\"->{notifyDirectSMTP.ToAddress}"), ct);
                }
                catch (Exception ex)
                {
                    var err = "TrialLicenseRequest sending rejected email: SMTP direct message failed";
                    await NotifyEventHelper.AddOpsProblemEvent(err, ex);
                    AddError(ApiErrorCode.API_SERVER_ERROR, null, err + ExceptionUtil.ExtractAllExceptionMessages(ex));
                    return null;
                }
            }


            ct.Replace(dbObject, putObject);
            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, putObject.Id, BizType, SockEvent.Modified), ct);
            await SearchIndexAsync(putObject, false);
            await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, putObject.Tags, dbObject.Tags);
            await HandlePotentialNotificationEvent(SockEvent.Modified, putObject, dbObject);
            return putObject;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////
        //DELETE
        //
        internal async Task<bool> DeleteAsync(long id)
        {
            using (var transaction = await ct.Database.BeginTransactionAsync())
            {

                TrialLicenseRequest dbObject = await GetAsync(id, false);
                if (dbObject == null)
                {
                    AddError(ApiErrorCode.NOT_FOUND);
                    return false;
                }
                await ValidateCanDeleteAsync(dbObject);
                if (HasErrors)
                    return false;




                {
                    var IDList = await ct.Review.AsNoTracking().Where(x => x.SockType == SockType.TrialLicenseRequest && x.ObjectId == id).Select(x => x.Id).ToListAsync();
                    if (IDList.Count() > 0)
                    {
                        ReviewBiz b = new ReviewBiz(ct, UserId, UserTranslationId, CurrentUserRoles);
                        foreach (long ItemId in IDList)
                            if (!await b.DeleteAsync(ItemId, transaction))
                            {
                                AddError(ApiErrorCode.CHILD_OBJECT_ERROR, null, $"Review [{ItemId}]: {b.GetErrorsAsString()}");
                                return false;
                            }
                    }
                }

                ct.TrialLicenseRequest.Remove(dbObject);
                await ct.SaveChangesAsync();

                //Log event 
                await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct);
                await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct);
                await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
                await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
                await transaction.CommitAsync();
                await HandlePotentialNotificationEvent(SockEvent.Deleted, dbObject);

                return true;
            }
        }




        ////////////////////////////////////////////////////////////////////////////////////////////////
        //SEARCH
        //
        private async Task SearchIndexAsync(TrialLicenseRequest obj, bool isNew)
        {
            var SearchParams = new Search.SearchIndexProcessObjectParameters(UserTranslationId, obj.Id, BizType);
            DigestSearchText(obj, SearchParams);
            if (isNew)
                await Search.ProcessNewObjectKeywordsAsync(SearchParams);
            else
                await Search.ProcessUpdatedObjectKeywordsAsync(SearchParams);
        }

        public async Task<Search.SearchIndexProcessObjectParameters> GetSearchResultSummary(long id, SockType specificType)
        {
            var obj = await GetAsync(id, false);
            var SearchParams = new Search.SearchIndexProcessObjectParameters();
            DigestSearchText(obj, SearchParams);
            return SearchParams;
        }

        public void DigestSearchText(TrialLicenseRequest obj, Search.SearchIndexProcessObjectParameters searchParams)
        {
            if (obj != null)
                searchParams.AddText(obj.DbId)
                            .AddText(obj.CompanyName)
                            .AddText(obj.ContactName)
                            .AddText(obj.Email)
                            .AddText(obj.EmailConfirmCode)
                            .AddText(obj.RejectReason)
                            .AddText(obj.Tags);
        }

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

        private async Task ValidateAsync(TrialLicenseRequest proposedObj, TrialLicenseRequest currentObj)
        {
            //no need to validate required fields, they are set to required in the model with an attribute 
            //no need to validate required fields, they are set to required in the model with an attribute 
            //no need to validate required fields, they are set to required in the model with an attribute 
            await Task.CompletedTask;
            // bool isNew = currentObj == null;
            //no need to validate required fields, they are set to required in the model with an attribute 
            //no need to validate required fields, they are set to required in the model with an attribute 
            //no need to validate required fields, they are set to required in the model with an attribute 


        }


        private async Task ValidateCanDeleteAsync(TrialLicenseRequest inObj)
        {

            await Task.CompletedTask;
        }


        ////////////////////////////////////////////////////////////////////////////////////////////////
        //REPORTING
        //
        public async Task<JArray> GetReportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId)
        {
            var idList = dataListSelectedRequest.SelectedRowIds;
            JArray ReportData = new JArray();
            while (idList.Any())
            {
                var batch = idList.Take(IReportAbleObject.REPORT_DATA_BATCH_SIZE);
                idList = idList.Skip(IReportAbleObject.REPORT_DATA_BATCH_SIZE).ToArray();

                //query for this batch, comes back in db natural order unfortunately
                var batchResults = await ct.TrialLicenseRequest.AsNoTracking().Where(z => batch.Contains(z.Id)).ToArrayAsync();

                //order the results back into original
                //What is happening here:
                //for performance the query is batching a bunch at once by fetching a block of items from the sql server
                //however it's returning in db order which is often not the order the id list is in
                //so it needs to be sorted back into the same order as the ide list
                //This would not be necessary if just fetching each one at a time individually (like in workorder get report data)

                var orderedList = from id in batch join z in batchResults on id equals z.Id select z;
                batchResults = null;

                foreach (TrialLicenseRequest w in orderedList)
                {
                    if (!ReportRenderManager.KeepGoing(jobId)) return null;
                    var jo = JObject.FromObject(w);
                    if (!JsonUtil.JTokenIsNullOrEmpty(jo["CustomFields"]))
                        jo["CustomFields"] = JObject.Parse((string)jo["CustomFields"]);
                    ReportData.Add(jo);
                }
                orderedList = null;
            }
            vc.Clear();
            return ReportData;
        }
        private VizCache vc = new VizCache();




        ////////////////////////////////////////////////////////////////////////////////////////////////       
        // IMPORT EXPORT
        //

        public async Task<JArray> GetExportData(DataListSelectedRequest dataListSelectedRequest, Guid jobId)
        {
            return await GetReportData(dataListSelectedRequest, jobId);
        }


        ////////////////////////////////////////////////////////////////////////////////////////////////
        //JOB / OPERATIONS
        //
        public async Task HandleJobAsync(OpsJob job)
        {
            //Hand off the particular job to the corresponding processing code
            //NOTE: If this code throws an exception the caller (JobsBiz::ProcessJobsAsync) will automatically set the job to failed and log the exeption so 
            //basically any error condition during job processing should throw up an exception if it can't be handled
            switch (job.JobType)
            {
                case JobType.BatchCoreObjectOperation:
                    await ProcessBatchJobAsync(job);
                    break;
                default:
                    throw new System.ArgumentOutOfRangeException($"TrialLicenseRequestBiz.HandleJob-> Invalid job type{job.JobType.ToString()}");
            }
        }



        private async Task ProcessBatchJobAsync(OpsJob job)
        {
            await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Running);
            await JobsBiz.LogJobAsync(job.GId, $"LT:StartJob {job.SubType}");
            List<long> idList = new List<long>();
            long FailedObjectCount = 0;
            JObject jobData = JObject.Parse(job.JobInfo);
            if (jobData.ContainsKey("idList"))
                idList = ((JArray)jobData["idList"]).ToObject<List<long>>();
            else
                idList = await ct.TrialLicenseRequest.AsNoTracking().Select(z => z.Id).ToListAsync();
            bool SaveIt = false;

            //---------------------------------
            //case 4192
            TimeSpan ProgressAndCancelCheckSpan = new TimeSpan(0, 0, ServerBootConfig.JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS);
            DateTime LastProgressCheck = DateTime.UtcNow.Subtract(new TimeSpan(1, 1, 1, 1, 1));
            var TotalRecords = idList.LongCount();
            long CurrentRecord = -1;
            //---------------------------------

            foreach (long id in idList)
            {
                try
                {
                    //--------------------------------
                    //case 4192
                    //Update progress / cancel requested?                    
                    CurrentRecord++;
                    if (DateUtil.IsAfterDuration(LastProgressCheck, ProgressAndCancelCheckSpan))
                    {
                        await JobsBiz.UpdateJobProgressAsync(job.GId, $"{CurrentRecord}/{TotalRecords}");
                        if (await JobsBiz.GetJobStatusAsync(job.GId) == JobStatus.CancelRequested)
                            break;
                        LastProgressCheck = DateTime.UtcNow;
                    }
                    //---------------------------------

                    SaveIt = false;
                    ClearErrors();
                    TrialLicenseRequest o = null;
                    //save a fetch if it's a delete
                    if (job.SubType != JobSubType.Delete)
                        o = await GetAsync(id, false);
                    switch (job.SubType)
                    {
                        case JobSubType.TagAddAny:
                        case JobSubType.TagAdd:
                        case JobSubType.TagRemoveAny:
                        case JobSubType.TagRemove:
                        case JobSubType.TagReplaceAny:
                        case JobSubType.TagReplace:
                            SaveIt = TagBiz.ProcessBatchTagOperation(o.Tags, (string)jobData["tag"], jobData.ContainsKey("toTag") ? (string)jobData["toTag"] : null, job.SubType);
                            break;
                        case JobSubType.Delete:
                            if (!await DeleteAsync(id))
                            {
                                await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}");
                                FailedObjectCount++;
                            }
                            break;
                        default:
                            throw new System.ArgumentOutOfRangeException($"ProcessBatchJobAsync -> Invalid job Subtype{job.SubType}");
                    }
                    if (SaveIt)
                    {
                        o = await PutAsync(o);
                        if (o == null)
                        {
                            await JobsBiz.LogJobAsync(job.GId, $"LT:Errors {GetErrorsAsString()} id {id}");
                            FailedObjectCount++;
                        }
                    }

                    //delay so we're not tying up all the resources in a tight loop
                    await Task.Delay(Sockeye.Util.ServerBootConfig.JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY);
                }
                catch (Exception ex)
                {
                    await JobsBiz.LogJobAsync(job.GId, $"LT:Errors id({id})");
                    await JobsBiz.LogJobAsync(job.GId, ExceptionUtil.ExtractAllExceptionMessages(ex));
                }
            }

            //---------------------------------
            //case 4192
            await JobsBiz.UpdateJobProgressAsync(job.GId, $"{++CurrentRecord}/{TotalRecords}");
            //---------------------------------

            await JobsBiz.LogJobAsync(job.GId, $"LT:BatchJob {job.SubType} {idList.Count}{(FailedObjectCount > 0 ? " - LT:Failed " + FailedObjectCount : "")}");
            await JobsBiz.UpdateJobStatusAsync(job.GId, JobStatus.Completed);
        }






        ////////////////////////////////////////////////////////////////////////////////////////////////
        // NOTIFICATION PROCESSING
        //
        public async Task HandlePotentialNotificationEvent(SockEvent ayaEvent, ICoreBizObjectModel proposedObj, ICoreBizObjectModel currentObj = null)
        {
            if (ServerBootConfig.MIGRATING) return;
            ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger<TrialLicenseRequestBiz>();

            log.LogDebug($"HandlePotentialNotificationEvent processing:  [SockType:{this.BizType}, AyaEvent:{ayaEvent}]");

            bool isNew = currentObj == null;

            proposedObj.Name = "TRIAL LICENSE REQUEST ID" + proposedObj.Id.ToString();
            //STANDARD EVENTS FOR ALL OBJECTS
            await NotifyEventHelper.ProcessStandardObjectEvents(ayaEvent, proposedObj, ct);

            //SPECIFIC EVENTS FOR THIS OBJECT
            TrialLicenseRequest o = (TrialLicenseRequest)proposedObj;

            //# TRIAL KEY REQUEST EMAIL CONFIRMED - NOTIFY US IS READY FOR PROCESSING               
            if (ayaEvent == SockEvent.Modified && o.EmailValidated && o.Status == TrialRequestStatus.AwaitingApproval && o.Processed == null)
            {
                {
                    //Newly created only and immediate delivery so no need to remove
                    var subs = await ct.NotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.LicenseTrialRequestReceived).ToListAsync();
                    foreach (var sub in subs)
                    {
                        //not for inactive users
                        if (!await UserBiz.UserIsActive(sub.UserId)) continue;

                        NotifyEvent n = new NotifyEvent()
                        {
                            EventType = NotifyEventType.LicenseTrialRequestReceived,
                            UserId = sub.UserId,
                            SockType = BizType,
                            ObjectId = o.Id,
                            NotifySubscriptionId = sub.Id,
                            Name = $"{o.CompanyName} - trial license requested"
                        };
                        await ct.NotifyEvent.AddAsync(n);
                        log.LogDebug($"Adding NotifyEvent:  [{n.ToString()}]");
                        await ct.SaveChangesAsync();
                    }
                }
            }//new trial key request event





            //  todo: maybe this should be a direct smtp message, not bother with the notification system at all as that's how it was done in rockfish and it's kind of orthogonal

            // if ( == !string.IsNullOrWhiteSpace(o.Email))//can this customer receive *any* customer notifications?
            // {
            //     //-------- Customer is notifiable ------

            //     // //# STATUS CHANGE (create new status)
            //     // {
            //     //     //Conditions: must match specific status id value and also tags below
            //     //     //delivery is immediate so no need to remove old ones of this kind
            //     //     var subs = await ct.CustomerNotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.WorkorderStatusChange && z.IdValue == oProposed.WorkOrderStatusId).OrderBy(z => z.Id).ToListAsync();
            //     //     foreach (var sub in subs)
            //     //     {
            //     //         //Object tags must match and Customer tags must match
            //     //         if (NotifyEventHelper.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags) && NotifyEventHelper.ObjectHasAllSubscriptionTags(custInfo.Tags, sub.CustomerTags))
            //     //         {
            //     //             CustomerNotifyEvent n = new CustomerNotifyEvent()
            //     //             {
            //     //                 EventType = NotifyEventType.WorkorderStatusChange,
            //     //                 CustomerId = WorkorderInfo.CustomerId,
            //     //                 AyaType = AyaType.WorkOrder,
            //     //                 ObjectId = oProposed.WorkOrderId,
            //     //                 CustomerNotifySubscriptionId = sub.Id,
            //     //                 Name = WorkorderInfo.Serial.ToString()
            //     //             };
            //     //             await ct.CustomerNotifyEvent.AddAsync(n);
            //     //             log.LogDebug($"Adding CustomerNotifyEvent:  [{n.ToString()}]");
            //     //             await ct.SaveChangesAsync();
            //     //             break;//we have a match no need to process any further subs for this event
            //     //         }
            //     //     }
            //     // }//workorder status change event


            //     // //# STATUS AGE 
            //     // {
            //     //     //WorkorderStatusAge = 24,//* Workorder STATUS unchanged for set time (stuck in state), conditional on: Duration (how long stuck), exact status selected IdValue, Tags. Advance notice can NOT be set
            //     //     //Always clear any old ones for this object as they are all irrelevant the moment the state has changed:
            //     //     await NotifyEventHelper.ClearPriorCustomerNotifyEventsForObject(ct, proposedObj.AyaType, proposedObj.Id, NotifyEventType.WorkorderStatusAge);
            //     //     var subs = await ct.CustomerNotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.WorkorderStatusAge && z.IdValue == oProposed.WorkOrderStatusId).OrderBy(z => z.Id).ToListAsync();
            //     //     foreach (var sub in subs)
            //     //     {
            //     //         //Object tags must match and Customer tags must match
            //     //         if (NotifyEventHelper.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags) && NotifyEventHelper.ObjectHasAllSubscriptionTags(custInfo.Tags, sub.CustomerTags))
            //     //         {
            //     //             CustomerNotifyEvent n = new CustomerNotifyEvent()
            //     //             {
            //     //                 EventType = NotifyEventType.WorkorderStatusAge,
            //     //                 CustomerId = WorkorderInfo.CustomerId,
            //     //                 AyaType = AyaType.WorkOrder,
            //     //                 ObjectId = oProposed.WorkOrderId,
            //     //                 CustomerNotifySubscriptionId = sub.Id,
            //     //                 Name = WorkorderInfo.Serial.ToString()
            //     //             };
            //     //             await ct.CustomerNotifyEvent.AddAsync(n);
            //     //             log.LogDebug($"Adding CustomerNotifyEvent:  [{n.ToString()}]");
            //     //             await ct.SaveChangesAsync();
            //     //             break;//we have a match no need to process any further subs for this event
            //     //         }
            //     //     }
            //     // }//workorder status age event


            //     // //# WorkorderCompleted 
            //     // {
            //     //     if (wos.Completed)
            //     //     {
            //     //         var subs = await ct.CustomerNotifySubscription.AsNoTracking().Where(z => z.EventType == NotifyEventType.WorkorderCompleted).OrderBy(z => z.Id).ToListAsync();
            //     //         foreach (var sub in subs)
            //     //         {
            //     //             //Object tags must match and Customer tags must match
            //     //             if (NotifyEventHelper.ObjectHasAllSubscriptionTags(WorkorderInfo.Tags, sub.Tags) && NotifyEventHelper.ObjectHasAllSubscriptionTags(custInfo.Tags, sub.CustomerTags))
            //     //             {

            //     //                 CustomerNotifyEvent n = new CustomerNotifyEvent()
            //     //                 {
            //     //                     EventType = NotifyEventType.WorkorderCompleted,
            //     //                     CustomerId = WorkorderInfo.CustomerId,
            //     //                     AyaType = AyaType.WorkOrder,
            //     //                     ObjectId = oProposed.WorkOrderId,
            //     //                     CustomerNotifySubscriptionId = sub.Id,
            //     //                     Name = WorkorderInfo.Serial.ToString()
            //     //                 };
            //     //                 await ct.CustomerNotifyEvent.AddAsync(n);
            //     //                 log.LogDebug($"Adding CustomerNotifyEvent:  [{n.ToString()}]");
            //     //                 await ct.SaveChangesAsync();
            //     //                 break;//we have a match no need to process any further subs for this event
            //     //             }
            //     //         }
            //     //     }
            //     // }//WorkorderCompleted

            //     //-----------------------
            // } //------------------ /default notifications ---------------



        }//end of process notifications


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

    }//eoc


}//eons

