using System;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Collections.Generic;
using System.Net.Http;
using AyaNova.Util;
using AyaNova.Models;
using System.Linq;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;

//JSON KEY
using Org.BouncyCastle.Security;
using Org.BouncyCastle.OpenSsl;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;





namespace AyaNova.Core
{



    internal static class License
    {

        //License server address
       private const string LICENSE_SERVER_URL = "https://rockfish.ayanova.com/";

        // #if (DEBUG)        
        //         private const string LICENSE_SERVER_URL = "http://localhost:3001/";
        // #else
        //         private const string LICENSE_SERVER_URL = "https://rockfish.ayanova.com/";
        // #endif

        //Unlicensed token
        private const string UNLICENSED_TOKEN = "UNLICENSED";

        //REVOKED token
        private const string REVOKED_TOKEN = "REVOKED";



        //Scheduleable users 
        private const string SERVICE_TECHS_FEATURE_NAME = "ServiceTechs";

        //Accounting add-on
        private const string ACCOUNTING_FEATURE_NAME = "Accounting";

        //This feature name means it's a trial key
        private const string TRIAL_FEATURE_NAME = "TrialMode";

        //This feature name means it's a SAAS or rental mode key for month to month hosted service
        private const string RENTAL_FEATURE_NAME = "ServiceMode";


        //Trial key magic number for development and testing, all other guids will be fully licensed
        // private static Guid TEST_TRIAL_KEY_DBID = new Guid("{A6D18A8A-5613-4979-99DA-80D07641A2FE}");



        //Current license key, can be empty
        private static AyaNovaLicenseKey _ActiveLicense = new AyaNovaLicenseKey();

        //The license dbid, separate from teh server dbid
        private static string LicenseDbId { get; set; }

        #region license classes

        //DTO object returned on license query
        internal class LicenseFeature
        {
            //name of feature / product
            public string Feature { get; set; }

            //Optional count for items that require it
            public long Count { get; set; }

        }

        //DTO object for parsed key
        internal class AyaNovaLicenseKey
        {

            public enum LicenseStatus
            {
                NONE = 0,//fast track
                ActiveTrial = 1,//slow track
                ExpiredTrial = 2,//fast track
                ActivePurchased = 3,//slow track
                ExpiredPurchased = 4,//fast track
                Revoked = 5//slow track
            }

            public AyaNovaLicenseKey()
            {
                Features = new List<LicenseFeature>();
                RegisteredTo = UNLICENSED_TOKEN;
                Id = RegisteredTo;
            }


            /// <summary>
            /// Fetch the license status of the feature in question
            /// </summary>
            /// <param name="Feature"></param>
            /// <returns>LicenseFeature object or null if there is no license</returns>
            public LicenseFeature GetLicenseFeature(string Feature)
            {
                if (IsEmpty)
                    return null;

                string lFeature = Feature.ToLowerInvariant();

                foreach (LicenseFeature l in Features)
                {
                    if (l.Feature.ToLowerInvariant() == lFeature)
                    {
                        return l;
                    }
                }
                return null;
            }

            public long ActiveNumber
            {
                get
                {
                    return GetLicenseFeature(SERVICE_TECHS_FEATURE_NAME).Count;
                }
            }

            /// <summary>
            /// Check for the existance of license feature
            /// </summary>
            /// <param name="Feature"></param>
            /// <returns>bool</returns>
            public bool HasLicenseFeature(string Feature)
            {
                if (IsEmpty)
                    return false;

                string lFeature = Feature.ToLowerInvariant();

                foreach (LicenseFeature l in Features)
                {
                    if (l.Feature.ToLowerInvariant() == lFeature)
                    {
                        return true;
                    }
                }
                return false;
            }

            public LicenseStatus Status
            {
                get
                {
                    //TEST 
                    //   return LicenseStatus.Revoked;


                    if (string.IsNullOrWhiteSpace(RegisteredTo) || RegisteredTo == UNLICENSED_TOKEN)
                        return LicenseStatus.NONE;
                    if (RegisteredTo == REVOKED_TOKEN)
                        return LicenseStatus.Revoked;
                    if (TrialLicense && !LicenseExpired)
                        return LicenseStatus.ActiveTrial;
                    if (TrialLicense && LicenseExpired)
                        return LicenseStatus.ExpiredTrial;
                    if (!TrialLicense && !LicenseExpired)
                        return LicenseStatus.ActivePurchased;
                    if (!TrialLicense && LicenseExpired)
                        return LicenseStatus.ExpiredPurchased;
                    throw new System.Exception("License::Status - unable to determine license status");
                }
            }

