﻿using System;
using System.Collections.Generic;
using System.DirectoryServices.AccountManagement;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.AccessControl;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

using C4IT.Logging;
using C4IT.Matrix42.ServerInfo;
using LiamNtfs;
using static C4IT.Logging.cLogManager;
using static LiamNtfs.cActiveDirectoryBase;

namespace C4IT.LIAM
{
    public static class LiamInitializer
    {
        static public cLiamProviderBase CreateInstance(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData)
        {
            return new cLiamProviderNtfs(LiamConfiguration, ProviderData);
        }
    }

    public class cLiamProviderNtfs : cLiamProviderBase
    {
        public static Guid nftsModuleId = new Guid("77e213a1-6517-ea11-4881-000c2980fd94");
        public readonly cNtfsBase ntfsBase = new cNtfsBase();
        public readonly cActiveDirectoryBase activeDirectoryBase = new cActiveDirectoryBase();

        //public readonly bool WithoutPrivateFolders = true;

        public cLiamProviderNtfs(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData) :
            base(LiamConfiguration, ProviderData)
        {
            this.ReplaceNtfsCustomTags();
        }

        public override async Task<bool> LogonAsync()
        {
            if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId))
            {
                LogEntry($"Error: License not valid", LogLevels.Error);
                return false;
            }
            return await LogonAsync(true);
        }
        private void ReplaceNtfsCustomTags()
        {
            foreach (var namingConvention in NamingConventions)
            {
                String key = null;
                String value = null;
                key = "GROUPTYPEPOSTFIX";
                if (namingConvention.AccessRole == eLiamAccessRoles.Owner)
                {
                    value = CustomTags["Filesystem_GroupOwnerTag"];
                }
                else if (namingConvention.AccessRole == eLiamAccessRoles.Write)
                {
                    value = CustomTags["Filesystem_GroupWriteTag"];
                }
                else if (namingConvention.AccessRole == eLiamAccessRoles.Read)
                {
                    value = CustomTags["Filesystem_GroupReadTag"];
                }
                else if (namingConvention.AccessRole == eLiamAccessRoles.Traverse)
                {
                    value = CustomTags["Filesystem_GroupTraverseTag"];
                }
                if (!String.IsNullOrEmpty(key) && !String.IsNullOrEmpty(value))
                {
                    namingConvention.DescriptionTemplate = namingConvention.DescriptionTemplate.Replace($"{{{{{key}}}}}", value);
                    namingConvention.NamingTemplate = namingConvention.NamingTemplate.Replace($"{{{{{key}}}}}", value);
                    namingConvention.Wildcard = namingConvention.Wildcard.Replace($"{{{{{key}}}}}", value);
                }

                value = null;
                key = "SCOPE";
                if (namingConvention.Scope == eLiamAccessRoleScopes.DomainLocal)
                {
                    value = CustomTags["Filesystem_GroupDomainLocalTag"];
                }
                else if (namingConvention.Scope == eLiamAccessRoleScopes.Global)
                {
                    value = CustomTags["Filesystem_GroupGlobalTag"];
                }
                if (!String.IsNullOrEmpty(key) && !String.IsNullOrEmpty(value))
                {
                    namingConvention.DescriptionTemplate = namingConvention.DescriptionTemplate.Replace($"{{{{{key}}}}}", value);
                    namingConvention.NamingTemplate = namingConvention.NamingTemplate.Replace($"{{{{{key}}}}}", value);
                    namingConvention.Wildcard = namingConvention.Wildcard.Replace($"{{{{{key}}}}}", value);
                }
            }
        }
        public async Task<bool> LogonAsync(bool force = false)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var LI = new cNtfsLogonInfo()
                {
                    Domain = Domain,
                    User = Credential?.Identification,
                    UserSecret = Credential?.Secret,
                    TargetNetworkName = RootPath,
                    TargetGroupPath = this.GroupPath
                };
                var RetVal = await ntfsBase.LogonAsync(LI) && await activeDirectoryBase.LogonAsync(LI);
                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }

            return false;
        }

        public override async Task<List<cLiamDataAreaBase>> getDataAreasAsync(int Depth = -1)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId))
                {
                    LogEntry($"Error: License not valid", LogLevels.Error);
                    return new List<cLiamDataAreaBase>();
                }
                if (!await LogonAsync())
                    return null;
                if (string.IsNullOrEmpty(this.RootPath))
                    return null;

                var DataAreas = new List<cLiamDataAreaBase>();

                var rootpathSplit = this.RootPath.Split(new string[] { "\\" }, StringSplitOptions.RemoveEmptyEntries);
                cLiamNtfsShare share = null;
                cLiamNtfsFolder NtfsRootFolder = null;
                switch (rootpathSplit.Length)
                {
                    case 0:
                    case 1:
                        return null;
                    case 2:
                        {
                            share = new cLiamNtfsShare(this, new cNtfsResultShare()
                            {
                                DisplayName = rootpathSplit.Last(),
                                Path = RootPath,
                                Level = 0
                            });
                            DataAreas.Add(share);
                            break;
                        }
                    default:
                        {
                            NtfsRootFolder = new cLiamNtfsFolder(this, null, null, new cNtfsResultFolder()
                            {
                                DisplayName = rootpathSplit.Last(),
                                Path = RootPath,
                                Level = 0
                            });
                            DataAreas.Add(NtfsRootFolder);
                            break;
                        }
                }

                var DAL = await ntfsBase.RequestFoldersListAsync(this.RootPath, Depth);
                if (DAL == null)
                    return null;

                foreach (var Entry in DAL)
                {
                    if (!string.IsNullOrEmpty(this.DataAreaRegEx) && !Regex.Match(Entry.Value.DisplayName, this.DataAreaRegEx).Success)
                        continue;


                    var Folder = new cLiamNtfsFolder(this, share, NtfsRootFolder, (cNtfsResultFolder)Entry.Value);
                    DataAreas.Add(Folder);
                }
                return DataAreas;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }

            return null;
        }
        public override async Task<cLiamDataAreaBase> LoadDataArea(string UID)
        {
            //TODO implement LoadDataArea
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);
                if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId))
                {
                    LogEntry($"Error: License not valid", LogLevels.Error);
                    return null;
                }
                var splt = UID.Split(System.IO.Path.DirectorySeparatorChar);
                var name = Path.GetDirectoryName(UID);
                switch (splt.Length)
                {
                    case 0:
                    case 1:
                        return null;
                    case 2:
                        {
                            return new cLiamNtfsShare(this, new cNtfsResultShare()
                            {
                                DisplayName = name,
                                Path = UID,
                                Level = getDepth(UID)
                            });
                        }
                    default:
                        {
                            return new cLiamNtfsFolder(this, null, null, new cNtfsResultFolder()
                            {
                                DisplayName = name,
                                Path = UID,
                                Level = getDepth(UID)
                            });
                        }
                }
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }

        }

        public override async Task<List<cLiamDataAreaBase>> getSecurityGroupsAsync(string groupFilter)
        {

            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(nftsModuleId))
                {
                    LogEntry($"Error: License not valid", LogLevels.Error);
                    return new List<cLiamDataAreaBase>();
                }
                if (!await LogonAsync())
                    return null;
                if (string.IsNullOrEmpty(this.GroupPath))
                    return null;

                var SecurityGroups = new List<cLiamDataAreaBase>();

                var SGL = await activeDirectoryBase.RequestSecurityGroupsListAsync(groupFilter);
                if (SGL == null)
                    return null;

                foreach (var Entry in SGL)
                {
                    if (!string.IsNullOrEmpty(this.GroupRegEx) && !Regex.Match(Entry.Value.DisplayName, this.GroupRegEx).Success)
                        continue;


                    var SecurityGroup = new cLiamAdGroup(this, (cSecurityGroupResult)Entry.Value);
                    SecurityGroups.Add(SecurityGroup);
                }
                return SecurityGroups;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }

            return null;
        }
        public int getDepth(string path)
        {
            return getDepth(this.RootPath, path);
        }
        public static int getDepth(DirectoryInfo root, DirectoryInfo folder)
        {
            var rootDepth = root.FullName.TrimEnd(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar).Length;
            var folderDepth = folder.FullName.TrimEnd(Path.DirectorySeparatorChar).Split(Path.DirectorySeparatorChar).Length;
            return folderDepth - rootDepth;

        }
        public static int getDepth(string root, string folder)
        {
            return getDepth(new DirectoryInfo(root), new DirectoryInfo(folder));

        }

        public override string GetLastErrorMessage()
        {
            throw new NotImplementedException();
        }
    }


    public class cLiamNtfsShare : cLiamDataAreaBase
    {
        public new readonly cLiamProviderNtfs Provider = null;

        private readonly cNtfsResultBase Share = null;

        public cLiamNtfsShare(cLiamProviderNtfs Provider, cNtfsResultBase Share) :
            base(Provider)
        {
            this.Provider = Provider;
            this.Share = Share;

            this.DisplayName = Share.Path.Split('\\').Last();
            this.TechnicalName = Share.Path;
            this.UID = cLiamNtfsFolder.GetUniqueDataAreaID(Share.Path);
            this.Level = Share.Level;
            this.DataType = eLiamDataAreaTypes.NtfsShare;
            this.SupportsOwners = false;
            this.SupportsPermissions = false;
        }

        internal async Task<List<cLiamDataAreaBase>> getFolders()
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);
                var RetVal = new List<cLiamDataAreaBase>(0);
                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override async Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);
                var RetVal = new List<cLiamDataAreaBase>();
                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

    }
    public class cLiamAdGroup : cLiamDataAreaBase
    {
        public new readonly cLiamProviderNtfs Provider = null;
        public readonly string dn = null;
        public readonly string scope = null;
        public override Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1)
        {
            throw new NotImplementedException();
        }
        public cLiamAdGroup(cLiamProviderNtfs Provider, cSecurityGroupResult secGroup) : base(Provider)
        {
            this.UID = secGroup.ID;
            this.TechnicalName = secGroup.DisplayName;
            this.Provider = Provider;
            this.dn = secGroup.Path;
            this.scope = secGroup.Scope.ToString();
        }
    }
    public class cLiamNtfsFolder : cLiamDataAreaBase
    {
        public new readonly cLiamProviderNtfs Provider = null;
        public readonly cLiamNtfsShare Share = null;
        public readonly cLiamNtfsFolder NtfsRootFolder = null;
        public string OwnerGroupIdentifier = "S-1-0-0";
        public string WriteGroupIdentifier = "S-1-0-0";
        public string ReadGroupIdentifier = "S-1-0-0";
        public string TraverseGroupIdentifier = "S-1-0-0";
        public cLiamNtfsFolder(cLiamProviderNtfs Provider, cLiamNtfsShare share, cLiamNtfsFolder ntfsRootFolder, cNtfsResultFolder NtfsFolder) : base(Provider)
        {
            var ntfsParent = NtfsFolder.Parent;
            this.Provider = Provider;
            this.NtfsRootFolder = ntfsRootFolder;
            this.Share = share;
            this.TechnicalName =  NtfsFolder.Path;
            this.UID =GetUniqueDataAreaID(NtfsFolder.Path);
            this.DisplayName = new DirectoryInfo(NtfsFolder.Path).Name;
            this.Level = NtfsFolder.Level;
            this.DataType = eLiamDataAreaTypes.NtfsFolder;
            this.SupportsPermissions = true;
            this.CreatedDate = NtfsFolder.CreatedDate;
            if (ntfsParent != null)
            {
                this.ParentUID = GetUniqueDataAreaID(ntfsParent.Path);
            }
            else if (this.Level == 1)
            {
                this.ParentUID = GetUniqueDataAreaID(this.Provider.RootPath);
            }
            assignPermissionGroups(Provider, NtfsFolder);
        }
        public override async Task<List<cLiamUserInfo>> GetOwnersAsync()
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                return await GetMembersAsync(true);
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private async Task<List<cLiamUserInfo>> GetMembersAsync(bool owners)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var AD = this.Provider?.activeDirectoryBase;
                if (AD == null)
                {
                    LogEntry($"Could not get ad class from Provider for folder '{this.TechnicalName}'", LogLevels.Warning);
                    return null;
                }

                cADCollectionBase lstMembers;
                this.OwnerGroupIdentifier = this.OwnerRef ?? this.OwnerGroupIdentifier;
                if (owners && !String.IsNullOrEmpty(this.OwnerGroupIdentifier))
                    lstMembers = await AD.GetMembersAsync(this.OwnerGroupIdentifier);
                else
                    lstMembers = null;
                if (lstMembers == null)
                {
                    LogEntry($"Could not get owner list for folder '{this.TechnicalName}'", LogLevels.Warning);
                    return null;
                }

                var RetVal = new List<cLiamUserInfo>(lstMembers.Count);
                LogEntry($"Owners for folder found: {lstMembers.Count}", LogLevels.Debug);
                foreach (var MemberEntry in lstMembers.Values)
                {
                    var User = new cLiamUserInfo()
                    {
                        DisplayName = MemberEntry.DisplayName,
                        UserPrincipalName = (MemberEntry as cADUserResult).UserPrincipalName,
                        SID = MemberEntry.ID
                    };
                    RetVal.Add(User);
                }

                return RetVal;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private async void assignPermissionGroups(cLiamProviderNtfs Provider, cNtfsResultFolder NtfsFolder)
        {
            var ACLs = Provider.activeDirectoryBase.GetAccessControlList(NtfsFolder.Path);
            var ownerNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal));
            var writeNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal));
            var readNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGP && i.Scope == eLiamAccessRoleScopes.Global || Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP && i.Scope == eLiamAccessRoleScopes.DomainLocal));
            var traverseNamingConvention = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Traverse);
            foreach (FileSystemAccessRule rule in ACLs)
            {
                //skip ACLs for user "everyone"
                if (rule.IdentityReference.Value == "S-1-1-0")
                    continue;
                GroupPrincipal grp = GroupPrincipal.FindByIdentity(Provider.activeDirectoryBase.adContext, IdentityType.Sid, rule.IdentityReference.Value);

                if (grp != null)
                {
                    DefaultLogger.LogEntry(LogLevels.Debug, $"Try matching: {grp.Name}");
                    if (Regex.IsMatch(grp.SamAccountName, ownerNamingConvention.Wildcard, RegexOptions.IgnoreCase))
                    {
                        this.OwnerGroupIdentifier = rule.IdentityReference.Value;
                        if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
                        {
                            var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
                            var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
                            var ownerNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Owner && i.Scope == eLiamAccessRoleScopes.Global);

                            foreach (var memberItem in res)
                            {
                                var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
                                if (Regex.IsMatch( SecurityGroup.TechnicalName,ownerNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
                                    this.OwnerGroupIdentifier = SecurityGroup.UID;
                            }
                        }
                    }
                    else if (Regex.IsMatch(grp.SamAccountName, writeNamingConvention.Wildcard, RegexOptions.IgnoreCase))
                    {
                        WriteGroupIdentifier = rule.IdentityReference.Value;
                        if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
                        {
                            var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
                            var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
                            var writeNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Write && i.Scope == eLiamAccessRoleScopes.Global);

                            foreach (var memberItem in res)
                            {
                                var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
                                if (Regex.IsMatch(SecurityGroup.TechnicalName, writeNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
                                    this.WriteGroupIdentifier = SecurityGroup.UID;
                            }
                        }
                    }
                    else if (Regex.IsMatch(grp.SamAccountName, readNamingConvention.Wildcard, RegexOptions.IgnoreCase))
                    {
                        ReadGroupIdentifier = rule.IdentityReference.Value;
                        if (Provider.GroupStrategy == eLiamGroupStrategies.Ntfs_AGDLP)
                        {
                            var ldapFilter = String.Format("memberOf={0}", grp.DistinguishedName);
                            var res = await Provider.activeDirectoryBase.RequestSecurityGroupsListAsync(ldapFilter);
                            var readNamingConventionGlobal = Provider.NamingConventions.First(i => i.AccessRole == eLiamAccessRoles.Read && i.Scope == eLiamAccessRoleScopes.Global);

                            foreach (var memberItem in res)
                            {
                                var SecurityGroup = new cLiamAdGroup(this.Provider, (cSecurityGroupResult)memberItem.Value);
                                if (Regex.IsMatch(SecurityGroup.TechnicalName, readNamingConventionGlobal.Wildcard, RegexOptions.IgnoreCase))
                                    this.ReadGroupIdentifier = SecurityGroup.UID;
                            }
                        }
                    }
                    else if (Regex.IsMatch(grp.SamAccountName, traverseNamingConvention.Wildcard, RegexOptions.IgnoreCase))
                    {
                        TraverseGroupIdentifier = rule.IdentityReference.Value;
                    }
                    else
                        DefaultLogger.LogEntry(LogLevels.Debug, $"No match for: {grp.Name}");
                }

            }
        }

        public static string GetUniqueDataAreaID(string fullPath)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            try
            {
                var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
                var utf8 = new System.Text.UTF8Encoding();
                var hash = BitConverter.ToString(md5.ComputeHash(utf8.GetBytes(fullPath)));
                hash = hash.ToLower().Replace("-", "");
                return hash;
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
        }
        public string GetUniqueDataAreaID()
        {
            return GetUniqueDataAreaID(this.TechnicalName);
        }
        public override async Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = 1)
        {
            //TODO implement getChildrenAsync
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                await Task.Delay(0);
                var DataAreas = new List<cLiamDataAreaBase>();

                return DataAreas;
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

    }
}
