using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
namespace InABox.Core
{
    public static class LicenseUtils
    {
        #region License Generation
        /// 
        /// Generate a new, out-of-the-box license.
        /// 
        /// The new license, valid for 1 month.
        public static LicenseData GenerateNewLicense()
        {
            return new LicenseData
            {
                LastRenewal = DateTime.Now,
                Expiry = DateTime.Now.AddMonths(1),
                CustomerID = Guid.Empty,
                RenewalAvailable = DateTime.Now.AddMonths(1).AddDays(-7)
            };
        }
        public static LicenseData RenewLicense(LicenseData oldLicense, DateTime renewed, DateTime newExpiry, DateTime renewAvailable)
        {
            return new LicenseData
            {
                LastRenewal = renewed,
                Expiry = newExpiry,
                CustomerID = oldLicense.CustomerID,
                RenewalAvailable = renewAvailable
            };
        }
        private static readonly byte[] LicenseKey = Convert.FromBase64String("dCyTyQkj1o1rqJJQlT+Jcnkxr+OQnO4KCoF/b+6cx54=");
        /// 
        /// Encrypts the license data.
        /// 
        /// The license to encrypt.
        /// 
        /// The encrypted data.
        /// 
        public static string? EncryptLicense(LicenseData license)
        {
            return Encryption.EncryptV2(Serialization.Serialize(license), LicenseKey);
        }
        public static bool TryEncryptLicense(LicenseData license, [NotNullWhen(true)] out string? result, [NotNullWhen(false)] out string? error)
        {
            return Encryption.TryEncryptV2(Serialization.Serialize(license), LicenseKey, out result, out error);
        }
        /// 
        /// Decrypts .
        /// 
        /// The license to decrypt.
        /// 
        /// The new license data, or  if errors occurred.
        /// 
        public static bool TryDecryptLicense(string data, [NotNullWhen(true)] out LicenseData? result, [NotNullWhen(false)] out string? error)
        {
            if (!Encryption.TryDecryptV2(data, LicenseKey, out var decrypted, out error))
            {
                result = null;
                return false;
            }
            result = Serialization.Deserialize(decrypted);
            if(result == null)
            {
                error = "License deserialization failed";
                return false;
            }
            return true;
        }
        /// 
        /// Decrypts , throwing an  on fail.
        /// 
        /// The license to decrypt.
        /// 
        /// The new license data.
        /// 
        public static LicenseData DecryptLicense(string data)
        {
            if (!TryDecryptLicense(data, out var result, out var error))
                throw new Exception(error);
            return result;
        }
        #endregion
        #region Fees & Discounts
        private static readonly Dictionary _licensefees = new Dictionary();
        private static readonly Dictionary _periods = new Dictionary();
        private static readonly Dictionary _userDiscounts = new Dictionary();
        public static double GetLicenseFee(Type type)
        {
            return _licensefees.GetValueOrDefault(type, 0);
        }
        public static double GetLicenseFee() where T : LicenseToken
        {
            return GetLicenseFee(typeof(T));
        }
        public static IEnumerable LicenseTypes()
        {
            return _licensefees.Keys;
        }
        public static double GetUserDiscount(int users)
        {
            var key = _userDiscounts.Keys
                .Where(x => x <= users)
                .DefaultIfEmpty(-1)
                .Max();
            if (key == -1)
                return 0;
            return _userDiscounts[key];
        }
        public static double GetTimeDiscount(int months)
        {
            var period = _periods.Keys
                .Where(x => x <= months)
                .DefaultIfEmpty(-1)
                .Max();
            if (period == -1)
                return 0;
            return _periods[period];
        }
        public static IEnumerable TimeDiscountLevels()
        {
            return _periods.Keys;
        }
        public static double CalculateLicenseFee(Dictionary licenses, int time)
        {
            double licensefee = 0.00F;
            foreach (var license in licenses.Keys)
                if (_licensefees.ContainsKey(license))
                    licensefee += _licensefees[license] * licenses[license];
            var users = licenses.Values.OrderBy(x => x).LastOrDefault();
            var userdiscount = GetUserDiscount(users);
            var timediscount = GetTimeDiscount(time);
            var result = licensefee * ((100.0F - userdiscount) / 100.0F) * ((100.0F - timediscount) / 100.0F);
            return result;
        }
        public static void LoadSummary(LicenseSummary summary)
        {
            _licensefees.Clear();
            _periods.Clear();
            _userDiscounts.Clear();
            foreach (var license in summary.LicenseFees)
                _licensefees[CoreUtils.GetEntity(license.Key)] = license.Value;
            foreach (var (months, period) in summary.TimeDiscounts)
                _periods[months] = period;
            foreach (var (users, discount) in summary.UserDiscounts)
                _userDiscounts[users] = discount;
        }
        /// 
        /// Gets a map from entities to their associated licenses.
        /// 
        /// A map of entity types to license types.
        public static Dictionary LicenseMap()
        {
            var result = new Dictionary();
            var licenses = CoreUtils.TypeList(
                AppDomain.CurrentDomain.GetAssemblies(),
                myType =>
                    myType.IsClass
                    && !myType.IsAbstract
                    && !myType.IsGenericType
                    && myType.IsSubclassOf(typeof(LicenseToken))
            ).ToArray();
            var entities = CoreUtils.TypeList(
                AppDomain.CurrentDomain.GetAssemblies(),
                myType =>
                    myType.IsClass
                    && !myType.IsAbstract
                    && !myType.IsGenericType
                    && myType.IsSubclassOf(typeof(Entity))
            ).ToArray();
            foreach (var entity in entities)
            {
                var lic = entity.GetInterfaces()
                    .FirstOrDefault(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILicense<>));
                result[entity] = lic?.GenericTypeArguments.FirstOrDefault();
            }
            return result;
        }
        public static void SaveLicenseSummary(string filename)
        {
            var entitymap = LicenseMap();
            var summary = new List { "\"License Token\",\"Class Name\"" };
            foreach (var mapentry in entitymap.OrderBy(x => x.Value))
                summary.Add(string.Format("\"{0}\",\"{1}\"", mapentry.Value?.GetCaption(), mapentry.Value.EntityName()));
            File.WriteAllLines(filename, summary.ToArray());
        }
        public static void Reset()
        {
            _licensefees.Clear();
            _periods.Clear();
            _userDiscounts.Clear();
        }
        #endregion
    }
    public class LicenseSummary
    {
        public LicenseSummary()
        {
            LicenseFees = new Dictionary();
            TimeDiscounts = new Dictionary();
            UserDiscounts = new Dictionary();
        }
        public Dictionary LicenseFees { get; set; }
        public Dictionary TimeDiscounts { get; set; }
        public Dictionary UserDiscounts { get; set; }
    }
}