            //Has any kind of valid license that is active
            //used for auth route checking to allow for fixing this issue
            public bool KeyDoesNotNeedAttention
            {
                get
                {
                    // return !IsEmpty && !LicenseExpired;
                    return (Status == LicenseStatus.ActivePurchased) || (Status == LicenseStatus.ActiveTrial);
                }

            }


            public bool IsEmpty
            {
                get
                {
                    //Key is empty if it's not registered to anyone or there are no features in it
                    return string.IsNullOrWhiteSpace(RegisteredTo) || (Features == null || Features.Count == 0);
                }
            }

            public bool WillExpire
            {
                get
                {
                    return LicenseExpiration < DateUtil.EmptyDateValue;
                }
            }


            public bool LicenseExpired
            {
                get
                {
                    return LicenseExpiration < DateTime.Now;
                }
            }

            public bool MaintenanceExpired
            {
                get
                {
                    return MaintenanceExpiration < DateTime.Now;
                }
            }


            public bool TrialLicense
            {
                get
                {
                    return HasLicenseFeature(TRIAL_FEATURE_NAME);
                }
            }

            public bool RentalLicense
            {
                get
                {
                    return HasLicenseFeature(RENTAL_FEATURE_NAME);
                }
            }

            public string LicenseFormat { get; set; }
            public string Id { get; set; }
            public string RegisteredTo { get; set; }
            public string DbId { get; set; }
            public DateTime LicenseExpiration { get; set; }
            public DateTime MaintenanceExpiration { get; set; }
            public List<LicenseFeature> Features { get; set; }


        }
        #endregion

        #region sample v8 key
        //         private static string SAMPLE_KEY = @"[KEY
        // {
        //   ""Key"": { 
        //     ""LicenseFormat"": ""8"",       
        //     ""Id"": ""34-1516288681"", <----Customer id followed by key serial id
        //     ""RegisteredTo"": ""Super TestCo"", or "REVOKED" if revoked
        //     ""DBID"": ""df558559-7f8a-4c7b-955c-959ebcdf71f3"",
        //     ""LicenseExpiration"": ""2019-01-18T07:18:01.2329138-08:00"", <--- UTC, DateTime  if perpetual license 1/1/5555 indicates not expiring 
        //     ""MaintenanceExpiration"": ""2019-01-18T07:18:01.2329138-08:00"", <-- UTC, DateTime support and updates subscription runs out, applies to all features
        //     ""Features"": {
        //       ""Feature"": [
        //         {
        //           ""Name"": ""Scheduleable users"", 
        //           ""Count"":""10"",
        //         },
        //         {
        //           ""Name"": ""Accounting""
        //         },
        //         {
        //           ""Name"": ""TrialMode""<---means is a trial key
        //         },
        //         {
        //           ""Name"": ""ServiceMode"" <----Means it's an SAAS/Rental key
        //         }

        //       ]
        //     }
        //   }
        // }
        // KEY]
        // [SIGNATURE
        // HEcY3JCVwK9HFXEFnldUEPXP4Q7xoZfMZfOfx1cYmfVF3MVWePyZ9dqVZcY7pk3RmR1BbhQdhpljsYLl+ZLTRhNa54M0EFa/bQnBnbwYZ70EQl8fz8WOczYTEBo7Sm5EyC6gSHtYZu7yRwBvhQzpeMGth5uWnlfPb0dMm0DQM7PaqhdWWW9GCSOdZmFcxkFQ8ERLDZhVMbd8PJKyLvZ+sGMrmYTAIoL0tqa7nrxYkM71uJRTAmQ0gEl4bJdxiV825U1J+buNQuTZdacZKEPSjQQkYou10jRbReUmP2vDpvu+nA1xdJe4b5LlyQL+jiIXH17lf93xlCUb0UkDpu8iNQ==
        // SIGNATURE]\";

