using System;
using System.Text;
using System.Collections.Generic;
using System.IO;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using rockfishCore.Models;
using rockfishCore.Util;



namespace rockfishCore.Util
{
    //Key generator controller
    public static class KeyFactory
    {

        public const string PLUGIN_MBI_KEY = "MBI - Minimal browser interface";
        public const string PLUGIN_WBI_KEY = "WBI - Web browser interface";
        public const string PLUGIN_QBI_KEY = "QBI - QuickBooks interface";
        public const string PLUGIN_QBOI_KEY = "QBOI - QuickBooks Online interface";
        public const string PLUGIN_PTI_KEY = "PTI - US Sage 50/Peachtree interface";
        public const string PLUGIN_QUICK_NOTIFICATION_KEY = "QuickNotification";
        public const string PLUGIN_EXPORT_TO_XLS_KEY = "ExportToXls";
        public const string PLUGIN_OUTLOOK_SCHEDULE_KEY = "OutlookSchedule";
        public const string PLUGIN_OLI_KEY = "AyaNovaOLI";
        public const string PLUGIN_IMPORT_EXPORT_CSV_DUPLICATE_KEY = "ImportExportCSVDuplicate";
        public const string PLUGIN_RI_KEY = "RI - Responsive Interface";


        private static Dictionary<string, DateTime> _plugins;

        //Generate a key message reply from a key selection object
        //CALLED BY LicenseController Generate route
        public static string GetKeyReply(dtoKeyOptions ko, LicenseTemplates t, rockfishContext ct)
        {

            //case 3542
            ko.installByDate = System.DateTime.Now.AddYears(1);//changed from one month to one year
            string sKey = genKey(ko, ct);
            string sMsg = genMessage(sKey, ko, t);
            return sMsg;
        }



        //called by trialkeyrequesthandler.GenerateFromRequest
        //get a trial key for named regTo        
        public static string GetTrialKey(string regTo, bool lite, LicenseTemplates t, string authorizedUserKeyGeneratorStamp,
        string emailAddress, rockfishContext ct)//case 3233
        {
            DateTime dtoneMonth = System.DateTime.Now.AddMonths(1);
            long oneMonth = DateUtil.DateToEpoch(System.DateTime.Now.AddMonths(1));
          
            dtoKeyOptions ko = new dtoKeyOptions();

            //case 3233
            ko.emailAddress = emailAddress;
            ko.customerId = 0; //not a customer so trial 0

            ko.registeredTo = regTo;
            ko.supportExpiresDate = oneMonth;
            ko.isLite = lite;
            ko.installByDate = dtoneMonth;
            ko.authorizedUserKeyGeneratorStamp = authorizedUserKeyGeneratorStamp;
            if (lite)
            {
                ko.users = 1;
            }
            else
            {
                ko.users = 5;
            }

            ko.licenseType = "webRequestedTrial";

            ko.qbi = true;
            ko.qbiSupportExpiresDate = oneMonth;

            ko.qboi = true;
            ko.qboiSupportExpiresDate = oneMonth;

            ko.pti = true;
            ko.ptiSupportExpiresDate = oneMonth;

            ko.exportToXls = true;
            ko.exportToXlsSupportExpiresDate = oneMonth;

            ko.outlookSchedule = true;
            ko.outlookScheduleSupportExpiresDate = oneMonth;

            ko.oli = true;
            ko.oliSupportExpiresDate = oneMonth;

            ko.importExportCSVDuplicate = true;
            ko.importExportCSVDuplicateSupportExpiresDate = oneMonth;

            if (!lite)
            {
                ko.quickNotification = true;
                ko.quickNotificationSupportExpiresDate = oneMonth;

                ko.mbi = true;
                ko.mbiSupportExpiresDate = oneMonth;

                ko.wbi = true;
                ko.wbiSupportExpiresDate = oneMonth;

                ko.ri = true;
                ko.riSupportExpiresDate = oneMonth;
            }

            string sKey = genKey(ko, ct);
            string sMsg = genMessage(sKey, ko, t);
            return sMsg;

        }


