using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.IO;

using C4IT.Logging;
using static C4IT.Logging.cLogManager;


namespace C4IT.LIAM
{
    public enum eLiamAccessRoles
    {
        Owner = 1, Write = 2, Read = 3, Traverse = 4,
        ADOwner = 110,                     // "AD Owner Group"
        ADMember = 100,                    // "AD Member Group"
        ExchangeMLMember = 200,            // "Mailing List Member"
        ExchangeMLOwner = 210,             // "Mailing List Owner"
        ExchangeSMBFullAccess = 250,       // "Shared Mailbox Full Access"
        ExchangeSMBSendAs = 260,           // "Shared Mailbox Send as"
        ExchangeSMBOwner = 270             // "Shared Mailbox Owner"
    };
    public enum eLiamAccessRoleScopes { Unknown = 0, Universal = 1, Global = 2, DomainLocal = 3 };

    public enum eLiamProviderTypes { Unknown = 0, Ntfs = 1, Sharepoint = 2, Matrix42 = 3, MsTeams = 4, ActiveDirectory = 5, Exchange = 6 };

    public enum eLiamDataAreaTypes
    {
        Unknown = 0,
        NtfsServerRoot = 100,
        NtfsShare = 101,
        NtfsFolder = 102,
        DfsNamespaceRoot = 103,
        MsTeamsTeam = 401,
        MsTeamsChannel = 402,
        MsTeamsFolder = 403,
        ActiveDirectoryGroup = 501,
        ExchangeSharedMailbox = 601,
        ExchangeDistributionGroup = 602
    };

    public enum eLiamGroupStrategies { None = 0, Ntfs_AGP = 0, Ntfs_AGDLP = 1 };

    public class cLiamConfiguration
    {
    }

    public class cLiamProviderData : ICloneable
    {

        public eLiamProviderTypes ProviderType { get; set; } = eLiamProviderTypes.Unknown;

        public string Domain { get; set; } = "";

        public string RootPath { get; set; } = "";

        public cLiamCredential Credential { get; set; } = null;

        public int MaxDepth { get; set; } = 1;

        public eLiamGroupStrategies GroupStrategy { get; set; } = eLiamGroupStrategies.None;

        public string DataAreaFilter { get; set; } = "";

        public string DataAreaRegEx { get; set; } = "";

        public string GroupFilter { get; set; } = "";

        public string GroupRegEx { get; set; } = "";

        public string GroupPath { get; set; } = "";

        public string OwnerGroupGlobal { get; set; } = "";
        public string OwnerGroupLocal { get; set; } = "";
        public string WriteGroupGlobal { get; set; } = "";
        public string WriteGroupLocal { get; set; } = "";
        public string ReadGroupGlobal { get; set; } = "";
        public string ReadGroupLocal { get; set; } = "";
        public string TraverseGroup { get; set; } = "";

