using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.IO;

using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;

using Microsoft.Win32;

using C4IT.Licensing;
using C4IT.Logging;
using C4IT.DataHistoryProvider;

using static C4IT.Logging.cLogManager;

namespace C4IT.FASD.Licensing
{
    public class cF4SDLicense : cC4itLicenseBase
    {
        static public cF4SDLicense Instance = null;

        public bool IsIntrinsic { get; private set; } = false;

        static cF4SDLicense()
        {
            Instance = new cF4SDLicense
            {
                IsIntrinsic = true
            };
            //Instance.CreateIntrinsicModules();
            //Instance.SetValid();
        }

        public cF4SDLicense() : base(new Guid("776D99A6-D341-4B70-BA4B-2CC55B76A079"))
        {
            if (Instance == null)
                Instance = this;
        }

        public void SetValid()
        {
            this.StartDate = null;
            this.EndDate = null;
            this.licenseType = LicenseType.productive;
            this.LicenseMetrics = new cLicenseMetrics()
            {
                MetricType = cLicenseMetrics.eLicenseMetricType.ManagedUsers,
                Count = int.MaxValue
            };
        }

        protected override cC4itLicenseBaseEntry CreateLicenseEntry()
        {
            return new cF4SDLicenseEntry();
        }

        public class cF4SDLicenseEntry : cC4itLicenseBaseEntry
        {
            public override bool Validate()
            {
                return true;
            }
        }

        static public string GetProgramFolderLocation(Assembly ExecutingAssembly, string SubFolder, string Location)
        {
            try
            {
                var Ass = ExecutingAssembly;
                if (Ass == null)
                    Ass = Assembly.GetEntryAssembly();

                if (Ass == null)
                    return null;

                var P = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Path.GetFullPath(Ass.Location))));
                if (!string.IsNullOrEmpty(SubFolder))
                    P = Path.Combine(P, SubFolder);
                var FullPath = Path.Combine(P, Location);
                if (File.Exists(FullPath))
                    return FullPath;