        #endregion

        #region Exposed properties

        //The database id value stored in the schema table
        internal static string ServerDbId { get; private set; }

        /// <summary>
        /// Fetch a summary of the license key for displaying to the end user
        /// </summary>
        /// <returns> string containing current license information</returns>
        internal static string LicenseInfo
        {
            get
            {
                StringBuilder sb = new StringBuilder();

                if (ActiveKey.IsEmpty)
                {
                    sb.AppendLine(UNLICENSED_TOKEN);
                    sb.AppendLine($"Server DB ID: {ServerDbId}");
                }
                else
                {
                    if (ActiveKey.TrialLicense)
                        sb.AppendLine("TRIAL LICENSE FOR EVALUATION PURPOSES ONLY");

                    sb.AppendLine($"Registered to: {ActiveKey.RegisteredTo}");
                    sb.AppendLine($"Key ID: {ActiveKey.Id}");
                    sb.AppendLine($"Server DB ID: {ServerDbId}");
                    sb.AppendLine($"Type: {(ActiveKey.RentalLicense ? "Service" : "Perpetual")}");
                    if (ActiveKey.WillExpire)
                        sb.AppendLine($"License expires: {DateUtil.ServerDateTimeString(ActiveKey.LicenseExpiration)}");
                    sb.AppendLine($"Maintenance subscription expires: {DateUtil.ServerDateTimeString(ActiveKey.MaintenanceExpiration)}");
                    sb.AppendLine("Features:");
                    foreach (LicenseFeature l in ActiveKey.Features)
                    {
                        //don't show the rental or trial features
                        if (l.Feature != TRIAL_FEATURE_NAME && l.Feature != RENTAL_FEATURE_NAME)
                        {
                            if (l.Count != 0)
                                sb.AppendLine($"{l.Feature} - {l.Count}");
                            else
                                sb.AppendLine($"{l.Feature}");
                        }
                    }

                }
                return sb.ToString();
            }
        }


        /// <summary>
        /// Fetch a summary of the license key for displaying in the log
        /// </summary>
        /// <returns> string containing current license information</returns>
        internal static string LicenseInfoLogFormat
        {
            get
            {
                StringBuilder sb = new StringBuilder();

                if (ActiveKey.IsEmpty)
                {
                    return $"UNLICENSED, DB ID: {LicenseDbId}";
                }
                else
                {

                    if (ActiveKey.TrialLicense)
                        sb.Append("TRIAL, ");

                    sb.Append($"regto: {ActiveKey.RegisteredTo}, ");
                    sb.Append($"keyid: {ActiveKey.Id}, ");
                    sb.Append($"dbid: {LicenseDbId}, ");
                    //  sb.Append($"serverdbid: {ServerDbId}");
                    sb.Append($"type: {(ActiveKey.RentalLicense ? "service" : "perpetual")}, ");
                    if (ActiveKey.WillExpire)
                        sb.Append($"exp: {DateUtil.ServerDateTimeString(ActiveKey.LicenseExpiration)} (utc), ");
                    sb.Append($"maint. sub. exps: {DateUtil.ServerDateTimeString(ActiveKey.MaintenanceExpiration)} (utc), ");
                    sb.Append("feat:");
                    foreach (LicenseFeature l in ActiveKey.Features)
                    {
                        //don't show the rental or trial features
                        if (l.Feature != TRIAL_FEATURE_NAME && l.Feature != RENTAL_FEATURE_NAME)
                        {
                            if (l.Count != 0)
                                sb.Append($"{l.Feature} - {l.Count}, ");
                            else
                                sb.Append($"{l.Feature}, ");
                        }
                    }

                }
                return sb.ToString().Trim().TrimEnd(',');
            }
        }