        public List<cLiamNamingConvention> NamingConventions { get; set; } = new List<cLiamNamingConvention>();
        public Dictionary<string, string> CustomTags { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        public Dictionary<string, string> AdditionalConfiguration { get; set; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        public static void Copy(cLiamProviderData From, cLiamProviderData To)
        {
            To.ProviderType = From.ProviderType;
            To.Domain = From.Domain;
            To.RootPath = From.RootPath;
            To.Credential = From.Credential;
            To.MaxDepth = From.MaxDepth;
            To.GroupStrategy = From.GroupStrategy;
            To.DataAreaFilter = From.DataAreaFilter;
            To.DataAreaRegEx = From.DataAreaRegEx;
            To.GroupFilter = From.GroupFilter;
            To.GroupRegEx = From.GroupRegEx;
            To.GroupPath = From.GroupPath;
            To.CustomTags = From.CustomTags;
            To.AdditionalConfiguration = From.AdditionalConfiguration;
            To.CustomTags = From.CustomTags;
            To.NamingConventions = From.NamingConventions;
        }

        public cLiamProviderData()
        {
        }

        public cLiamProviderData(cLiamProviderData PD)
        {
            Copy(PD, this);
        }

        public object Clone()
        {
            return new cLiamProviderData(this);
        }
        public void ReplaceCustomTags()
        {
            foreach (var customTag in this.CustomTags)
            {
                var Key = customTag.Key;

                foreach (var namingConvention in NamingConventions)
                {
                    if (customTag.Key == "Filesystem_GroupDomainLocalTag" && namingConvention.Scope == eLiamAccessRoleScopes.DomainLocal || customTag.Key == "Filesystem_GroupGlobalTag" && namingConvention.Scope == eLiamAccessRoleScopes.Global)
                        Key = "SCOPETAG";
                    else
                        Key = customTag.Key;
                    namingConvention.DescriptionTemplate = namingConvention.DescriptionTemplate.Replace($"{{{{{Key}}}}}", customTag.Value);
                    namingConvention.NamingTemplate = namingConvention.NamingTemplate.Replace($"{{{{{Key}}}}}", customTag.Value);
                    namingConvention.Wildcard = namingConvention.Wildcard.Replace($"{{{{{Key}}}}}", customTag.Value);
                }
                GroupFilter = GroupFilter.Replace($"{{{{{Key}}}}}", customTag.Value);
            }
        }
    }

    public abstract class cLiamProviderBase : cLiamProviderData
    {
        public cLiamConfiguration LiamConfiguration { get; private set; }

        public cLiamProviderBase(cLiamConfiguration Configuration, cLiamProviderData ProviderData) :
            base(ProviderData)
        {
            LiamConfiguration = Configuration;
        }

        public abstract Task<List<cLiamDataAreaBase>> getDataAreasAsync(int MaxDepth = -1);

        public abstract Task<bool> LogonAsync();
        public abstract string GetLastErrorMessage();

        public abstract Task<cLiamDataAreaBase> LoadDataArea(string UID);

        public static cLiamProviderBase CreateInstance(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData)
        {

            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var MyPath = Assembly.GetExecutingAssembly()?.Location;
                if (MyPath == null)
                    return null;

                MyPath = Path.GetDirectoryName(MyPath);
                var DllPath = Path.Combine(MyPath, $"Liam{ProviderData.ProviderType}.dll");
                if (!File.Exists(DllPath))
                {
                    LogEntry($"Couldn not found Data Provider library '{DllPath}'", LogLevels.Error);
                    return null;
                }

                var assLocal = Assembly.LoadFrom(DllPath);
                if (assLocal == null)
                {
                    LogEntry($"Could not load Data Provider library '{DllPath}'", LogLevels.Error);
                    return null;
                }
                var TP = assLocal.GetTypes();
                var type = assLocal.GetType("C4IT.LIAM.LiamInitializer");
                if (type == null)
                {
                    LogEntry($"Could not found class 'C4IT.LIAM.LiamInitializer' in library '{DllPath}'", LogLevels.Error);
                    return null;
                }

                var MI = type.GetMethod("CreateInstance");
                if (MI == null)
                {
                    LogEntry($"Could not found method 'CreateInstance' in class 'C4IT.LIAM.LiamInitializer' in library '{DllPath}'", LogLevels.Error);
                    return null;
                }
                var objDll = MI.Invoke(null, new object[] { LiamConfiguration, ProviderData });
                var RetVal = objDll as cLiamProviderBase;
                ProviderData.ReplaceCustomTags();
                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }

        }

        public abstract Task<List<cLiamDataAreaBase>> getSecurityGroupsAsync(string groupFilter);
    }

    public class cLiamCredential
    {
        public string Domain { get; set; } = "";
        public string Identification { get; set; } = "";
        public string Secret { get; set; } = "";
    }

    public class cLiamNamingConvention
    {
        public string Name { get; set; } = "";
        public string Description { get; set; } = "";
        public string NamingTemplate { get; set; } = "";
        public string DescriptionTemplate { get; set; } = "";
        public string Wildcard { get; set; } = "";
        public eLiamAccessRoles AccessRole { get; set; } = eLiamAccessRoles.Read;
        public eLiamAccessRoleScopes Scope { get; set; } = eLiamAccessRoleScopes.Unknown;
        public eLiamProviderTypes? ProviderType { get; set; } = null;
    }