                P = Ass.CodeBase;
                var uri = new Uri(P);
                string path = uri.LocalPath;
                path = Path.GetDirectoryName(path);
                string webserviceProgramFolder = Path.GetFullPath(path);
                if (!string.IsNullOrEmpty(SubFolder))
                    webserviceProgramFolder = Path.Combine(webserviceProgramFolder, SubFolder);
                webserviceProgramFolder = Path.Combine(webserviceProgramFolder, Location);
                webserviceProgramFolder = Path.GetFullPath(webserviceProgramFolder);
                if (File.Exists(webserviceProgramFolder))
                {
                    FullPath = webserviceProgramFolder;
                    return webserviceProgramFolder;
                }
            }
            catch { }

            return null;
        }


        public static class RegistryHelper
        {
            public const int KEY_WOW64_32KEY = 0x200;

            [DllImport("advapi32.dll", EntryPoint = "RegQueryInfoKey", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
            private static extern int RegQueryInfoKey(
                UIntPtr hkey,
                out StringBuilder lpClass,
                ref uint lpcbClass,
                IntPtr lpReserved,
                out uint lpcSubKeys,
                out uint lpcbMaxSubKeyLen,
                out uint lpcbMaxClassLen,
                out uint lpcValues,
                out uint lpcbMaxValueNameLen,
                out uint lpcbMaxValueLen,
                out uint lpcbSecurityDescriptor,
                ref FILETIME lpftLastWriteTime);

            [DllImport("advapi32.dll", SetLastError = true)]
            private static extern int RegCloseKey(UIntPtr hKey);


            [DllImport("advapi32.dll", CharSet = CharSet.Auto)]
            private static extern int RegOpenKeyEx(
              UIntPtr hKey,
              string subKey,
              int ulOptions,
              int samDesired,
              out UIntPtr hkResult);

            private static DateTime ToDateTime(FILETIME ft)
            {
                IntPtr buf = IntPtr.Zero;
                try
                {
                    long[] longArray = new long[1];
                    int cb = Marshal.SizeOf(ft);
                    buf = Marshal.AllocHGlobal(cb);
                    Marshal.StructureToPtr(ft, buf, false);
                    Marshal.Copy(buf, longArray, 0, 1);
                    return DateTime.FromFileTime(longArray[0]);
                }
                finally
                {
                    if (buf != IntPtr.Zero) Marshal.FreeHGlobal(buf);
                }
            }

            public static DateTime? GetDateModified(RegistryHive registryHive, string path, bool use32Bit)
            {
                var lastModified = new FILETIME();
                var lpcbClass = new uint();
                var lpReserved = new IntPtr();
                UIntPtr key = UIntPtr.Zero;

                try
                {
                    try
                    {
                        int samDesired = (int)RegistryRights.ReadKey;
                        if (use32Bit)
                            samDesired |= KEY_WOW64_32KEY;

                        var hive = new UIntPtr(unchecked((uint)registryHive));
                        if (RegOpenKeyEx(hive, path, 0, samDesired, out key) != 0)
                        {
                            return null;
                        }

                        if (RegQueryInfoKey(
                                     key,
                                     out StringBuilder sb,
                                     ref lpcbClass,
                                     lpReserved,
                                     out uint lpcbSubKeys,
                                     out uint lpcbMaxKeyLen,
                                     out uint lpcbMaxClassLen,
                                     out uint lpcValues,
                                     out uint maxValueName,
                                     out uint maxValueLen,
                                     out uint securityDescriptor,
                                     ref lastModified) != 0)
                        {
                            return null;
                        }

                        var result = ToDateTime(lastModified);
                        return result;
                    }
                    finally
                    {
                        if (key != UIntPtr.Zero)
                            RegCloseKey(key);

                    }
                }
                catch (Exception ex)
                {
                    LogException(ex);
                    return null;
                }
            }

        }

        private void CreateIntrinsicModules()
        {
            if (!IsIntrinsic)
                return;

            if (this.Modules == null)
                this.Modules = new Dictionary<Guid, cC4itLicenseBaseModule>(2);

            var _mod = new cC4itLicenseBaseModule()
            {
                Name = cDataHistoryCollectorClientAgent.constConnectorName,
                Id = new Guid(cDataHistoryCollectorClientAgent.constLicenseId)
            };
            this.Modules.Add(_mod.Id,_mod);

            _mod = new cC4itLicenseBaseModule()
            {
                Name = cDataHistoryCollectorActiveDirectory.constConnectorName,
                Id = new Guid(cDataHistoryCollectorActiveDirectory.constLicenseId)
            };
            this.Modules.Add(_mod.Id, _mod);

        }

        static public void CheckLicense(object sender, System.Timers.ElapsedEventArgs e)
        {
            var strLicPath = cF4SDLicense.GetProgramFolderLocation(Assembly.GetCallingAssembly(), "..\\License", "F4SD_License.xml");
            if (!File.Exists(strLicPath))
                strLicPath = cF4SDLicense.GetProgramFolderLocation(Assembly.GetCallingAssembly(), "License", "F4SD_License.xml");

            if (File.Exists(strLicPath))
            {
                var Lic = new cF4SDLicense();
                if (Lic.LoadFromFile(strLicPath, false))
                    cF4SDLicense.Instance = Lic;
            }
            if (cF4SDLicense.Instance.IsIntrinsic)
                cLogManager.DefaultLogger.LogEntry(LogLevels.Info, $"Intrinsic license set");
        }

        static public List<string> LogLicenseState()
        {
            var s = new List<string>();
            var _lic = Instance;
            if (_lic == null)
                return null;

            if (_lic.IsIntrinsic)
                s.Add("The F4SD license is intrinsic.");
            if (_lic.IsValid && _lic.licenseType == LicenseType.productive)
                s.Add("The F4SD License is valid.");
            else if (_lic.IsValid)
                s.Add($"The F4SD License is valid only for {_lic.licenseType.ToString().ToLowerInvariant()} purposes.");
            else
                s.Add("The F4SD License is invalid.");

            if (_lic.IsValid || _lic.IsExpired)
            {
                s.Add($"  Customer:{_lic.Customer}");
                if (_lic.EndDate == null)
                    s.Add($"  valid until: everytime");
                else
                    s.Add($"  valid until: {(DateTime)_lic.EndDate}");
                if (_lic.LicenseMetrics != null)
                    s.Add($"  license count {_lic.LicenseMetrics.Count}");

            }
            s.Add("");
            s.Add("Licensed modules:");
            if (_lic.Modules == null || _lic.Modules.Count == 0)
                s.Add("- none -");
            else
                foreach (var _mod in _lic.Modules.Values)
                    s.Add($"  {_mod.Name}");

            return s;
        }

    }
}