        /// <summary>
        /// Fetch a summary of the license key for displaying to the end user
        /// via the API
        /// </summary>
        /// <returns> JSON object containing current license information</returns>
        internal static object LicenseInfoAsJson
        {
            get
            {
                Newtonsoft.Json.Linq.JObject o = Newtonsoft.Json.Linq.JObject.FromObject(new
                {
                    license = new
                    {
                        serverDbId = ServerDbId,
                        licensedTo = ActiveKey.RegisteredTo,
                        dbId = ActiveKey.DbId,
                        keySerial = ActiveKey.Id,
                        licenseExpiration = ActiveKey.LicenseExpiration,
                        licenseWillExpire = ActiveKey.WillExpire,
                        maintenanceExpired = ActiveKey.MaintenanceExpired,
                        maintenanceExpiration = ActiveKey.MaintenanceExpiration,
                        features =
                            from f in ActiveKey.Features
                            orderby f.Feature
                            select new
                            {
                                Feature = f.Feature,
                                Count = f.Count
                            }
                    }
                });
                return o;
            }
        }


        /// <summary>
        /// Returns the active key
        /// </summary>
        /// <returns></returns>
        internal static AyaNovaLicenseKey ActiveKey
        {
            get
            {
                return _ActiveLicense;
            }
        }



        #endregion


        #region Trial license request handling
        /// <summary>
        /// Request a key
        /// </summary>      
        /// <returns>Result string</returns>
        internal static async Task<string> RequestTrialAsync(RequestTrial trialRequest, ILogger log)
        {
            trialRequest.DbId = ServerDbId;

            log.LogDebug($"Requesting trial license for DBID {LicenseDbId}");
            string sUrl = $"{LICENSE_SERVER_URL}rvr";
            try
            {
                var content = new StringContent(JsonConvert.SerializeObject(trialRequest), Encoding.UTF8, "application/json");
                var client = ServiceProviderProvider.HttpClientFactory.CreateClient();
                var res = await client.PostAsync(sUrl, content);
                var responseText = await res.Content.ReadAsStringAsync();
                if (res.IsSuccessStatusCode)
                {
                    if (string.IsNullOrWhiteSpace(responseText))
                        return "ok";
                    else
                        return responseText;
                }
                else return $"E1020 - Error requesting trial license key: {res.ReasonPhrase}";
            }
            catch (Exception ex)
            {
                var msg = "E1020 - Error requesting trial license key see log for details";
                log.LogError(ex, msg);
                return msg;
            }
        }
        #endregion trial license request handling


        #region License fetching and handling

        public class dtoFetchRequest
        {
            public string DbId { get; set; }
        }

        /// <summary>
        /// Fetch a key, validate it and install it in the db then initialize with it
        /// </summary>      
        /// <returns>Result string</returns>
#if (DEBUG)
        internal static async Task<string> FetchKeyAsync(AyaNova.Api.ControllerHelpers.ApiServerState apiServerState, AyContext ct, ILogger log, bool calledFromInternalJob, bool devTestTrial=false)
#else
         internal static async Task<string> FetchKeyAsync(AyaNova.Api.ControllerHelpers.ApiServerState apiServerState, AyContext ct, ILogger log, bool calledFromInternalJob)
#endif
        {
            if (calledFromInternalJob)
                log.LogTrace($"Fetching license for DBID {LicenseDbId} (called by job)");
            else
                log.LogInformation($"Fetching license for DBID {LicenseDbId}");

            var FetchRequest = new dtoFetchRequest() { DbId = LicenseDbId };
#if (DEBUG)
            string sUrl = $"{LICENSE_SERVER_URL}rvf";
            if (devTestTrial)
            {
                sUrl += "?dtt=true";//signal to rockfish to provide a key immediately for dev testing
            }
#else
         string sUrl = $"{LICENSE_SERVER_URL}rvf";
#endif

            try
            {
                // string ResponseText = await ServiceProviderProvider.HttpClientFactory.CreateClient().GetStringAsync(sUrl);

                var content = new StringContent(JsonConvert.SerializeObject(FetchRequest), Encoding.UTF8, "application/json");
                var client = ServiceProviderProvider.HttpClientFactory.CreateClient();
                var res = await client.PostAsync(sUrl, content);
                if (res.IsSuccessStatusCode)
                {
                    var responseText = await res.Content.ReadAsStringAsync();
                    var responseJson = JObject.Parse(responseText);
                    var keyText = responseJson["data"]["key"].Value<string>();

                    AyaNovaLicenseKey ParsedKey = Parse(keyText, log);

                    if (ParsedKey != null)
                    {
                        await InstallAsync(keyText, ParsedKey, apiServerState, ct, log);
                        //Log the license info so it's on the record
                        log.LogInformation($"License - new key installed [{AyaNova.Core.License.LicenseInfoLogFormat}]");
                        return "ok";
                    }
                    return $"E1020 - Error fetching license key: No key was returned";
                }
                else
                {
                    //some kind of server error??
                    if ((int)res.StatusCode > 499)
                    {
                        return $"E1020 - License server error fetching license key, contact technical support: {res.ReasonPhrase}";
                    }
                    //If it's NOT FOUND, that's not an error, just a normal response to be expected
                    if (res.StatusCode == System.Net.HttpStatusCode.NotFound)
                    {
                        return "notfound";
                    }

                    return $"E1020 - Error fetching license key: {res.ReasonPhrase}";
                }

            }
            catch (Exception ex)
            {


                if (ex is System.Net.Http.HttpRequestException)
                {//communications issue or rockfish down?
                    if (!calledFromInternalJob)
                    {
                        //user wants to see this:
                        var msg = $"E1020 - COMM Error fetching / installing license key: {ex.Message}";
                        //we want it in the log as this is on demand so it won't fill up the log and tech support might need to see this
                        log.LogError(ex, msg);
                        return msg;
                    }
                    else
                    {
                        //happened when called from JOB, don't want to fill up the log with this error so just logdebug it and move on
                        var msg = $"E1020 - COMM Error fetching / installing license key (From Job)";
                        log.LogDebug(ex, msg);
                        return msg;
                    }
                }
                else
                {
                    //some other error
                    if (calledFromInternalJob) throw;
                    var msg = "E1020 - Error fetching / installing license key";
                    log.LogError(ex, msg);
                    return msg;
                }
            }
        }


