using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Sockeye.Util;
using Sockeye.Api.ControllerHelpers;
using Sockeye.Models;
using System.Collections.Generic;
using System;
using System.Text;
using System.IO;
using System.Net.Http;

using System.Linq;
using Microsoft.Extensions.Logging;

//JSON KEY
using Org.BouncyCastle.Security;
using Org.BouncyCastle.OpenSsl;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;

namespace Sockeye.Biz
{

    internal class GlobalBizSettingsBiz : BizObject
    {

        internal GlobalBizSettingsBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles)
        {
            ct = dbcontext;
            UserId = currentUserId;
            UserTranslationId = userTranslationId;
            CurrentUserRoles = UserRoles;
            BizType = SockType.Global;
        }

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




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

        //Get one
        internal async Task<GlobalBizSettings> GetAsync(bool logTheGetEvent = true)
        {
            //first try to fetch from db
            var ret = await ct.GlobalBizSettings.AsNoTracking().SingleOrDefaultAsync(m => m.Id == 1);
            if (logTheGetEvent && ret != null)
            {
                //Log
                await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 1, BizType, SockEvent.Retrieved), ct);
            }

            //not in db then get the default
            if (ret == null)
            {
                throw new System.Exception("GlobalBizSettingsBiz::GetAsync -> Global settings object not found in database!!");
            }
            return ret;
        }



        ////////////////////////////////////////////////////////////////////////////////////////////////
        //UPDATE
        //
        internal async Task<GlobalBizSettings> PutAsync(GlobalBizSettings putObject)
        {
            var dbObject = await GetAsync(false);
            if (dbObject == null)
                throw new System.Exception("GlobalBizSettingsBiz::PutAsync -> Global settings object not found in database. Contact support immediately!");

            if (dbObject.Concurrency != putObject.Concurrency)
            {
                AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
                return null;
            }
            Validate(putObject, dbObject);
            if (HasErrors) return null;

            List<string> originalTags = dbObject.AllTags();
            List<string> newTags = putObject.AllTags();

            ct.Replace(dbObject, putObject);


            try
            {
                await ct.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
                return null;
            }
            //Update cache
            ServerGlobalBizSettings.Initialize(putObject, null);

            //Log modification and save context
            await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, 1, BizType, SockEvent.Modified), ct);
            await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newTags, originalTags);

            return putObject;
        }



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

        //Can save or update?
        private void Validate(GlobalBizSettings proposedObj, GlobalBizSettings currentObj)
        {
            //currently nothing to validate
        }





        //IMPORT FROM ROCKFISH

        public static async Task ImportRockfish(ILogger log)
        {
            log.LogInformation("Start import from rockfish, authenticating");
            ApiServerState apiServerState = (ApiServerState)ServiceProviderProvider.Provider.GetService(typeof(ApiServerState));

            //get the current server state so can set back to it later
            ApiServerState.ServerState wasServerState = apiServerState.GetState();
            string wasReason = apiServerState.Reason;
            apiServerState.SetOpsOnly("Migrating from Rockfish");
            ServerBootConfig.MIGRATING = true;

            //Authenticate to rockfish
            //string sUrl = $"{LICENSE_SERVER_URL_ROCKFISH}rvr";
            string URL_ROCKFISH = "https://rockfish.ayanova.com/";

            try
            {
                //var content = new StringContent("login=john&password=b43698c255365ee739c05ba0d42855e96c2365c76bb2f9b9eb149cec7b52174c");
                var content = new StringContent(JsonConvert.SerializeObject(new
                {
                    login = "john",
                    password = "b43698c255365ee739c05ba0d42855e96c2365c76bb2f9b9eb149cec7b52174c"
                }), Encoding.UTF8, "application/json");

                var client = ServiceProviderProvider.HttpClientFactory.CreateClient();

                //AUTHENTICATE
                var res = await client.PostAsync($"{URL_ROCKFISH}authenticate?login=john&password=b43698c255365ee739c05ba0d42855e96c2365c76bb2f9b9eb149cec7b52174c", content);
                var responseText = await res.Content.ReadAsStringAsync();

                if (!res.IsSuccessStatusCode)
                    throw new Exception($"E1020 - Error authenticating to rockfish: {res.ReasonPhrase}");

                var responseJson = JObject.Parse(responseText);

                var authToken = responseJson["token"].Value<string>();
                var dlKey = responseJson["dlkey"].Value<string>();
                var userId = responseJson["id"].Value<long>();

                var contentType = new MediaTypeWithQualityHeaderValue("application/json");
                client.DefaultRequestHeaders.Accept.Add(contentType);
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);

                #region CUSTOMERS
                log.LogInformation("RFImport customers");

                res = await client.GetAsync($"{URL_ROCKFISH}api/customer/list");
                responseText = await res.Content.ReadAsStringAsync();
                var jaCustomerList = JArray.Parse(responseText);

                foreach (JObject jCustomerListItem in jaCustomerList)
                {
                    res = await client.GetAsync($"{URL_ROCKFISH}api/customer/{jCustomerListItem["id"].Value<long>()}");

                    var jCustomer = JObject.Parse(await res.Content.ReadAsStringAsync());

                    //SITES
                    res = await client.GetAsync($"{URL_ROCKFISH}api/customer/{jCustomerListItem["id"].Value<long>()}/sites");
                    var jaSiteList = JArray.Parse(await res.Content.ReadAsStringAsync());

                    bool multiSite = jaSiteList.Count() > 1;

                    foreach (JObject jSite in jaSiteList)
                    {
                        long CurrentCustomerId = 0;
                        using (AyContext ct = ServiceProviderProvider.DBContext)
                        {
                            var CustomerName = jCustomer["name"].Value<string>();
                            if (multiSite)
                            {
                                CustomerName += " - " + jSite["name"].Value<string>();
                                log.LogInformation($"RFImport MULTISITE CUSTOMER: {CustomerName}");
                            }


                            //Create customer if we don't have one already
                            if (await ct.Customer.AnyAsync(z => z.Name == CustomerName))
                                continue;//already have this one so no need to process it again
                            {
                                //CREATE CUSTOMER
                                Customer c = new Customer();
                                c.Active = jCustomerListItem["active"].Value<bool>();
                                c.Name = CustomerName;
                                c.Country = jSite["country"].Value<string>();
                                c.Region = jSite["stateProvince"].Value<string>();
                                c.DoNotContact = jCustomer["doNotContact"].Value<bool>();
                                c.Notes = jCustomer["notes"].Value<string>();
                                c.DbId = jSite["dbId"].Value<string>();
                                if (c.DbId == "v7_no_dbid")
                                {
                                    c.DbId = null;
                                    c.Tags.Add("v7");
                                }
                                else
                                    c.Tags.Add("raven");

                                if (jSite["hosted"].Value<bool>() == true)
                                    c.Tags.Add("hosted");

                                //In rockfish there were support emails that were people allowed to be contacting us on behalf of the customer
                                var supportEmail = jCustomer["supportEmail"].Value<string>();
                                if (!string.IsNullOrWhiteSpace(supportEmail))
                                    c.Notes += "\nSupport Emails: " + supportEmail;
                                //in Rockfish the admin email is the main license related contact and technically the only person responsible to contact us
                                //usually the same as the purchase email
                                c.EmailAddress = jCustomer["adminEmail"].Value<string>();
                                CustomerBiz biz = CustomerBiz.GetBiz(ct);
                                var NewObject = await biz.CreateAsync(c);
                                CurrentCustomerId = NewObject.Id;
                            }//customer creation
                        }//context


                        //SITE PURCHASES                       
                        res = await client.GetAsync($"{URL_ROCKFISH}api/site/{jSite["id"].Value<long>()}/purchases");
                        var jaPurchaseList = JArray.Parse(await res.Content.ReadAsStringAsync());

                        foreach (JObject jPurchase in jaPurchaseList)
                        {
                            using (AyContext ct = ServiceProviderProvider.DBContext)
                            {
                                //create product if not exist then import
                                //Get product id if exists
                                var ProductName = jPurchase["name"].Value<string>();
                                ProductName = ProductName.Replace("- Renewal", "").Replace(" Renewal", "").Replace(" RENEWAL", "").Replace("CANCELLED ", "").Replace("CANCELED ", "");
                                var p = await ct.Product.AsNoTracking().FirstOrDefaultAsync(z => z.VendorCode == jPurchase["productCode"].Value<string>());


                                if (p == null)
                                {

                                    //Create it here
                                    p = new Product();
                                    p.Name = ProductName;
                                    p.Active = true;//not entirely true but we can always deactivate there aren't many products
                                    p.VendorId = 1;
                                    p.OurCode = p.VendorCode = jPurchase["productCode"].Value<string>();
                                    p.PGroup = ProductBiz.ProductGroupFromProductCode(p.OurCode);

                                    if (p.VendorCode == "301028468")//subscription yearly
                                    {
                                        p.LicenseInterval = new TimeSpan(365, 0, 0, 0); //actual length, in license is where I pad to cover them
                                    }

                                    if (p.VendorCode == "301028467")//subscription monthly
                                    {
                                        p.LicenseInterval = new TimeSpan(31, 0, 0, 0);//there is no concept beyond days for timespan so a month is 31 days max
                                        p.MaintInterval = new TimeSpan(31, 0, 0, 0);//the only product less than one year of maintenance save the ones with no maintenance which can be edited manually later as there are so few of them
                                    }
                                    else
                                        p.MaintInterval = new TimeSpan(365, 0, 0, 0);

                                    ProductBiz biz = ProductBiz.GetBiz(ct);
                                    p = await biz.CreateAsync(p);
                                }

                                //Now we can add the sale that we have the product
                                if (!await ct.Purchase.AnyAsync(z => z.SalesOrderNumber == jPurchase["salesOrderNumber"].Value<string>()))
                                {
                                    var s = new Purchase();
                                    s.VendorId = 1;
                                    s.ProductId = p.Id;
                                    s.PGroup = p.PGroup;
                                    s.CustomerId = CurrentCustomerId;
                                    s.CancelDate = DateUtil.EpochToDateNullIsNull(jPurchase["cancelDate"].Value<long?>());
                                    s.CouponCode = jPurchase["couponCode"].Value<string>();
                                    s.ExpireDate = DateUtil.EpochToDateNullIsNull(jPurchase["expireDate"].Value<long?>());
                                    s.Name = p.Name;
                                    s.Processed = true;
                                    s.PurchaseDate = DateUtil.EpochToDateNullIsMin(jPurchase["purchaseDate"].Value<long?>());
                                    s.Quantity = jPurchase["quantity"].Value<int>();
                                    s.RenewNoticeSent = jPurchase["renewNoticeSent"].Value<bool>();
                                    s.SalesOrderNumber = jPurchase["salesOrderNumber"].Value<string>();
                                    s.Notes = jPurchase["notes"].Value<string>();
                                    PurchaseBiz biz = PurchaseBiz.GetBiz(ct);
                                    await biz.CreateAsync(s);
                                }
                            }//context
                        }



                    }//site loop

                }//end of all customers iteration

                #endregion customers

                #region VENDOR NOTIFICATIONS
                log.LogInformation("RFImport Vendor notifications");
                {

                    res = await client.GetAsync($"{URL_ROCKFISH}api/vendor-notifications/list");
                    responseText = await res.Content.ReadAsStringAsync();
                    var jaVendorNotificationList = JArray.Parse(responseText);

                    foreach (JObject jVendorNotificationItem in jaVendorNotificationList.Reverse())
                    {
                        /*[]
                   "{\n \"creation_date\": \"2022-12-28T20:20:24Z\",\n \
                   "id\": 356247074,\n \
                   "order_notification\": {\n \
                   "purchase\": {\n \"customer_data\": {\n \"billing_contact\": {\n \"address\": {\n \"city\": \"Alpharetta\",\n \"country\": \"USA\",\n \"country_id\": \"US\",\n 
                   \"postal_code\": \"30009\",\n \"state\": \"Georgia\",\n \"state_id\": \"GA\",\n \"street1\": \"11675 Rainwater Dr., Suite 350\"\n },
                   \n …tart\": \"\",\n \"renewal_type\": \"auto\",\n \"retention_discount_count\": \"\",\n \"retention_discount_percent\": \"\",\n \
                   "start_date\": \"2022-12-28T00:00:00\",\n \"status\": \"ToProcess\",\n \"status_id\": \"TOP\"\n },\n \
                   "vat_pct\": 0.0,\n \"your_product_id\": \"perpetual\"\n }\n ],\n 
                   \"purchase_origin\": \"online\",\n \"sequential_invoice_no\": \"e5-US-2022-00001637070\"\n }\n }\n}"

                   {
       "id": 25,
       "dtCreated": 1670375661,
       "vendor": "shareit",
       "data": "{\n  \"creation_date\": \"2022-12-07T01:14:19Z\",\n  \"id\": 354935062,\n  \"order_notification\": {\n    \"purchase\": {\n      \"customer_data\": {\n        \"billing_contact\": {\n          \"address\": {\n            \"city\": \"Abbotsford\",\n            \"country\": \"Canada\",\n            \"country_id\": \"CA\",\n            \"postal_code\": \"V2S 8M1\",\n            \"state\": \"British Columbia\",\n            \"state_id\": \"BC\",\n            \"street1\": \"35355 McKee Rd\"\n          },\n          \"company\": \"Adtech Systems Inc.\",\n          \"email\": \"erling@myadtech.com\",\n          \"first_name\": \"Erling\",\n          \"last_name\": \"Lassesen\"\n        },\n        \"customer_payment_data\": {\n          \"currency\": \"USD\",\n          \"payment_method\": \"PayPal\"\n        },\n        \"delivery_contact\": {\n          \"address\": {\n            \"city\": \"Abbotsford\",\n            \"country\": \"Canada\",\n            \"country_id\": \"CA\",\n            \"postal_code\": \"V2S 8M1\",\n            \"state\": \"British Columbia\",\n            \"state_id\": \"BC\",\n            \"street1\": \"35355 McKee Rd\"\n          },\n          \"company\": \"Adtech Systems Inc.\",\n          \"email\": \"erling@myadtech.com\",\n          \"first_name\": \"Erling\",\n          \"last_name\": \"Lassesen\"\n        },\n        \"language\": \"English\",\n        \"language_iso\": \"en\",\n        \"reg_name\": \"Adtech Systems Inc.\",\n        \"shopper_id\": \"50862261\",\n        \"subscribe_newsletter\": true,\n        \"user_id\": \"erling@adtech-systems.com-4\"\n      },\n      \"is_test\": false,\n      \"payment_complete_date\": \"2022-12-07T01:14:19Z\",\n      \"payment_status\": \"complete\",\n      \"payment_status_id\": \"PCA\",\n      \"purchase_date\": \"2022-12-07T01:14:16Z\",\n      \"purchase_id\": 833707313,\n      \"purchase_item\": [\n        {\n          \"accounting\": {\n            \"currency\": \"USD\",\n            \"product_net\": 55.65,\n            \"discount\": 0.0,\n            \"product_vat\": 6.68,\n            \"shipping\": 0.0,\n            \"shipping_vat\": 0.0,\n            \"eu_vat\": -6.68,\n            \"margin_net\": -5.44,\n            \"your_revenue\": 50.21\n          },\n          \"additional_information\": [\n            {\n              \"additional_id\": \"ADDITIONAL1\",\n              \"additional_value\": \"YES\"\n            },\n            {\n              \"additional_id\": \"ADDITIONAL2\",\n              \"additional_value\": \"YES\"\n            },\n            {\n              \"additional_id\": \"ADDITIONAL3\",\n              \"additional_value\": \"YES\"\n            }\n          ],\n          \"currency\": \"USD\",\n          \"delivery_type\": \"Electronically\",\n          \"discount\": 0.0,\n          \"extended_download_price\": 0.0,\n          \"manual_order_price\": 0.0,\n          \"notification_no\": 7926,\n          \"product_id\": 300740315,\n          \"product_name\": \"Single AyaNova schedulable resource 1 year subscription license\",\n          \"product_single_price\": 55.65,\n          \"purchase_item_key\": [],\n          \"quantity\": 1,\n          \"running_no\": 1,\n          \"shipping_price\": 0.0,\n          \"shipping_vat_pct\": 0.0,\n          \"subscription\": {\n            \"expiration_date\": \"2023-12-07T17:28:16Z\",\n            \"id\": \"550286373-1\",\n            \"interval\": \"Yearly without end\",\n            \"original_notification_no\": \"6872\",\n            \"original_purchase_id\": \"550286373\",\n            \"original_running_no\": \"1\",\n            \"renewal_discount_count\": \"\",\n            \"renewal_discount_start\": \"\",\n            \"renewal_type\": \"auto\",\n            \"retention_discount_count\": \"\",\n            \"retention_discount_percent\": \"\",\n            \"start_date\": \"2017-12-07T00:00:00\",\n            \"status\": \"ToProcess\",\n            \"status_id\": \"TOP\"\n          },\n          \"vat_pct\": 12.0\n        }\n      ],\n      \"purchase_origin\": \"Subscription\",\n      \"sequential_invoice_no\": \"e5-DE-2022-00002647549\"\n    }\n  }\n}",
       "dtProcessed": null,
       "processed": false
   },
               */
                        using (AyContext ct = ServiceProviderProvider.DBContext)
                        {
                            var vn = new VendorNotification();
                            vn.Created = DateUtil.EpochToDateNullIsMin(jVendorNotificationItem["dtCreated"].Value<long>());
                            vn.VendorId = 1;
                            vn.VendorData = jVendorNotificationItem["data"].Value<string>();
                            vn.Processed = vn.Created;//indicate it's been processed
                            ct.VendorNotification.Add(vn);
                            await ct.SaveChangesAsync();


#if (DEBUG)
                            //Test dev stuff 
#warning DEV TEST ORDER PROCESSING REMOVE THIS WHEN DONE
                            if (!string.IsNullOrWhiteSpace(vn.VendorData))
                                await SockBotProcessVendorNotifications.ParseVendorNotificationData(vn, ct, log);
#endif
                        }

                    }// all vendor notifications loop 
                }
                #endregion vendor notifications

                #region CASES

                log.LogInformation("RFImport Cases");
                //case projects to be tags
                List<NameIdItem> CaseProjectList = new List<NameIdItem>();
                {
                    res = await client.GetAsync($"{URL_ROCKFISH}api/rfcaseproject");
                    responseText = await res.Content.ReadAsStringAsync();

                    var jaRFCaseProjectList = JArray.Parse(responseText);
                    foreach (JObject jRFCaseProject in jaRFCaseProjectList)
                    {
                        CaseProjectList.Add(new NameIdItem() { Name = jRFCaseProject["name"].Value<string>(), Id = jRFCaseProject["id"].Value<long>() });

                    }

                }
                {
                    using (AyContext ct = ServiceProviderProvider.DBContext)
                    {
                        res = await client.GetAsync($"{URL_ROCKFISH}api/rfcase/list");
                        responseText = await res.Content.ReadAsStringAsync();
                        var jaRFCaseList = JArray.Parse(responseText);
                        //some cases are missing the start date so substitute a close other case date (not critical but sb at least in the ballpark for list viewing purposes)
                        DateTime dtTempCreated = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                        foreach (JObject jRFCase in jaRFCaseList.Reverse())
                        {


                            var g = new GZCase();
                            g.CaseId = jRFCase["id"].Value<long>();
                            g.Closed = DateUtil.EpochToDateNullIsNull(jRFCase["dtClosed"].Value<long?>());
                            //NOTE: closed in rockfish was the date at midnight in GMT
                            //so to be in the same day as here need to add a few hours, let's say 8 am for each so add 8 hours
                            if (g.Closed != null)
                            {
                                g.Closed = ((DateTime)g.Closed).AddHours(16);
                            }

                            //fuckery to try to insert a at least semi close date when created date is missing
                            DateTime? dtTemp = DateUtil.EpochToDateNullIsNull(jRFCase["dtCreated"].Value<long?>());
                            if (dtTemp == null)
                            {
                                dtTemp = dtTempCreated;
                            }
                            else
                            {
                                dtTempCreated = (DateTime)dtTemp;
                            }
                            g.Created = (DateTime)dtTemp;
                            g.Name = jRFCase["title"].Value<string>();
                            g.Notes = jRFCase["notes"].Value<string>();

                            var ver = jRFCase["releaseVersion"].Value<string>();
                            if (!string.IsNullOrWhiteSpace(ver))
                                g.Notes += $"\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\nReleased on version:{ver}";

                            var releaseNotes = jRFCase["releaseNotes"].Value<string>();
                            if (!string.IsNullOrWhiteSpace(releaseNotes))
                                g.Notes += $"\nRelease notes:{releaseNotes}";

                            //Project name to tags                 
                            g.Tags.Add(CaseProjectList.FirstOrDefault(z => z.Id == jRFCase["rfCaseProjectId"].Value<long>()).Name.Replace("z_", "legacy-"));

                            //priority to tags
                            g.Tags.Add($"{jRFCase["priority"].Value<long>()}-priority");

                            //check for attachments and just add as a note, don't bother with actual transfer of attachment, there aren't a lot and most are way in the past and not required for anything
                            //if needed in future can manually xfer it over from the rockfish.sqlite db directly using DB BRowser for sqlite which allows opening the blob from the rfcaseblob table and saving it
                            res = await client.GetAsync($"{URL_ROCKFISH}api/rfcase/{g.CaseId}/attachments");
                            responseText = await res.Content.ReadAsStringAsync();
                            var jAttachments = JObject.Parse(responseText);
                            if (jAttachments["attach"].Count() > 0)
                            {
                                g.Notes += "\n********\nRockfish attachments\n";
                                foreach (JObject jAttachmentRecord in jAttachments["attach"])
                                {
                                    g.Notes += $"File: \"{jAttachmentRecord["name"].Value<string>()}\", rfcaseblob table id: {jAttachmentRecord["id"].Value<long>()}\n";

                                }
                                g.Notes += "\n********\n";
                            }

                            GZCaseBiz biz = GZCaseBiz.GetBiz(ct);
                            await biz.CreateAsync(g);

                            //attachments example 86400000
                            // /api/rfcase/4360/attachments
                            //{"dlkey":"ZFkAUpo1L0Gi3Q9aO5szkA","attach":[{"id":259,"name":"desired weight calcs.txt"}]}
                            //{"dlkey":"iR6ncD70CkzkozyT0otA","attach":[]}




                        }// all cases loop 

                        //Start next case with a new sequence caseid of 4444
                        using (var command = ct.Database.GetDbConnection().CreateCommand())
                        {
                            command.CommandText = $"ALTER SEQUENCE agzcase_caseid_seq RESTART WITH 4444;";
                            await ct.Database.OpenConnectionAsync();
                            await command.ExecuteNonQueryAsync();
                            await ct.Database.CloseConnectionAsync();
                        }
                    }//end of db context 
                }

                #endregion cases

                #region LICENSES
                log.LogInformation("RFImport Licenses");
                {

                    res = await client.GetAsync($"{URL_ROCKFISH}api/license/list");
                    responseText = await res.Content.ReadAsStringAsync();
                    var jaLicenseList = JArray.Parse(responseText);
                    //some cases are missing the start date so substitute a close other case date (not critical but sb at least in the ballpark for list viewing purposes)
                    DateTime dtTempCreated = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                    foreach (JObject jLicenseListItem in jaLicenseList.Reverse())
                    {
                        using (AyContext ct = ServiceProviderProvider.DBContext)
                        {
                            res = await client.GetAsync($"{URL_ROCKFISH}api/license/{jLicenseListItem["id"].Value<long>()}");
                            responseText = await res.Content.ReadAsStringAsync();
                            var jLicense = JObject.Parse(responseText);
                            //  log.LogInformation($"Importing license:{responseText}");
                            /*
                            {"regTo":"PDI Technologies","customerName":"PDI Technologies","dtcreated":1672261044,"email":"jberkel@pdisoftware.com","code":"na","fetched":true,"dtfetched":1672262347,"fetchFrom":null,
                            "key":"[KEY\n{\n  \"Key\": {\n    \"LicenseFormat\": \"8\",\n    \"Id\": \"1672261044\",\n    \"RegisteredTo\": \"PDI Technologies\",\n    \"DBID\": \"R6U37uNUN2hSQideG6Gg+MqoQY8vuUeyHFI6Kv7VDsE=\",\n    \"Perpetual\": true,\n    \"LicenseExpiration\": \"5555-01-01T00:00:00\",\n    \"MaintenanceExpiration\": \"2023-12-28T00:00:00\",\n    \"Features\": [\n      {\n        \"Name\": \"ActiveInternalUsers\",\n        \"Count\": 5\n      }\n    ]\n  }\n}\nKEY]\n[SIGNATURE\nkzVs8GH0MSIfsR7ZYQ5x+5wdVDJqpfOYvTfBCx32Vs+zqP7h89uUKI17jTx5rMvkOYX40GyJt0pTWOzltljzf+MaoTzoSvTsSPkWtdVWv8ZGOXUUdaZhzMoTJGxTg0JHka/8S5tLkTiuct3x+voiLAKXuFCp9TSZo4+UnejB6U2Bp6MfdZxLtKVZ/1RLu/h4SnP8ZbczuwbQReB1z4k4IRwjh5GHnUUm6YqZg/04m1X9FEeKQZQfGZk/qZ788jONbLQb4CLnq0/ZmIteeocDDBz59TYaC9BHwHp73y8jyPfEugVfyH2AE0J8ZILiFiozYQ7meP5X4ZOmd1nhTA8MkA==\nSIGNATURE]\n"
                            }
                            */
                            var l = new License();
                            l.Active = true;
                            l.RegTo = jLicense["regTo"].Value<string>();

                            //try to match customer; rockfish didn't track customer id in the license so this is a bit wishy washy
                            Customer cust = null;
                            cust = await ct.Customer.AsNoTracking().FirstOrDefaultAsync(z => z.Name == jLicense["customerName"].Value<string>());
                            if (cust == null)//email?                       
                                cust = await ct.Customer.AsNoTracking().FirstOrDefaultAsync(z => z.EmailAddress.Contains(jLicense["email"].Value<string>()));
                            if (cust != null)
                                l.CustomerId = cust.Id;



                            var KeyText = jLicense["key"].Value<string>();

                            l.Key = KeyText;
                            LicenseBiz.ParseKeySetDTOFields(l);
                            if (KeyText.Contains("AyaNovaLicenseKey"))
                            {
                                //v7 key
                                /*   
                                    [KEY
                                    {
                                    "AyaNovaLicenseKey": {
                                    "SchemaVersion": "7",
                                    "Id": "1517418112",
                                    "Created": "2018-01-31T09:01:52.1878494-08:00",
                                    "Sub": "true",
                                    "RegisteredTo": "Direct Telecom Services",
                                    "EmailAddress": "chrisw@dts.solutions",
                                    "FetchCode": "AgYuDnjDyQ",
                                    "Source": "5246494432",
                                    "InstallableUntil": "2019-01-31T09:01:52.089767-08:00",
                                    "TotalScheduleableUsers": "15",
                                    "Expires": "2019-01-31T00:00:00",
                                    "RequestedTrial": "False",
                                    "Plugins": {
                                    "Plugin": [
                                    {
                                    "Item": "MBI - Minimal browser interface",
                                    "SubscriptionExpires": "2018-06-13T00:00:00"
                                    },
                                    {
                                    "Item": "WBI - Web browser interface",
                                    "SubscriptionExpires": "2018-06-13T00:00:00"
                                    },
                                    {
                                    "Item": "OutlookSchedule",
                                    "SubscriptionExpires": "2018-06-13T00:00:00"
                                    },
                                    {
                                    "Item": "AyaNovaOLI",
                                    "SubscriptionExpires": "2018-06-13T00:00:00"
                                    },
                                    {
                                    "Item": "RI - Responsive Interface",
                                    "SubscriptionExpires": "2018-06-13T00:00:00"
                                    }
                                    ]
                                    }
                                    }
                                    }
                                    KEY]
                                    [SIGNATURE
                                    uBjnooIDd6MOiqT/z4tDQfeafkQiWDBtxDHXOxhZ7av1oWS72yPoe8BrAnDZiYbxE4+cHR3C0sPCgEVva5miV1foyi7P6YKkxkKQMxTUR5IssgWVHM59KnO1lR2ndCHWaqH3gHgSsb/sdvYfuHg8luTl1RgjNDZRdQqbPl4NLMcGGW86LoXjpLjsRRxImckBEJFnntd+aXCRmQjXEZWmfxDVW84qa6h+ZCOwL3KYJHuPQDcCmhcpp3MIR3OHoeYhmNG7TWuELsJ4hrsROcqSbEC/CdZD8hoZwtrysu/ZvNZOKchwFsiBaN47+DxK0K/fL/X8CDcG+w3iqgH/x5ipIw==
                                    SIGNATURE]

                                    */
                                string keyNoWS = System.Text.RegularExpressions.Regex.Replace(StringUtil.Extract(KeyText, "[KEY", "KEY]").Trim(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1");
                                var jKey = JObject.Parse(keyNoWS);

                                //In v7 the license expires is an optional property set called "LockDate"
                                l.LicenseExpire = null;
                                if (jKey["AyaNovaLicenseKey"]["LockDate"] != null)
                                    l.LicenseExpire = jKey["AyaNovaLicenseKey"]["LockDate"].Value<DateTime>().ToUniversalTime();

                                l.MaintenanceExpire = jKey["AyaNovaLicenseKey"]["Expires"].Value<DateTime>().ToUniversalTime();
                                //l.Users=jKey["AyaNovaLicenseKey"]["TotalScheduleableUsers"].Value<int>();
                                l.PGroup = ProductGroup.AyaNova7;
                                l.FetchCode = jLicense["code"].Value<string>();

                            }
                            else if (KeyText.Contains("AyaNovaLiteLicenseKey"))
                            {
                                /*
                                "[KEY\n{\n  \"AyaNovaLiteLicenseKey\": {\n    \"SchemaVersion\": \"7\",\n    \"Id\": \"1648506791\",\n    \"Created\": \"2022-03-28T15:33:11.6010225-07:00\",\n    
                                \"Sub\": \"true\",\n    \"RegisteredTo\": \"Duncan Electric\",\n    \"EmailAddress\": \"sandrajod@att.net\",\n    
                                \"FetchCode\": \"hGAmScqYcU\",\n    \"Source\": \"5246494431\",\n    \"InstallableUntil\": \"2023-03-28T15:33:11.6009851-07:00\",\n    
                                \"TotalScheduleableUsers\": \"1\",\n    \"Expires\": \"2023-03-29T00:00:00\",\n    \"RequestedTrial\": \"False\",\n    \"Plugins\": {\n      
                                \"Plugin\": []\n    }\n  }\n}\nKEY]\n
                                [SIGNATURE\nKuOF/SpBL1d8AFebvm2lipmKeGdbR6WzbhN68fun+ffVGRjXNX1jWI3rbf9P/shs2/M8gHqW/B7T0vVovGqosmNsGtvaYo30TKlZj9Eicr2+zDf7ojwZiBCeEnFzXr9+7aZrsZSvN20Pqof0xf/J4BVp1T66ecuZywMzH0NGsXXZtXhWYhGvLAZAR1X5/j5gqysSdysmV9j8Euz91zs/BRyfdU0uwwrdQzrJzI4V1MFl+/mIkhMUNcJ5bzjisWS2xeyNYCYnGpMF5oaGPaIcEtmTAdf5fPNNvw3sNhPaZgwlzN8FjfK6T0VgS19PcHCMOA1bTAiLLFNk6wkcjGp2Cw==\nSIGNATURE]\n"
                                */
                                string keyNoWS = System.Text.RegularExpressions.Regex.Replace(StringUtil.Extract(KeyText, "[KEY", "KEY]").Trim(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1");
                                var jKey = JObject.Parse(keyNoWS);
                                //In v7 the license expires is an optional property set called "LockDate"
                                l.LicenseExpire = null;
                                if (jKey["AyaNovaLiteLicenseKey"]["LockDate"] != null)
                                    l.LicenseExpire = jKey["AyaNovaLiteLicenseKey"]["LockDate"].Value<DateTime>().ToUniversalTime();

                                l.MaintenanceExpire = jKey["AyaNovaLiteLicenseKey"]["Expires"].Value<DateTime>().ToUniversalTime();

                                l.PGroup = ProductGroup.AyaNova7;
                                l.FetchCode = jLicense["code"].Value<string>();

                                l.Tags.Add("lite");
                            }
                            else
                            {
                                //RAVEN KEY
                                /*
                                 {{
      "Key": {
        "LicenseFormat": "8",
        "Id": "1672261044",
        "RegisteredTo": "PDI Technologies",
        "DBID": "R6U37uNUN2hSQideG6Gg+MqoQY8vuUeyHFI6Kv7VDsE=",
        "Perpetual": true,
        "LicenseExpiration": "5555-01-01T00:00:00",
        "MaintenanceExpiration": "2023-12-28T00:00:00",
        "Features": [
          {
            "Name": "ActiveInternalUsers",
            "Count": 5
          }
        ]
      }
    }}
                                */
                                string keyNoWS = System.Text.RegularExpressions.Regex.Replace(StringUtil.Extract(KeyText, "[KEY", "KEY]").Trim(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1");
                                var jKey = JObject.Parse(keyNoWS);
                                l.DbId = jKey["Key"]["DBID"].Value<string>();
                                l.LicenseExpire = jKey["Key"]["LicenseExpiration"].Value<DateTime>().ToUniversalTime();
                                //if (jKey["Key"]["Perpetual"].Value<bool>())
                                if ((bool?)jKey["Key"]["Perpetual"] ?? true)
                                    l.PGroup = ProductGroup.RavenPerpetual;
                                else
                                    l.PGroup = ProductGroup.RavenSubscription;
                                l.MaintenanceExpire = jKey["Key"]["MaintenanceExpiration"].Value<DateTime>().ToUniversalTime();
                            }

                            l.Created = (DateTime)DateUtil.EpochToDateNullIsNull(jLicense["dtcreated"].Value<long>());
                            l.FetchedOn = DateUtil.EpochToDateNullIsNull(jLicense["dtfetched"].Value<long?>());
                            l.FetchEmail = jLicense["email"].Value<string>();
                            // l.Key = jLicense["key"].Value<string>();

                            l.Active = true;//active here means it's been fully prepared and is viable for use, all prior licenses fit this description so all are active
                            l.NotificationSent = true;
                            LicenseBiz biz = LicenseBiz.GetBiz(ct);
                            await biz.CreateAsync(l, true);
                        }//context

                    }// all licenses loop 


                }
                #endregion licenses

                #region TRIAL LICENSE REQUESTS
                log.LogInformation("RFImport Raven trial requests");
                {

                    res = await client.GetAsync($"{URL_ROCKFISH}api/trial/list");
                    responseText = await res.Content.ReadAsStringAsync();
                    var jaTrialRequestList = JArray.Parse(responseText);

                    foreach (JObject jTrialRequestItem in jaTrialRequestList.Reverse())
                    {
                        /*                    
                        /api/trial/list
                        [
                        {
                            "id": 296,
                            "dbId": "R6U37uNUN2hSQideG6Gg+MqoQY8vuUeyHFI6Kv7VDsE=",
                            "perpetual": true,
                            "companyName": "PDI Technologies",
                            "contactName": "Jason Berkel",
                            "notes": null,
                            "email": "jberkel@pdisoftware.com",
                            "emailConfirmCode": "296RuWFbzuEps",
                            "emailValidated": true,
                            "dtRequested": 1672257483,
                            "dtProcessed": 1672257587,
                            "status": 1,
                            "rejectReason": null,
                            "key": "[KEY\n{\n  \"Key\": {\n    \"LicenseFormat\": \"8\",\n    \"Id\": \"1672257587\",\n    \"RegisteredTo\": \"PDI Technologies\",\n    \"DBID\": \"R6U37uNUN2hSQideG6Gg+MqoQY8vuUeyHFI6Kv7VDsE=\",\n    \"Perpetual\": true,\n    \"LicenseExpiration\": \"2023-01-04T19:59:47.6170429Z\",\n    \"MaintenanceExpiration\": \"2023-01-04T19:59:47.6170429Z\",\n    \"Features\": [\n      {\n        \"Name\": \"TrialMode\"\n      },\n      {\n        \"Name\": \"ActiveInternalUsers\",\n        \"Count\": 5000\n      }\n    ]\n  }\n}\nKEY]\n[SIGNATURE\ng/9yvrUi18PsllDyc4SJSlngmW3n411OtlFaGl3nK5SznCMf7CFDxH6eRqrvpI4452RblqAquHmsUUwFHIzKjaIj1VF3z1X9RHz7BcPrNX7lvWM8ErJb0jJjylKxvecaalMPXVxR1QLULDBFdVT5Gv0QUIzkFKVSwAF2mLUg1JNut939YoG2u95ABeQNe4SH7WQ3+gcsI/qSLcbzz/PmRMrWqPOzXi7jVbZTigfZLdbSC+DlUbNsCvkSgraEwBBnCpE8ZfCG1oFjdbP64qF1ArY7DbPUuxHNfPSZcDlhQz8D7JIqLTQwEijqvKfLy/KNMSlcfQtv5VBJtlwCP9ggdQ==\nSIGNATURE]\n",
                            "dtFetched": 1672257635
                        },
                        */

                        using (AyContext ct = ServiceProviderProvider.DBContext)
                        {
                            //Save Key first then can set keyid on tlr
                            long? TrialKeyId = null;
                            {
                                string sKey = jTrialRequestItem["key"].Value<string>();
                                if (!string.IsNullOrWhiteSpace(sKey))
                                {
                                    License l = new License();
                                    l.TrialMode = true;

                                    l.Key = sKey;
                                    //get number of users count and features etc
                                    LicenseBiz.ParseKeySetDTOFields(l);

                                    //Parse key as we need the expiry dates and they are only stored in the key not the trial request
                                    string keyNoWS = System.Text.RegularExpressions.Regex.Replace(StringUtil.Extract(l.Key, "[KEY", "KEY]").Trim(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1");
                                    var jKey = JObject.Parse(keyNoWS);


                                    l.RegTo = jKey["Key"]["RegisteredTo"].Value<string>();

                                    //try to match customer; rockfish didn't track customer id in the license so this is a bit wishy washy
                                    Customer cust = null;
                                    cust = await ct.Customer.AsNoTracking().FirstOrDefaultAsync(z => z.Name == l.RegTo);
                                    if (cust == null)//email?                       
                                        cust = await ct.Customer.AsNoTracking().FirstOrDefaultAsync(z => z.EmailAddress.Contains(jTrialRequestItem["email"].Value<string>()));
                                    if (cust != null)
                                        l.CustomerId = cust.Id;


                                    l.DbId = jKey["Key"]["DBID"].Value<string>();
                                    l.LicenseExpire = jKey["Key"]["LicenseExpiration"].Value<DateTime>().ToUniversalTime();
                                    if ((bool?)jKey["Key"]["Perpetual"] ?? true)
                                        l.PGroup = ProductGroup.RavenPerpetual;
                                    else
                                        l.PGroup = ProductGroup.RavenSubscription;
                                    l.MaintenanceExpire = jKey["Key"]["MaintenanceExpiration"].Value<DateTime>().ToUniversalTime();
                                    l.Created = DateUtil.EpochToDateNullIsMin(jTrialRequestItem["dtProcessed"].Value<long>());
                                    l.FetchedOn = DateUtil.EpochToDateNullIsNull(jTrialRequestItem["dtFetched"].Value<long?>());
                                    l.FetchEmail = jTrialRequestItem["email"].Value<string>();

                                    l.Active = true;//active here means it's been fully prepared and is viable for use, all prior licenses fit this description so all are active
                                    l.NotificationSent = true;
                                    //workaround sketchy old testing licenses
                                    if (l.PGroup == ProductGroup.RavenSubscription)
                                    {
                                        if (l.MaxDataGB == null || l.MaxDataGB == 0)
                                            l.MaxDataGB = 20;

                                        if (l.CustomerUsers == null || l.CustomerUsers == 0)
                                            l.CustomerUsers = 250;


                                    }



                                    LicenseBiz lbiz = LicenseBiz.GetBiz(ct);
                                    var newLicenseObject = await lbiz.CreateAsync(l, true);

#if (DEBUG)
                                    if (newLicenseObject == null)
                                    {
                                        System.Diagnostics.Debugger.Break();
                                    }
#endif
                                    TrialKeyId = newLicenseObject.Id;

                                }
                            }

                            {
                                var tlr = new TrialLicenseRequest();
                                tlr.DbId = jTrialRequestItem["dbId"].Value<string>();
                                tlr.PGroup = (jTrialRequestItem["perpetual"].Value<bool>() ? ProductGroup.RavenPerpetual : ProductGroup.RavenSubscription);
                                tlr.CompanyName = jTrialRequestItem["companyName"].Value<string>();
                                tlr.ContactName = jTrialRequestItem["contactName"].Value<string>();
                                tlr.Email = jTrialRequestItem["email"].Value<string>();
                                tlr.EmailConfirmCode = jTrialRequestItem["emailConfirmCode"].Value<string>();
                                tlr.EmailValidated = jTrialRequestItem["emailValidated"].Value<bool>();
                                tlr.Requested = (DateTime)DateUtil.EpochToDateNullIsNull(jTrialRequestItem["dtRequested"].Value<long>());
                                tlr.Processed = DateUtil.EpochToDateNullIsNull(jTrialRequestItem["dtProcessed"].Value<long?>());
                                tlr.Status = (TrialRequestStatus)jTrialRequestItem["status"].Value<int>();
                                tlr.RejectReason = jTrialRequestItem["rejectReason"].Value<string>();
                                tlr.LicenseId = TrialKeyId;
                                var biz = TrialLicenseRequestBiz.GetBiz(ct);
                                await biz.CreateAsync(tlr, true);
                            }

                        }//context
                    }// all trial requests loop 


                }
                #endregion trial requests

                log.LogInformation("RockFish import succeeded");
            }
            catch (Exception ex)
            {
                var msg = "*** RockFish import FAILED ***";
                log.LogError(ex, msg);
            }
            finally
            {
                ServerBootConfig.MIGRATING = false;
                log.LogInformation($"Migrate Rockfish: setting server state back to {wasServerState.ToString()}");
                apiServerState.SetState(wasServerState, wasReason);
            }



            //in the correct order retrieve every object and if it's not already present in sockeye, import it
            //this should be callable any time and it will just update so it can be test live in sync / parallel until ready to switch over
            //await Task.CompletedTask;

        }


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

    }//eoc


}//eons