        //Take the key and the options and make a return message ready to send
        private static string genMessage(string sKey, dtoKeyOptions ko, LicenseTemplates template)
        {
            string sMessage = "";

            if (ko.licenseType == "new")
            {
                if (ko.isLite)
                {
                    sMessage = template.LiteNew;
                }
                else
                {
                    sMessage = template.FullNew;
                }
            }
            else if (ko.licenseType == "addon")
            {
                if (ko.isLite)
                {
                    sMessage = template.LiteAddOn;
                }
                else
                {
                    sMessage = template.FullAddOn;
                }
            }
            else//licensed trial
            {
                if (ko.isLite)
                {
                    sMessage = template.LiteTrial;
                }
                else
                {
                    sMessage = template.FullTrial;
                }
            }

            //token substitutions
            sMessage = sMessage.Replace("[LicenseExpiryDate]", ko.installByDate.ToString("D"));//https://github.com/dotnet/coreclr/issues/2317
            sMessage = sMessage.Replace("[LicenseDescription]", LicenseInfo(ko));
            sMessage = sMessage.Replace("[LicenseKey]", sKey);

            return sMessage;

        }




        // Extra info to display about key at top of key message      
        private static string LicenseInfo(dtoKeyOptions ko)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("LICENSE DETAILS\r\n");
            // sb.Append("This key must be installed before: ");
            // sb.Append(ko.installByDate.ToString("D"));
            // sb.Append("\r\n");

            //if (kg.SelectedLicenseType == "Web requested trial")
            //{
            //    sb.Append("*** This temporary license key has been provided for limited evaluation purposes only *** \r\n");
            //    sb.Append("This license will expire and AyaNova usage will be restricted after: " + kg.Expires.ToLongDateString() + "\r\n\r\n");
            //}

            if (ko.keyWillLockout)
            {
                sb.Append("*** This temporary license key is provided for evaluation use only pending payment ***\r\n");
                sb.Append("This license will expire and AyaNova usage will be restricted after: " + DateUtil.EpochToString(ko.lockoutDate) + "\r\n");
                sb.Append("\r\n");
                sb.Append("A permanent license key will be sent to you when payment \r\n" +
                    "has been received and processed.  There will be no extensions or \r\n" +
                    "exceptions.  Please send in payment early enough to allow for \r\n" +
                    "mail and processing time to ensure uninterrupted use of AyaNova" + (ko.isLite ? " Lite" : "") + ". \r\n\r\n");
            }




            sb.Append("Registered to: ");
            sb.Append(ko.registeredTo);
            sb.Append("\r\n");

            //case 3233
            sb.Append("Fetch address: ");
            sb.Append(ko.emailAddress);
            sb.Append("\r\n");

            sb.Append("Fetch code: ");
            sb.Append(ko.fetchCode);
            sb.Append("\r\n");


            sb.Append("Scheduleable resources: ");
            switch (ko.users)
            {
                case 1:
                    sb.AppendLine("1");
                    break;
                case 5:
                    sb.AppendLine("Up to 5");
                    break;
                case 10:
                    sb.AppendLine("Up to 10");
                    break;
                case 15:
                    sb.AppendLine("Up to 15");//case 3550
                    break;
                case 20:
                    sb.AppendLine("Up to 20");
                    break;
                case 50:
                    sb.AppendLine("Up to 50");
                    break;
                case 999:
                    sb.AppendLine("Up to 999");
                    break;
            }

            sb.AppendLine("Support and updates until: " + DateUtil.EpochToString(ko.supportExpiresDate) + "\r\n");

            if (_plugins.Count > 0)
            {
                sb.Append("\r\n");
                sb.Append("Plugins:\r\n");
                foreach (KeyValuePair<string, DateTime> kv in _plugins)
                {
                    sb.Append("\t");
                    sb.Append(kv.Key);
                    sb.Append(" support and updates until: ");
                    sb.Append(kv.Value.ToString("D"));
                    sb.Append("\r\n");
                }
            }

            return sb.ToString();
        }