        /// <summary>
        /// Initialize the license
        /// 
        /// </summary>
        internal static async Task InitializeAsync(AyaNova.Api.ControllerHelpers.ApiServerState apiServerState, AyContext ct, ILogger log)
        {
            log.LogDebug("Initializing license");

            try
            {
                //First fetch the schema db id for the servers database, the license must match
                var schema = await ct.SchemaVersion.AsNoTracking().SingleOrDefaultAsync();
                //if (schema == null || schema.Id == Guid.Empty)
                if (schema == null || string.IsNullOrWhiteSpace(schema.Id))
                {
                    //cryptic message deliberately, this is probably caused by someone trying to circumvent licensing
                    var msg = "E1030 - Database integrity check failed (2). Contact support.";
                    apiServerState.SetOpsOnly(msg);
                    log.LogCritical(msg);
                    return;
                }
                ServerDbId = schema.Id;

                //Fetch key from db as no tracking so doesn't hang round if need to immediately clear and then re-add the key 
                Models.License ldb = await ct.License.AsNoTracking().SingleOrDefaultAsync();

                //Non existent license should restrict server to ops routes only with closed API
                if (ldb == null)
                {
                    ldb = new Models.License();
                    ldb.DbId = ServerDbId;
                    ldb.Key = "none";
                    ct.License.Add(ldb);
                    await ct.SaveChangesAsync();
                }

                //ensure DB ID
                //  if (ldb.DbId == Guid.Empty)
                if (string.IsNullOrWhiteSpace(ldb.DbId))
                {
                    ldb.DbId = ServerDbId;
                    //Convert the no tracking record fetched above to tracking
                    //this is required because a prior call to initialize before dumping the db would mean the license is still in memory in the context
                    ct.Entry(ldb).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
                    await ct.SaveChangesAsync();
                }

                //Get it early and set it here so that it can be displayed early to the user even if not licensed
                LicenseDbId = ldb.DbId;

                if (ldb.Key == "none")
                {
                    var msg = "E1020 - License key not found in database, running in unlicensed mode";
                    apiServerState.SetSystemLock(msg);
                    if (ServerBootConfig.AYANOVA_SERVER_TEST_MODE)
                        log.LogDebug(msg);
                    else
                        log.LogWarning(msg);
                    return;
                }

                //Validate the key
                AyaNovaLicenseKey k = Parse(ldb.Key, log);
                if (k == null)
                {
                    var msg = "E1020 - License key in database is not valid, running in unlicensed mode";
                    apiServerState.SetSystemLock(msg);
                    log.LogCritical(msg);
                    return;
                }

                _ActiveLicense = k;

                if (_ActiveLicense.LicenseExpired)
                {
                    var msg = $"E1020 - License key expired {DateUtil.ServerDateTimeString(_ActiveLicense.LicenseExpiration)}";
                    apiServerState.SetSystemLock(msg);
                    log.LogCritical(msg);
                    return;
                }

                if (_ActiveLicense.Status == AyaNovaLicenseKey.LicenseStatus.Revoked)
                {
                    var msg = $"E1020 - License key revoked";
                    apiServerState.SetSystemLock(msg);
                    log.LogCritical(msg);
                    return;
                }

                //Has someone been trying funny business with the active techs in the db?
                if (await AyaNova.Biz.UserBiz.ActiveCountAsync() > _ActiveLicense.ActiveNumber)
                {
                    var msg = $"E1020 - Active count exceeded capacity";
                    apiServerState.SetSystemLock(msg);
                    log.LogCritical(msg);
                    return;
                }

                //Key is ok, might not have been on first boot so check and clear if locked
                //This works for now because system lock only means license lock
                //if ever changed for other purposes then need to handle that see serverstate for ideas
                if (apiServerState.IsSystemLocked)
                {
                    apiServerState.ClearSystemLock();
                }
                log.LogDebug("License key OK");
            }
            catch (Exception ex)
            {
                var msg = "E1020 - Error initializing license key";
                log.LogCritical(ex, msg);
                apiServerState.SetSystemLock(msg);
                throw new ApplicationException(msg, ex);
            }
        }