    public class cLiamDataAreaInfo
    {
        public string DisplayName { get; protected set; } = null;
        public string Description { get; protected set; } = null;
        public string OwnerRef { get; set; } = null;
        public string UID { get; protected set; } = null;
        public string CreatedDate { get; protected set; } = null;

        public string TechnicalName { get; protected set; } = "";

        public int Level { get; set; } = -1;

        public string ParentUID { get; protected set; } = null;

        public eLiamDataAreaTypes DataType { get; protected set; } = eLiamDataAreaTypes.Unknown;

        public bool SupportsPermissions { get; protected set; } = false;

        public bool SupportsOwners { get; protected set; } = false;
    }

    public abstract class cLiamDataAreaBase : cLiamDataAreaInfo
    {

        public readonly cLiamProviderBase Provider = null;

        public cLiamDataAreaBase(cLiamProviderBase Provider)
        {
            this.Provider = Provider;
        }

        public abstract Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1);

        public static async Task<List<cLiamDataAreaBase>> getChildrenFromListAsync(List<cLiamDataAreaBase> Items, int Depth = -1)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            var RetVal = new List<cLiamDataAreaBase>();
            try
            {
                if (Items == null)
                    return RetVal;

                if (Depth <= 0)
                    return RetVal;

                foreach (var Entry in Items)
                {
                    var Childs = await Entry.getChildrenAsync(Depth + 1);
                    if (Childs != null)
                        RetVal.AddRange(Childs);
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }
            return RetVal;
        }

        public virtual async Task<List<cLiamUserInfo>> GetOwnersAsync()
        {
            await Task.Delay(0);
            return null;
        }

        public virtual async Task<List<cLiamPermissionInfo>> GetPermissionsAsync(bool force)
        {
            await Task.Delay(0);
            return null;
        }

        public virtual async Task<cLiamPermissionResult> GrantPermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role)
        {
            await Task.Delay(0);
            return null;
        }

        public virtual async Task<bool> RevokePermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role)
        {
            await Task.Delay(0);
            return false;
        }
    }

    public class cLiamUserInfo
    {
        public string DisplayName { get; set; } = null;
        public string GivenName { get; set; } = null;
        public string SurName { get; set; } = null;
        public string UserPrincipalName { get; set; } = null;
        public string EMail { get; set; } = null;
        public string SID { get; set; } = null;
    }

    public class cLiamPermissionResult
    {
        public bool Valid { get; set; } = false;
        public string UserReference { get; set; } = null;
    }

    public class cLiamPermissionInfo
    {
        public cLiamUserInfo User;
        public eLiamAccessRoles AccessRole = eLiamAccessRoles.Read;
        public bool OnlyEffective = false;
    }


    public class DataAreaEntryBase
    {
        public string DisplayName { get; set; }
        public string TechnicalName { get; set; }
        public string UID { get; set; }
        public string TargetType { get; set; }
    }

    public class SecurityGroupEntry : DataAreaEntryBase
    {
        public string Scope { get; set; }
    }
    public class DataAreaEntry : DataAreaEntryBase
    {
        public string Parent { get; set; }
        public string ParentUID { get; set; }
        public string Owner { get; set; }
        public string Write { get; set; }
        public string Read { get; set; }
        public string Traverse { get; set; }
        public string CreatedDate { get; set; }
        public string Level { get; set; }
        public string ConfigurationId { get; set; }
        public string BaseFolder { get; set; }
        public string Description { get; set; }
        public string UniqueId { get; set; }
        public string DataAreaType { get; set; }
        public int DataAreaTypeId { get; set; }
    }
    public class LiamApiVersionInfo
    {
        public string ProductName { get; set; } = "- unknown -";
        public string ProductVersion { get; set; } = "";
        public string AssemblyName { get; set; } = "- unknown -";
        public string AssemblyVersion { get; set; } = "";
    }

    public class ProviderCacheEntry
    {
        public Guid ID;
        public Guid ObjectID;
        public DateTime ValidUntil;
        public cLiamProviderBase Provider;
    }

    public class LiamDataAreaEntry
    {
        public string DisplayName { get; set; } = "";

        public string UID { get; set; } = "";

        public bool SupportsPermissions { get; set; } = false;
    }
}
