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

/*
Sample key:
[KEY
{
  "Key": {
    "LicenseFormat": "8",
    "Id": "1660594773",
    "RegisteredTo": "AAA TEST DEVELOPMENT",
    "DBID": "1q3Q8MedzUQ/VQVWjQiLs3o/7nSbNFUpF5KzkM/Ny0M=",
    "Perpetual": true,
    "LicenseExpiration": "5555-01-01T00:00:00",
    "MaintenanceExpiration": "2023-08-15T00:00:00",
    "Features": [
      {
        "Name": "ServiceTechs",
        "Count": 100
      }
    ]
  }
}
KEY]
[SIGNATURE
VaVGBaXjnh1A7etqYp9bu5XkGaLxD2AplT+DERaIL8gHqSEj+1s3seEZpuRBJAvof5j++Si3ZCuesYL2M6IQUwmimYMwbQPH07MixOYxgc4kKMO53AwTmtBxwEzUYdhMn5i3g/2xOpJoXTsyqOJ0v1ZidFswQs8vVtxBO8uanlMKzFpXZE8K0cI8ZII9nRdBoUERO+JoEE16l/UAgi6O7KT8l1r225RodFhAThrdAT5ntIJ0JaTHPaMgnYb25VGS9LYQUWU7nkA+IsfddjxZW70a8IHXvQ1B5oV6srFVdkS4wY0dgbT+k7VJBDhMmltRDdOUH

*/


namespace rockfishCore.Util
{
    //Key generator controller
    public static class RavenKeyFactory
    {
        public const int PERPETUAL_TRIAL_PERIOD_DAYS = 5;
        public const int SUBSCRIPTION_TRIAL_PERIOD_HOURS = 48;


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

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

        //LICENSE USER COUNT FEATURES

        //PERPETUAL Scheduleable users 
        private const string ACTIVE_SERVICE_TECHS_FEATURE_NAME = "ServiceTechs";

        //SUBSCRIPTION
        private const string ACTIVE_INTERNAL_USERS_FEATURE_NAME = "ActiveInternalUsers";
        private const string ACTIVE_CUSTOMER_USERS_FEATURE_NAME = "ActiveCustomerUsers";


        //Add-on's / integrations

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


        #region license classes

        //DTO object returned on license query
        public 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 AyaNovaLicenseKey()
            {
                Features = new List<LicenseFeature>();
                RegisteredTo = UNLICENSED_TOKEN;
                //Id = RegisteredTo;
                LicenseFormat = "8";

                var vv = Math.Truncate((DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
                string sId = vv.ToString();
                if (sId.Contains(","))
                    sId = sId.Split('.')[0];
                Id = sId;
            }

            public override string ToString()
            {

                System.Text.StringBuilder sb = new StringBuilder();
                sb.AppendLine("LICENSE DETAILS");
                sb.AppendLine($"Registered to: {RegisteredTo}");
                sb.AppendLine($"Database id: {DbId}");
                sb.AppendLine($"Type: {(Perpetual ? "Perpetual" : "Subscription")}");
                if (WillExpire)
                    sb.AppendLine($"License valid until: {LicenseExpiration.ToLongDateString()}");
                sb.AppendLine($"Maintenance until: {MaintenanceExpiration.ToLongDateString()}");
                foreach (LicenseFeature f in Features)
                {
                    if (f.Feature == TRIAL_FEATURE_NAME)
                    {
                        sb.AppendLine("Temporary license for evaluation");
                        continue;
                    }

                    if (f.Feature == ACTIVE_SERVICE_TECHS_FEATURE_NAME)
                    {
                        sb.AppendLine($"Scheduleable resources: {f.Count}");
                        continue;
                    }
                    //default for items added later not tokenized
                    if (f.Count > 0)
                        sb.AppendLine($"{f.Feature}: {f.Count}");
                    else
                        sb.AppendLine($"{f.Feature}");
                }
                return sb.ToString();
            }


            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);
                }
            }