        /// <summary>
        /// Install key to db
        /// </summary>        
        private static async Task<bool> InstallAsync(string RawTextNewKey, AyaNovaLicenseKey ParsedNewKey, AyaNova.Api.ControllerHelpers.ApiServerState apiServerState, AyContext ct, ILogger log)
        {
            try
            {
                var CurrentInDbKeyRecord = await ct.License.OrderBy(z => z.Id).FirstOrDefaultAsync();
                if (CurrentInDbKeyRecord == null)
                    throw new ApplicationException("E1020 - Can't install key, no key record found");

                if (ParsedNewKey == null)
                {
                    throw new ApplicationException("E1020 - License.Install -> key could not be parsed");
                }

                //Can't install a trial into a non-empty db
                if (ParsedNewKey.TrialLicense && !await DbUtil.DBIsEmptyAsync(ct, log))
                {
                    throw new ApplicationException("E1020 - Can't install a trial key into a non empty AyaNova database.  Erase the database first.");
                }

                //TODO: TECHCOUNT - new license causes exceeding count?  
                long NewTechCount = ParsedNewKey.GetLicenseFeature(SERVICE_TECHS_FEATURE_NAME).Count;
                if (await AyaNova.Biz.UserBiz.ActiveCountAsync() > NewTechCount)
                {
                    //attempt to set enough of the eldest last login techs to inactive
                    await AyaNova.Biz.UserBiz.DeActivateExcessiveTechs(NewTechCount, log);

                    if (await AyaNova.Biz.UserBiz.ActiveCountAsync() > NewTechCount)
                        throw new ApplicationException("E1020 - Can't install key, too many active techs and / or subcontractors in database. Deactivate enough to install key.");
                }

                //Update current license               
                CurrentInDbKeyRecord.Key = RawTextNewKey;
                await ct.SaveChangesAsync();
            }
            catch (Exception ex)
            {
                var msg = "E1020 - Error installing license key";
                log.LogError(ex, msg);
                throw new ApplicationException(msg, ex);
            }
            finally
            {
                await InitializeAsync(apiServerState, ct, log);
            }
            return true;
        }


        #endregion

