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

namespace Sockeye.Biz
{

    /// <summary>
    /// Notification processor
    /// turn notifyEvent records into inappnotification records for in app viewing and / or deliver smtp notifications seperately
    /// 
    /// </summary>
    internal static class CoreJobCustomerNotify
    {
        private static bool NotifyIsRunning = false;
        private static ILogger log = Sockeye.Util.ApplicationLogging.CreateLogger("CoreJobCustomerNotify");
        private static DateTime lastRun = DateTime.MinValue;

#if (DEBUG)      
        private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 0, 21);//no more frequently than once every 20 seconds
#else 
        private static TimeSpan RUN_EVERY_INTERVAL = new TimeSpan(0, 1, 1);//no more frequently than once every 1 minute
#endif

        ////////////////////////////////////////////////////////////////////////////////////////////////
        // DoSweep
        //
        public static async Task DoWorkAsync()
        {
            log.LogTrace("Checking if CustomerNotify should run");
            if (NotifyIsRunning)
            {
                log.LogTrace("CustomerNotify is running already exiting this cycle");
                return;
            }
            //This will get triggered roughly every minute, but we don't want to deliver that frequently
            if (DateTime.UtcNow - lastRun < RUN_EVERY_INTERVAL)
            {
                log.LogTrace($"CustomerNotify ran less than {RUN_EVERY_INTERVAL} ago, exiting this cycle");
                return;
            }
            try
            {
                NotifyIsRunning = true;
                log.LogDebug("CustomerNotify set to RUNNING state and starting now");

                using (AyContext ct = Sockeye.Util.ServiceProviderProvider.DBContext)
                {
                    var customerevents = await ct.CustomerNotifyEvent.AsNoTracking().ToListAsync();
                    log.LogDebug($"Found {customerevents.Count} CustomerNotifyEvents to examine for potential delivery");

                    //iterate and deliver
                    foreach (var customernotifyevent in customerevents)
                    {
                        //no notifications for inactive users, just delete it as if it was delivered                      
                        var CustInfo = await ct.Customer.AsNoTracking().Where(x => x.Id == customernotifyevent.CustomerId).Select(x => new { x.Name, x.Active, x.Tags, x.EmailAddress }).FirstOrDefaultAsync();


                        if (!CustInfo.Active)
                        {
                            log.LogDebug($"Inactive Customer {CustInfo.Name}, removing notify rather than delivering it: {customernotifyevent}");
                            ct.CustomerNotifyEvent.Remove(customernotifyevent);
                            await ct.SaveChangesAsync();
                            continue;
                        }

                        if (string.IsNullOrWhiteSpace(CustInfo.EmailAddress))
                        {
                            log.LogDebug($"Customer {CustInfo.Name} has no email address, removing notify rather than delivering it: {customernotifyevent}");
                            ct.CustomerNotifyEvent.Remove(customernotifyevent);
                            await ct.SaveChangesAsync();
                            continue;
                        }

                        //Get subscription for delivery
                        var Subscription = await ct.CustomerNotifySubscription.AsNoTracking().FirstOrDefaultAsync(x => x.Id == customernotifyevent.CustomerNotifySubscriptionId);

                        //NOTE: There is no need to separate out future delivery and immediate delivery because
                        // All events have an event date, it's either immediate upon creation or it's future
                        // but not all events have an age value including ones with future event dates,   
                        // and the default agevalue and advancenotice are both zero regardless so the block below works for either future or immediate deliveries                   

                        var deliverAfter = customernotifyevent.EventDate + Subscription.AgeValue - Subscription.AdvanceNotice;
                        if (deliverAfter < DateTime.UtcNow)
                        {
                            //Do the delivery, it's kosher
                            await DeliverCustomerNotificationSMTP(customernotifyevent, Subscription, CustInfo.EmailAddress, ct);
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                log.LogError(ex, $"Error processing customer notification event");
                DbUtil.HandleIfDatabaseUnavailableTypeException(ex);
            }
            finally
            {
                log.LogDebug("CustomerNotify is done setting to not running state and tagging lastRun timestamp");
                lastRun = DateTime.UtcNow;
                NotifyIsRunning = false;

            }

        }


        //===
        private static async Task DeliverCustomerNotificationSMTP(CustomerNotifyEvent ne, CustomerNotifySubscription subscription, string deliveryAddress, AyContext ct)
        {

            var DeliveryLogItem = new CustomerNotifyDeliveryLog()
            {
                Processed = DateTime.UtcNow,
                ObjectId = ne.ObjectId,
                CustomerNotifySubscriptionId = ne.CustomerNotifySubscriptionId,
                Fail = false
            };

            try
            {
                log.LogDebug($"DeliverCustomerNotificationSMTP delivering notify event: {ne}");
                if (string.IsNullOrWhiteSpace(deliveryAddress))
                {
                    DeliveryLogItem.Fail = true;
                    DeliveryLogItem.Error = $"No email address provided for smtp delivery; event: {ne}";
                }
                else
                {

                    if (!ServerGlobalOpsSettingsCache.Notify.SmtpDeliveryActive)
                    {
                        await NotifyEventHelper.AddOpsProblemEvent($"Email notifications are set to OFF at server, unable to send Customer email notification for this event:{ne}");
                        log.LogInformation($"** WARNING: SMTP notification is currently set to Active=False; unable to deliver Customer email notification, [CustomerId={ne.CustomerId}, Customer Notify subscription={ne.CustomerNotifySubscriptionId}].  Change this setting or remove all Customer notifications if this is permanent **");
                        DeliveryLogItem.Fail = true;
                        DeliveryLogItem.Error = $"Email notifications are set to OFF at server, unable to send Customer email notification for this event: {ne}";
                    }
                    else
                    {

                        //BUILD SUBJECT AND BODY FROM TOKENS IF REQUIRED
                        var Subject = subscription.Subject;
                        var Body = subscription.Template;

                        // if (Subject.Contains("{{") || Body.Contains("{{"))
                        // {
                        //     //fetch the object with viz fields for easy templatization
                        //     switch (ne.SockType)
                        //     {
                               
                                
                               
                        //     }


                        // }


                        IMailer m = Sockeye.Util.ServiceProviderProvider.Mailer;
                        //generate report if applicable
                        bool isReportableEvent = false;
                        // switch (ne.EventType)
                        // {
                        //     case NotifyEventType.QuoteStatusChange:
                        //     case NotifyEventType.WorkorderCompleted:
                        //     case NotifyEventType.WorkorderStatusChange:
                        //         isReportableEvent = true;
                        //         break;
                        // }
                        if (isReportableEvent && subscription.LinkReportId != null)
                        {
                            long subTranslationId = (long)subscription.TranslationId;

                            ReportBiz biz = new ReportBiz(ct, 1, subTranslationId, AuthorizationRoles.BizAdmin);
                            //example with workorder report
                            //{"SockType":34,"selectedRowIds":[355],"ReportId":9,"ClientMeta":{"UserName":"Sockeye SuperUser","Authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOiIxNjQ2NzgyNTc4IiwiaXNzIjoiYXlhbm92YS5jb20iLCJpZCI6IjEifQ.ad7Acq54JCRGitDWKDJFFnqKkidbdaKaFmj-RA_RG5E","DownloadToken":"NdoU8ca3LG4L39Tj2oi3UReeeM7FLevTgbgopTPhGbA","TimeZoneName":"America/Los_Angeles","LanguageName":"en-US","Hour12":true,"CurrencyName":"USD","DefaultLocale":"en","PDFDate":"3/3/22","PDFTime":"3:38 PM"}}

                            var reportRequest = new DataListReportRequest();

                            reportRequest.SockType = ne.SockType;
                            reportRequest.ReportId = (long)subscription.LinkReportId;
                            reportRequest.SelectedRowIds = new long[] { ne.ObjectId };
                            var jwt = Api.Controllers.AuthController.GenRpt(subTranslationId);

                            //this could be adjusted by culture if we allow user to set a culture but that's getting a bit into the weeds, likely the server default is fine
                            var pdfDate = new DateTime().ToShortDateString();
                            var pdfTime = new DateTime().ToShortTimeString();
                            var h12 = subscription.Hour12 ? "true" : "false";
                            reportRequest.ClientMeta = JToken.Parse($"{{'UserName':'-','Authorization':'Bearer {jwt}','TimeZoneName':'{subscription.TimeZoneOverride}','LanguageName':'{subscription.LanguageOverride}','Hour12':{h12},'CurrencyName':'{subscription.CurrencyName}','DefaultLocale':'en','PDFDate':'{pdfDate}','PDFTime':'{pdfTime}'}}");
                            //get port number
                            var match = System.Text.RegularExpressions.Regex.Match(ServerBootConfig.SOCKEYE_USE_URLS, "[0-9]+");
                            var API_URL = $"http://127.0.0.1:{match.Value}/api/{SockeyeVersion.CurrentApiVersion}/";
                            var jobid = await biz.RequestRenderReport(reportRequest, DateTime.UtcNow.AddMinutes(ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT), API_URL, "CUSTOMER NOTIFICATION - NO USER");
                            if (jobid == null)
                            {
                                throw new ApplicationException($"Report render job id is null failed to start");
                            }
                            else
                            {
                                bool done = false;
                                DateTime bailAfter = DateTime.Now.AddMinutes(ServerBootConfig.SOCKEYE_REPORT_RENDERING_TIMEOUT);
                                while (!done && DateTime.Now < bailAfter)
                                {
                                    var status = await JobsBiz.GetJobStatusAsync((Guid)jobid);
                                    switch (status)
                                    {
                                        case JobStatus.Completed:
                                            {
                                                done = true;
                                                //get job logs and parse file name from it
                                                JobOperationsBiz jobopsbiz = new JobOperationsBiz(ct, 1, AuthorizationRoles.BizAdmin);
                                                List<JobOperationsLogInfoItem> log = await jobopsbiz.GetJobLogListAsync((Guid)jobid);
                                                var lastLog = log[log.Count - 1];
                                                var lastLogJ = JObject.Parse(lastLog.StatusText);
                                                var path = (string)lastLogJ["reportfilename"];
                                                var FilePath = FileUtil.GetFullPathForTemporaryFile(path);
                                                var FileName = FileUtil.StringToSafeFileName(await TranslationBiz.GetTranslationStaticAsync(ne.SockType.ToString(), subTranslationId, ct) + $"-{ne.Name}.pdf").ToLowerInvariant();
                                                await m.SendEmailAsync(deliveryAddress, Subject, Body, ServerGlobalOpsSettingsCache.Notify, FilePath, FileName);
                                                break;
                                            }
                                        case JobStatus.Failed:
                                        case JobStatus.Absent:
                                            throw new ApplicationException($"REPORT RENDER JOB {jobid} started but failed");

                                    }

                                }
                                if (!done)
                                    throw new TimeoutException("JOB FAILED DUE TO REPORT RENDER TIMEOUT");
                            }
                        }
                        else
                            await m.SendEmailAsync(deliveryAddress, Subject, Body, ServerGlobalOpsSettingsCache.Notify);

                    }

                }
            }
            catch (Exception ex)
            {
                await NotifyEventHelper.AddOpsProblemEvent("SMTP Customer Notification failed", ex);
                DeliveryLogItem.Fail = true;
                DeliveryLogItem.Error = $"SMTP Notification failed to deliver for this Customer notify event: {ne}, message: {ex.Message}";
                log.LogDebug(ex, $"DeliverSMTP Failure delivering Customer notify event: {ne}");
            }
            finally
            {
                //remove event no matter what
                ct.CustomerNotifyEvent.Remove(ne);

                //add delivery log item
                await ct.CustomerNotifyDeliveryLog.AddAsync(DeliveryLogItem);
                await ct.SaveChangesAsync();
            }
        }


       


        //===









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

    }//eoc


}//eons