            /// <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;
            }


            /// <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 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 string LicenseFormat { get; set; }
            public string Id { get; set; }
            public string RegisteredTo { get; set; }
            public string DbId { get; set; }
            public bool Perpetual { get; set; }
            public DateTime LicenseExpiration { get; set; }
            public DateTime MaintenanceExpiration { get; set; }
            public List<LicenseFeature> Features { get; set; }


        }
        #endregion






        public static string GetRavenTrialKey(string dbid, string CompanyName, bool Perpetual)
        {

            //Build a sample test key, sign it and return it
            AyaNovaLicenseKey k = new AyaNovaLicenseKey();
            k.LicenseFormat = "8";
            k.RegisteredTo = CompanyName;
            k.DbId = dbid;
            k.Perpetual = Perpetual;

            //flag as trial key not regular key
            k.Features.Add(new LicenseFeature() { Feature = TRIAL_FEATURE_NAME, Count = 0 });
            if (Perpetual)
            {
                //trial period time limit
                k.MaintenanceExpiration = k.LicenseExpiration = DateTime.UtcNow.AddDays(PERPETUAL_TRIAL_PERIOD_DAYS);
                //enough techs to easily cover huge amounts of seeding (it's actually only 15 at the moment but you never know this is some buffer)
                k.Features.Add(new LicenseFeature() { Feature = ACTIVE_SERVICE_TECHS_FEATURE_NAME, Count = 250 });
            }
            else
            {
                //SUBSCRIPTION
                //trial period time limit
                k.MaintenanceExpiration = k.LicenseExpiration = DateTime.UtcNow.AddHours(SUBSCRIPTION_TRIAL_PERIOD_HOURS);

                //20k customer contacts will cover huge seeding level easily
                //5k inside staff users will cover huge seeding level easily
                k.Features.Add(new LicenseFeature() { Feature = ACTIVE_INTERNAL_USERS_FEATURE_NAME, Count = 5000 });
                k.Features.Add(new LicenseFeature() { Feature = ACTIVE_CUSTOMER_USERS_FEATURE_NAME, Count = 20000 });
            }
            return GenerateRavenKey(k);
        }


        //TESTING ONLY this is for development purposes only
        //No external usage
        public static string GetRavenTestKey(string dbid, bool Perpetual)
        {
            //Build a sample test key, sign it and return it
            AyaNovaLicenseKey k = new AyaNovaLicenseKey();
            k.LicenseFormat = "8";
            k.RegisteredTo = "GZ TestCo Inc.";
            k.DbId = dbid;
            k.Perpetual = Perpetual;
            if (Perpetual)
            {
                k.MaintenanceExpiration = k.LicenseExpiration = DateTime.UtcNow.AddDays(PERPETUAL_TRIAL_PERIOD_DAYS);
                k.Features.Add(new LicenseFeature() { Feature = ACTIVE_SERVICE_TECHS_FEATURE_NAME, Count = 250 });
            }
            else
            {
                //SUBSCRIPTION
                k.MaintenanceExpiration = k.LicenseExpiration = DateTime.UtcNow.AddHours(SUBSCRIPTION_TRIAL_PERIOD_HOURS);

                //20k customer contacts will cover huge seeding level easily
                //5k inside staff users will cover huge seeding level easily
                k.Features.Add(new LicenseFeature() { Feature = ACTIVE_INTERNAL_USERS_FEATURE_NAME, Count = 5000 });
                k.Features.Add(new LicenseFeature() { Feature = ACTIVE_CUSTOMER_USERS_FEATURE_NAME, Count = 20000 });

            }

            k.Features.Add(new LicenseFeature() { Feature = TRIAL_FEATURE_NAME, Count = 0 });
            return GenerateRavenKey(k);
        }


        /// <summary>
        /// RAVEN key generator
        /// </summary>
        /// <returns></returns>
        internal static string GenerateRavenKey(AyaNovaLicenseKey k)
        {


            if (string.IsNullOrWhiteSpace(k.RegisteredTo))
                throw new ArgumentException("RegisteredTo is required", "RegisteredTo");

            // if (k.DbId == Guid.Empty)
            if (string.IsNullOrWhiteSpace(k.DbId))
                throw new ArgumentException("DBId is required", "RegisteredTo");

            try
            {

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

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

                    //outer object start
                    w.WriteStartObject();

                    w.WritePropertyName("Key");

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

                    w.WritePropertyName("LicenseFormat");
                    w.WriteValue(k.LicenseFormat);

                    w.WritePropertyName("Id");
                    w.WriteValue(k.Id);

                    w.WritePropertyName("RegisteredTo");
                    w.WriteValue(k.RegisteredTo);

                    w.WritePropertyName("DBID");
                    w.WriteValue(k.DbId);

                    w.WritePropertyName("Perpetual");
                    w.WriteValue(k.Perpetual);

                    w.WritePropertyName("LicenseExpiration");
                    w.WriteValue(k.LicenseExpiration);

                    w.WritePropertyName("MaintenanceExpiration");
                    w.WriteValue(k.MaintenanceExpiration);



                    //FEATURES
                    // w.WritePropertyName("Features");
                    // w.WriteStartObject();
                    w.WritePropertyName("Features");
                    w.WriteStartArray();

                    foreach (LicenseFeature lf in k.Features)
                    {

                        w.WriteStartObject();

                        w.WritePropertyName("Name");
                        w.WriteValue(lf.Feature);

                        if (lf.Count > 0)
                        {
                            w.WritePropertyName("Count");
                            w.WriteValue(lf.Count);
                        }

                        w.WriteEndObject();

                    }


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

                    //end of features 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]");


                return sbOut.ToString();


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






        //eoc
    }
    //eons
}