        /// <summary>
        /// Generate keycode based on passed in data
        /// This is called by both regular and trial license key routes
        /// </summary>
        /// <returns></returns>
        private static string genKey(dtoKeyOptions ko, rockfishContext ct)
        {
            _plugins = new Dictionary<string, DateTime>();

            if (ko.registeredTo == null || ko.registeredTo == "")
                throw new ArgumentException("RegisteredTo is required", "RegisteredTo");

            if (string.IsNullOrWhiteSpace(ko.emailAddress))
                throw new ArgumentException("Email address is required", "emailAddress");

            try
            {

                StringBuilder sbKey = new StringBuilder();
                StringWriter sw = new StringWriter(sbKey);

                //case 3233
                ko.fetchCode = FetchKeyCode.generate();

                using (Newtonsoft.Json.JsonWriter w = new Newtonsoft.Json.JsonTextWriter(sw))
                {
                    w.Formatting = Newtonsoft.Json.Formatting.Indented;

                    //outer object start
                    w.WriteStartObject();

                    if (ko.isLite)
                        w.WritePropertyName("AyaNovaLiteLicenseKey");
                    else
                        w.WritePropertyName("AyaNovaLicenseKey");

                    w.WriteStartObject();//start of key object

                    w.WritePropertyName("SchemaVersion");
                    w.WriteValue("7");

                    //stamp a unique value in the key so it can be revoked later
                    //used to use the digest value of the key for this with xml key
                    //whole unix timestamp seconds but kept as a double to work beyond 2038
                    w.WritePropertyName("Id");
                    var vv = Math.Truncate((DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
                    string sId = vv.ToString();
                    if (sId.Contains(","))
                        sId = sId.Split('.')[0];
                    w.WriteValue(sId);

                    w.WritePropertyName("Created");
                    w.WriteValue(System.DateTime.Now);

                    w.WritePropertyName("Sub");
                    w.WriteValue("true");

                    w.WritePropertyName("RegisteredTo");
                    w.WriteValue(ko.registeredTo);//unicode test string

                    //case 3233
                    w.WritePropertyName("EmailAddress");
                    w.WriteValue(ko.emailAddress);

                    w.WritePropertyName("FetchCode");

                    w.WriteValue(ko.fetchCode);

                    //case 3187 - Source here
                    //rockfish                  
                    w.WritePropertyName("Source");
                    w.WriteValue(rockfishCore.Util.HexString.ToHex(ko.authorizedUserKeyGeneratorStamp));


                    w.WritePropertyName("InstallableUntil");
                    w.WriteValue(ko.installByDate);//case 3542, respect the KO option

                    w.WritePropertyName("TotalScheduleableUsers");
                    w.WriteValue(ko.users.ToString());//Needs to be a string to match rockfish format

                    w.WritePropertyName("Expires");
                    w.WriteValue(DateUtil.EpochToDate(ko.supportExpiresDate));

                    if (ko.keyWillLockout)
                    {
                        w.WritePropertyName("LockDate");
                        w.WriteValue(DateUtil.EpochToDate(ko.lockoutDate));

                    }

                    w.WritePropertyName("RequestedTrial");
                    bool bRequestedTrial = ko.licenseType == "webRequestedTrial";
                    w.WriteValue(bRequestedTrial.ToString());

                    //PLUGINS
                    w.WritePropertyName("Plugins");
                    w.WriteStartObject();//start of key object
                    w.WritePropertyName("Plugin");
                    w.WriteStartArray();

                    if (ko.mbi)
                        AddLicensePlugin(w, PLUGIN_MBI_KEY, DateUtil.EpochToDate(ko.mbiSupportExpiresDate));

                    if (ko.wbi)
                        AddLicensePlugin(w, PLUGIN_WBI_KEY, DateUtil.EpochToDate(ko.wbiSupportExpiresDate));

                    if (ko.qbi)
                        AddLicensePlugin(w, PLUGIN_QBI_KEY, DateUtil.EpochToDate(ko.qbiSupportExpiresDate));

                    if (ko.qboi)
                        AddLicensePlugin(w, PLUGIN_QBOI_KEY, DateUtil.EpochToDate(ko.qboiSupportExpiresDate));

                    if (ko.pti)
                        AddLicensePlugin(w, PLUGIN_PTI_KEY, DateUtil.EpochToDate(ko.ptiSupportExpiresDate));

                    if (ko.quickNotification)
                        AddLicensePlugin(w, PLUGIN_QUICK_NOTIFICATION_KEY, DateUtil.EpochToDate(ko.quickNotificationSupportExpiresDate));


                    if (ko.exportToXls)
                        AddLicensePlugin(w, PLUGIN_EXPORT_TO_XLS_KEY, DateUtil.EpochToDate(ko.exportToXlsSupportExpiresDate));


                    if (ko.outlookSchedule)
                        AddLicensePlugin(w, PLUGIN_OUTLOOK_SCHEDULE_KEY, DateUtil.EpochToDate(ko.outlookScheduleSupportExpiresDate));


                    if (ko.oli)
                        AddLicensePlugin(w, PLUGIN_OLI_KEY, DateUtil.EpochToDate(ko.oliSupportExpiresDate));


                    if (ko.importExportCSVDuplicate)
                        AddLicensePlugin(w, PLUGIN_IMPORT_EXPORT_CSV_DUPLICATE_KEY, DateUtil.EpochToDate(ko.importExportCSVDuplicateSupportExpiresDate));


                    if (ko.ri)
                        AddLicensePlugin(w, PLUGIN_RI_KEY, DateUtil.EpochToDate(ko.riSupportExpiresDate));


                    //end of plugins array
                    w.WriteEnd();

                    //end of plugins object
                    w.WriteEndObject();

                    //end of AyaNova/AyaNovaLite key object
                    w.WriteEndObject();

                    //close outer 'wrapper' object brace }
                    w.WriteEndObject();

                }//end of using statement


                // ## CALCULATE SIGNATURE

                //GET JSON as a string with whitespace stripped outside of delimited strings
                //http://stackoverflow.com/questions/8913138/minify-indented-json-string-in-net
                string keyNoWS = System.Text.RegularExpressions.Regex.Replace(sbKey.ToString(), "(\"(?:[^\"\\\\]|\\\\.)*\")|\\s+", "$1");


                //**** Note this is our real 2016 private key
                var privatePEM = @"-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAz7wrvLDcKVMZ31HFGBnLWL08IodYIV5VJkKy1Z0n2snprhSi
u3izxTyz+SLpftvKHJpky027ii7l/pL9Bo3JcjU5rKrxXavnE7TuYPjXn16dNLd0
K/ERSU+pXLmUaVN0nUWuGuUMoGJMEXoulS6pJiG11yu3BM9fL2Nbj0C6a+UwzEHF
mns3J/daZOb4gAzMUdJfh9OJ0+wRGzR8ZxyC99Na2gDmqYglUkSMjwLTL/HbgwF4
OwmoQYJBcET0Wa6Gfb17SaF8XRBV5ZtpCsbStkthGeoXZkFriB9c1eFQLKpBYQo2
DW3H1MPG2nAlQZLbkJj5cSh7/t1bRF08m6P+EQIDAQABAoIBAQCGvTpxLRXgB/Kk
EtmQBEsMx9EVZEwZeKIqKuDsBP8wvf4/10ql5mhT6kehtK9WhSDW5J2z8DtQKZMs
SBKuCZE77qH2CPp9E17SPWzQoRbaW/gDlWpYhgf8URs89XH5zxO4XtXKw/4omRlV
zLYiNR2pifv0EHqpOAg5KGzewdEo4VgXgtRWpHZLMpH2Q0/5ZIKMhstI6vFHP1p7
jmU4YI6uxiu7rVrZDmIUsAGoTdMabNqK/N8hKaoBiIto0Jn1ck26g+emLg8m160y
Xciu5yFUU+PP1SJMUs+k1UnAWf4p46X9jRLQCBRue9o0Ntiq/75aljRoDvgdwDsR
mg4ZANqxAoGBAPBoM5KoMZ4sv8ZFv8V+V8hgL5xiLgGoiwQl91mRsHRM/NQU5A/w
tH8nmwUrJOrksV7kX9228smKmoliTptyGGyi1NPmSkA7cN9YYnENoOEBHCVNK9vh
P+bkbMYUDNMW4fgOj09oXtQtMl5E2B3OTGoNwZ2w13YQJ8RIniLPsX7nAoGBAN01
eQNcUzQk9YrFGTznOs8udDLBfigDxaNnawvPueulJdBy6ZXDDrKmkQQA7xxl8YPr
dNtBq2lOgnb6+smC15TaAfV/fb8BLmkSwdn4Fy0FApIXIEOnLq+wjkte98nuezl8
9KXDzaqNI9hPuk2i36tJuLLMH8hzldveWbWjSlRHAoGBAKRPE7CQtBjfjNL+qOta
RrT0yJWhpMANabYUHNJi+K8ET2jEPnuGkFa3wwPtUPYaCABLJhprB9Unnid3wTIM
8RSO1ddd9jGgbqy3w9Bw+BvQnmQAMpG9iedNB+r5mSpM4XSgvuIO+4EYwuwbMXpt
nVx+um4Eh75xnDxTRYGVYkLRAoGAaZVpUlpR+HSfooHbPv+bSWKB4ewLPCw4vHrT
VErtEfW8q9b9eRcmP81TMFcFykc6VN4g47pfh58KlKHM7DwAjDLWdohIy89TiKGE
V3acEUfv5y0UoFX+6ara8Ey+9upWdKUY3Lotw3ckoc3EPeQ84DQK7YSSswnAgLaL
mS/8fWcCgYBjRefVbEep161d2DGruk4X7eNI9TFJ278h6ydW5kK9aTJuxkrtKIp4
CYf6emoB4mLXFPvAmnsalkhN2iB29hUZCXXSUjpKZrpijL54Wdu2S6ynm7aT97NF
oArP0E2Vbow3JMxq/oeXmHbrLMLQfYyXwFmciLFigOtkd45bfHdrbA==
-----END RSA PRIVATE KEY-----";

                PemReader pr = new PemReader(new StringReader(privatePEM));
                AsymmetricCipherKeyPair keys = (AsymmetricCipherKeyPair)pr.ReadObject();
                var encoder = new UTF8Encoding(false, true);
                var inputData = encoder.GetBytes(keyNoWS);
                var signer = SignerUtilities.GetSigner("SHA256WITHRSA");
                signer.Init(true, keys.Private);
                signer.BlockUpdate(inputData, 0, inputData.Length);
                var sign = signer.GenerateSignature();
                var signature = Convert.ToBase64String(sign);


                System.Text.StringBuilder sbOut = new StringBuilder();
                sbOut.AppendLine("[KEY");
                sbOut.AppendLine(sbKey.ToString());
                sbOut.AppendLine("KEY]");
                sbOut.AppendLine("[SIGNATURE");
                sbOut.AppendLine(signature);
                sbOut.AppendLine("SIGNATURE]");

                //case 3233 insert into db
                License l = new License();
                l.DtCreated = DateUtil.NowAsEpoch();
                l.Code = ko.fetchCode;
                l.CustomerId = ko.customerId;
                l.Email = ko.emailAddress.ToLowerInvariant();
                l.Key = sbOut.ToString();
                l.RegTo = ko.registeredTo;
                ct.License.Add(l);
                ct.SaveChanges();

                return sbOut.ToString();


            }
            catch (Exception ex)
            {
                return (ex.Message);
            }
        }



        private static void AddLicensePlugin(Newtonsoft.Json.JsonWriter w, string pluginName, DateTime pluginExpires)
        {

            //this dictionary is used by the additional message code to
            //make the human readable portion of the license
            _plugins.Add(pluginName, pluginExpires);

            //this is adding it to the actual key
            w.WriteStartObject();
            w.WritePropertyName("Item");
            w.WriteValue(pluginName);

            w.WritePropertyName("SubscriptionExpires");
            w.WriteValue(pluginExpires);

            w.WriteEndObject();
            //----------------
        }



        //eoc
    }
    //eons
}