        #region PARSE and Validate key
        /// <summary>
        /// Parses and validates the integrity of a passed in textual license key
        /// </summary>      
        /// <returns>a populated key if valid or else null</returns>
        private static AyaNovaLicenseKey Parse(string k, ILogger log)
        {
            AyaNovaLicenseKey key = new AyaNovaLicenseKey();

            log.LogDebug("Validating license");

            if (string.IsNullOrWhiteSpace(k))
            {
                throw new ApplicationException("E1020 - License.Parse -> License key is empty and can't be validated");
            }

            try
            {
                if (!k.Contains("[KEY") ||
                                    !k.Contains("KEY]") ||
                                    !k.Contains("[SIGNATURE") ||
                                    !k.Contains("SIGNATURE]"))
                {
                    throw new ApplicationException("E1020 - License.Parse -> License key is missing required delimiters");

                }


                string keyNoWS = System.Text.RegularExpressions.Regex.Replace(StringUtil.Extract(k, "[KEY", "KEY]").Trim(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1");
                string keySig = StringUtil.Extract(k, "[SIGNATURE", "SIGNATURE]").Trim();
                //bugbug second time around after installing key, keysig has cr/lf characters in it after this extract method runs, not sure wtf as it isnt there the first time

                #region Check Signature

                //***** NOTE: this is our real 2016 public key
                var publicPem = @"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz7wrvLDcKVMZ31HFGBnL
WL08IodYIV5VJkKy1Z0n2snprhSiu3izxTyz+SLpftvKHJpky027ii7l/pL9Bo3J
cjU5rKrxXavnE7TuYPjXn16dNLd0K/ERSU+pXLmUaVN0nUWuGuUMoGJMEXoulS6p
JiG11yu3BM9fL2Nbj0C6a+UwzEHFmns3J/daZOb4gAzMUdJfh9OJ0+wRGzR8ZxyC
99Na2gDmqYglUkSMjwLTL/HbgwF4OwmoQYJBcET0Wa6Gfb17SaF8XRBV5ZtpCsbS
tkthGeoXZkFriB9c1eFQLKpBYQo2DW3H1MPG2nAlQZLbkJj5cSh7/t1bRF08m6P+
EQIDAQAB
-----END PUBLIC KEY-----";


                PemReader pr = new PemReader(new StringReader(publicPem));
                var KeyParameter = (Org.BouncyCastle.Crypto.AsymmetricKeyParameter)pr.ReadObject();
                var signer = SignerUtilities.GetSigner("SHA256WITHRSA");
                signer.Init(false, KeyParameter);
                var expectedSig = Convert.FromBase64String(keySig);
                var msgBytes = Encoding.UTF8.GetBytes(keyNoWS);
                signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
                if (!signer.VerifySignature(expectedSig))
                {
                    throw new ApplicationException("E1020 - License.Parse -> License key failed integrity check and is not valid");
                }

                #endregion check signature

                #region Get Values
                Newtonsoft.Json.Linq.JToken token = Newtonsoft.Json.Linq.JObject.Parse(keyNoWS);

                key.LicenseFormat = (string)token.SelectToken("Key.LicenseFormat");
                if (key.LicenseFormat != "8")
                    throw new ApplicationException($"E1020 - License.Parse -> License key format {key.LicenseFormat} not recognized");
                key.Id = (string)token.SelectToken("Key.Id");
                key.RegisteredTo = (string)token.SelectToken("Key.RegisteredTo");
                key.DbId = (string)token.SelectToken("Key.DBID");
                if (key.DbId != ServerDbId)
                    throw new ApplicationException($"E1020 - License.Parse -> License key does not match this server");
                key.LicenseExpiration = (DateTime)token.SelectToken("Key.LicenseExpiration");
                key.MaintenanceExpiration = (DateTime)token.SelectToken("Key.MaintenanceExpiration");

                //FEATURES
                Newtonsoft.Json.Linq.JArray p = (Newtonsoft.Json.Linq.JArray)token.SelectToken("Key.Features");
                for (int z = 0; z < p.Count; z++)
                {
                    LicenseFeature lf = new LicenseFeature();
                    lf.Feature = (string)p[z].SelectToken("Name");
                    if (p[z].SelectToken("Count") != null)
                    {
                        lf.Count = (long)p[z].SelectToken("Count");
                    }
                    else
                    {
                        lf.Count = 0;
                    }
                    key.Features.Add(lf);
                }


                #endregion get values
                //All is well return key
                return key;

            }
            catch (Exception ex)
            {
                var msg = "E1020 - License key not valid";
                log.LogError(ex, msg);
                throw new ApplicationException(msg, ex);
            }


        }
        #endregion




    }//eoc

}//eons
