using C4IT_IAM_Engine;
using C4IT_IAM;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.IO;
using System.Linq;
using System.Net;
using System.Security;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using C4IT.Logging;

using static C4IT.Logging.cLogManager;
using System.Reflection;

namespace C4IT_IAM_GET
{
    public enum AccessRights { Owner = 2032127, Write = 1245631, Read = 1179817 }
    public enum PermissionGroupStrategy
    {
        AGP,
        AGDLP
    }
    public class DataArea_FileSystem
    {
        public const string constApplicationDataPath = "%ProgramData%\\Consulting4IT GmbH\\LIAM";

        public string domainName;
        public string DomainIPAddress;
        public string username;
        private SecureString password;
        private cNetworkConnection Connection;

        public string groupWildCard_Owner;
        public string groupWildCard_Read;
        public string groupWildCard_Write;
        public string groupWildCard_Traverse;

        public string groupGOwnerWildcard = string.Empty;
        public string groupGWriteWildcard = string.Empty;
        public string groupGReadWildcard = string.Empty;
        public string groupTraverseWildcard = string.Empty;
        public string groupDLOwnerWildcard = string.Empty;
        public string groupDLWriteWildcard = string.Empty;
        public string groupDLReadWildcard = string.Empty;

        public IDictionary<string, string> groupCustomTags;
        public string groupDLTag;
        public string groupGTag;
        public string groupOwnerTag;
        public string groupReadTag;
        public string groupWriteTag;
        public string groupTraverseTag;
        public string groupPrefix;

        public PermissionGroupStrategy groupPermissionStrategy;
        public string groupLDAPFilter;
        private string exportFolder;
        private PrincipalContext ctx;
        public string ConfigID;
        public int maxDepth = 1;
        public string groupLDAPPath;
        public bool console;
        private string startDir;

        public string SecGroupXML;
        public string DataAreasXML;

        public SecureString Password { get => password; set => password = value; }
        public string StartDir { get => startDir; set => startDir = value; }
        public string ExportFolder { get => exportFolder; set => exportFolder = value; }

        private ResultToken checkRequiredVariables()
        {
            ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
            resultToken.resultErrorId = 0;
            if (String.IsNullOrEmpty(ConfigID))
            {

                resultToken.resultErrorId = 20001;
                resultToken.resultMessage = "Keine Config ID angegeben";
                return resultToken;
            }
            if (String.IsNullOrEmpty(StartDir))
            {

                resultToken.resultErrorId = 20007;
                resultToken.resultMessage = "Kein Startverzeichnis gewählt ";
                return resultToken;
            }
            if (string.IsNullOrEmpty(username) | String.IsNullOrEmpty(new NetworkCredential("", password).Password) | String.IsNullOrEmpty(domainName))
            {

                resultToken.resultErrorId = 20002;
                resultToken.resultMessage = "Fehlende Anmeldeinformationen";
                return resultToken;
            }
            if (String.IsNullOrEmpty(groupWildCard_Owner) | String.IsNullOrEmpty(groupWildCard_Read) | String.IsNullOrEmpty(groupWildCard_Write))
            {

                resultToken.resultErrorId = 20004;
                resultToken.resultMessage = "Keine AD Gruppen Wildcards";
                return resultToken;
            }
            if (String.IsNullOrEmpty(groupLDAPFilter))
            {
                resultToken.resultErrorId = 20005;
                resultToken.resultMessage = "Kein LDAP Gruppenfilter angegeben";
                return resultToken;
            }
            if (String.IsNullOrEmpty(groupLDAPPath))
            {
                resultToken.resultErrorId = 20006;
                resultToken.resultMessage = "Kein LDAP Pfad angegeben";
                return resultToken;
            }

            return resultToken;
        }


        public DataArea_FileSystem()
        {
            var logDirectory = Environment.ExpandEnvironmentVariables(constApplicationDataPath);
            Helper.CreatePathWithWriteAccess(logDirectory);
            var LogPath = Path.Combine(logDirectory, "Logs");
            cLogManagerFile.CreateInstance(Path.Combine(LogPath, "LIAM.log"));
            DefaultLogger.LogEntry(LogLevels.Info, "=================================================");
            DefaultLogger.LogEntry(LogLevels.Info, $"LIAM engine v{Assembly.GetExecutingAssembly().GetName().Version} started");

        }

        private void FillMatchingGroups()
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());

            try
            {
                groupWildCard_Owner = (groupPermissionStrategy == PermissionGroupStrategy.AGP ? groupGOwnerWildcard.Replace("{{PREFIX}}", groupPrefix).Replace("{{SCOPETAG}}", groupGTag) : groupDLOwnerWildcard.Replace("{{PREFIX}}", groupPrefix).Replace("{{SCOPETAG}}", groupDLTag)).Replace("{{GROUPTYPEPOSTFIX}}", groupOwnerTag).ReplaceTags(groupCustomTags);
                groupWildCard_Write = (groupPermissionStrategy == PermissionGroupStrategy.AGP ? groupGWriteWildcard.Replace("{{PREFIX}}", groupPrefix).Replace("{{SCOPETAG}}", groupGTag) : groupDLWriteWildcard.Replace("{{PREFIX}}", groupPrefix).Replace("{{SCOPETAG}}", groupDLTag)).Replace("{{GROUPTYPEPOSTFIX}}", groupWriteTag).ReplaceTags(groupCustomTags);
                groupWildCard_Read = (groupPermissionStrategy == PermissionGroupStrategy.AGP ? groupGReadWildcard.Replace("{{PREFIX}}", groupPrefix).Replace("{{SCOPETAG}}", groupGTag) : groupDLReadWildcard.Replace("{{PREFIX}}", groupPrefix).Replace("{{SCOPETAG}}", groupDLTag)).Replace("{{GROUPTYPEPOSTFIX}}", groupReadTag).ReplaceTags(groupCustomTags);
                groupWildCard_Traverse = (groupPermissionStrategy == PermissionGroupStrategy.AGP ? groupTraverseWildcard.Replace("{{PREFIX}}", groupPrefix).Replace("{{SCOPETAG}}", groupGTag) : groupTraverseWildcard.Replace("{{PREFIX}}", groupPrefix).Replace("{{SCOPETAG}}", groupDLTag)).Replace("{{GROUPTYPEPOSTFIX}}", groupTraverseTag).ReplaceTags(groupCustomTags);
                DefaultLogger.LogEntry(LogLevels.Debug, $"Owner regex: {groupWildCard_Owner}");
                DefaultLogger.LogEntry(LogLevels.Debug, $"Write regex: {groupWildCard_Write}");
                DefaultLogger.LogEntry(LogLevels.Debug, $"Read regex: {groupWildCard_Read}");
                DefaultLogger.LogEntry(LogLevels.Debug, $"Traverse regex: {groupWildCard_Traverse}");
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
        }

        public ResultToken BuildRootStructureWrapper()
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            FillMatchingGroups();
            ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
            resultToken.resultErrorId = 0;
            try
            {
                if (checkRequiredVariables().resultErrorId == 0)
                {
                    if (Connection != null)
                        Connection.Dispose();


                    DefaultLogger.LogEntry(LogLevels.Debug, $"Establishing connection to {startDir}, User: {username}, Password: {Helper.MaskAllButLastAndFirst(new NetworkCredential("", password).Password)}");
                    using (Connection = new cNetworkConnection(startDir, username, new NetworkCredential("", password).Password))
                    {
                        DefaultLogger.LogEntry(LogLevels.Debug, $"Creating Domain Context: {domainName}");
                        ctx = new PrincipalContext(ContextType.Domain, domainName, username, new NetworkCredential("", password).Password);
                        try
                        {
                            BuildRootStructure(startDir);
                        }
                        catch (Exception e)
                        {

                            resultToken.resultErrorId = 20200;
                            resultToken.resultMessage = "Fehler bei der Analyse der Verzeichnisstruktur \n " + e.Message;
                        }
                    }
                }
                else
                {
                    return checkRequiredVariables();
                }
            }
            catch (Exception E)
            {
                DefaultLogger.LogException(E);
                resultToken.resultErrorId = 20000;
                resultToken.resultMessage = $"Fehler beim Ermitteln der Dateistruktur \n{E.Message}";
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
            return resultToken;
        }

        public void BuildRootStructure(string currentDirectory, int currentDepth = 0, DataArea dA = null)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            var firstRun = false;
            try
            {
                string[] folders = Directory.GetDirectories(currentDirectory);
                if (dA == null)
                {
                    firstRun = true;
                    dA = new DataArea();
                    dA.rootUID = DataArea.GetUniqueDataAreaID(currentDirectory);
                    DirectoryInfo dADir = new DirectoryInfo(currentDirectory);
                    IAM_Folder iamFolder = new IAM_Folder()
                    {
                        Name = dADir.Name,
                        technicalName = dADir.FullName,
                        UID = DataArea.GetUniqueDataAreaID(dADir.FullName),
                        Parent = string.Empty,
                        ParentUID = string.Empty,
                        CreatedDate = dADir.CreationTimeUtc.ToString("s"),
                        Level = 0,
                        targetType = Convert.ToInt32(IAM_TargetType.FileSystem),
                        configurationID = ConfigID
                    };
                    dA.IAM_Folders.Add(iamFolder);
                }
                currentDepth++;
                DefaultLogger.LogEntry(LogLevels.Debug, $"Current folder: {currentDirectory}, Level: {currentDepth}, Subdirectories: {folders.Length}");
                foreach (string folder in folders)
                {
                    DirectoryInfo dADir = new DirectoryInfo(folder);
                    IAM_Folder iamFolder = new IAM_Folder()
                    {
                        Name = dADir.Name,
                        technicalName = dADir.FullName,
                        UID = DataArea.GetUniqueDataAreaID(dADir.FullName),
                        Parent = dADir.Parent.FullName,
                        ParentUID = DataArea.GetUniqueDataAreaID(dADir.Parent.FullName),
                        CreatedDate = dADir.CreationTimeUtc.ToString("s"),
                        Level = currentDepth,
                        targetType = Convert.ToInt32(IAM_TargetType.FileSystem),
                        configurationID = ConfigID

                    };

                    var dAACL = dADir.GetAccessControl();
                    var dAAR = dAACL.GetAccessRules(true, false, typeof(System.Security.Principal.SecurityIdentifier));
                    var k = "";
                    DefaultLogger.LogEntry(LogLevels.Debug, $"Current folder: {folder}, Access Control Rules: {dAAR.Count}");
                    foreach (FileSystemAccessRule rule in dAAR)
                    {
                        //skip ACLs for user "everyone"
                        if (rule.IdentityReference.Value == "S-1-1-0")
                            continue;
                        
                        k += rule.FileSystemRights + " -  ";
                        GroupPrincipal grp = GroupPrincipal.FindByIdentity(ctx, IdentityType.Sid, rule.IdentityReference.Value);

                        if (grp != null)
                        {

                            if (Regex.IsMatch(grp.SamAccountName, groupWildCard_Owner, RegexOptions.IgnoreCase))
                            {
                                iamFolder.Owner = rule.IdentityReference.Value;
                                DefaultLogger.LogEntry(LogLevels.Debug, $"ACL on folder: {folder}, matching Owner: {grp.Name}");
                            }
                            else if (Regex.IsMatch(grp.SamAccountName, groupWildCard_Write, RegexOptions.IgnoreCase))
                            {
                                DefaultLogger.LogEntry(LogLevels.Debug, $"ACL on folder: {folder}, matching Write: {grp.Name}");
                                iamFolder.Write = rule.IdentityReference.Value;
                            }
                            else if (Regex.IsMatch(grp.SamAccountName, groupWildCard_Read, RegexOptions.IgnoreCase))
                            {
                                DefaultLogger.LogEntry(LogLevels.Debug, $"ACL on folder: {folder}, matching Read: {grp.Name}");
                                iamFolder.Read = rule.IdentityReference.Value;
                            }
                            else if (Regex.IsMatch(grp.SamAccountName, groupWildCard_Traverse, RegexOptions.IgnoreCase))
                            {
                                DefaultLogger.LogEntry(LogLevels.Debug, $"ACL on folder: {folder}, matching Traverse: {grp.Name}");
                                iamFolder.Traverse = rule.IdentityReference.Value;
                            }
                            else
                                DefaultLogger.LogEntry(LogLevels.Debug, $"No match for: {grp.Name}");
                        }
                    }
                    dA.IAM_Folders.Add(iamFolder);
                    if (currentDepth < maxDepth)
                    {
                        BuildRootStructure(folder, currentDepth, dA);
                    }
                    DefaultLogger.LogEntry(LogLevels.Debug, $"Max level ({ maxDepth }) reached");

                }
                if (firstRun)
                    SetDataAreaXML(dA);
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw E;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }

        }


        public ResultToken GetRelevantADGroupsWrapper()
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            FillMatchingGroups();
            ResultToken resultToken = new ResultToken(System.Reflection.MethodBase.GetCurrentMethod().ToString());
            resultToken.resultErrorId = 0;
            try
            {
                if (checkRequiredVariables().resultErrorId == 0)
                {
                    GetRelevantADGroups();
                }
                else
                {
                    return checkRequiredVariables();
                }
            }
            catch (Exception E)
            {
                resultToken.resultErrorId = 20000;
                resultToken.resultMessage = "Fehler Ermitteln der AD-Gruppen \n" + E.Message;
                cLogManager.DefaultLogger.LogException(E);
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
            return resultToken;
        }
        public void GetRelevantADGroups()
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            try
            {
                SecurityGroups secGroups = new SecurityGroups();
                var ldapPath = $"LDAP://{domainName}/{groupLDAPPath}";
                DefaultLogger.LogEntry(LogLevels.Debug, $"Connecting to LDAP using: Domain: {domainName}, LDAPPath: {ldapPath}, User: {username}, Password: {Helper.MaskAllButLastAndFirst(new NetworkCredential("", password).Password)}");
                ctx = new PrincipalContext(ContextType.Domain, domainName, username, new NetworkCredential("", password).Password);
                DefaultLogger.LogEntry(LogLevels.Debug, $"1 " + ctx.ConnectedServer);
                DefaultLogger.LogEntry(LogLevels.Debug, $"2 " + ctx.UserName);
                DefaultLogger.LogEntry(LogLevels.Debug, $"3 " + ctx.ValidateCredentials(username, new NetworkCredential("", password).Password));
               // DefaultLogger.LogEntry(LogLevels.Debug, $"3.5 " + ctx.ToString());
                DirectoryEntry entry = new DirectoryEntry
                {
                    Path = ldapPath,
                    Username = username,
                    Password = new NetworkCredential(username, password).Password,
                    AuthenticationType = AuthenticationTypes.Secure | AuthenticationTypes.Sealing
                };
                DefaultLogger.LogEntry(LogLevels.Debug, $"4 " + entry.NativeGuid);
                DefaultLogger.LogEntry(LogLevels.Debug, $"5 " + entry.Path);
                var tmpFilter = "(&(" + groupLDAPFilter + ")(objectClass=group))";
                DefaultLogger.LogEntry(LogLevels.Debug, $"6 " + tmpFilter);
                //DefaultLogger.LogEntry(LogLevels.Debug, $"6.5 " + entry.ToString());
                DirectorySearcher dSearch = new DirectorySearcher(entry)
                {
                    Filter = tmpFilter
                };
                dSearch.PageSize = 100000;
                DefaultLogger.LogEntry(LogLevels.Debug, $"7 " + dSearch.Filter);
                SearchResultCollection sr = dSearch.FindAll();
                DefaultLogger.LogEntry(LogLevels.Debug, $"8 " + sr.Count);
                if (sr.Count > 0)
                {
                    foreach (SearchResult k in sr)
                    {
                        IAM_SecurityGroup sec = new IAM_SecurityGroup();

                        sec.Name = k.Properties["Name"][0].ToString();
                        setGroupType(sec);

                        SecurityIdentifier sid = new SecurityIdentifier(k.Properties["objectSid"][0] as byte[], 0);
                        sec.UID = sid.Value;
                        sec.technicalName = k.Properties["distinguishedname"][0].ToString();
                        sec.targetTyp = Convert.ToInt32(IAM_TargetType.FileSystem);
                        var group = GroupPrincipal.FindByIdentity(ctx, IdentityType.Sid, sid.Value);
                        sec.Scope = (GroupScope)group.GroupScope;

                        var filter = "(&(" + groupLDAPFilter + ")(objectClass=group)(memberOf=" + sec.technicalName + "))";
                        DirectorySearcher iDSearch = new DirectorySearcher(entry)
                        {
                            Filter = filter
                        };
                        SearchResultCollection iSr = iDSearch.FindAll();
                        if (iSr.Count > 0 )
                        {
                            foreach (SearchResult iK in iSr)
                            {
                                IAM_SecurityGroup iSec = new IAM_SecurityGroup();

                                iSec.Name = iK.Properties["Name"][0].ToString();
                                setGroupType(iSec);
                                SecurityIdentifier iSid = new SecurityIdentifier(iK.Properties["objectSid"][0] as byte[], 0);
                                iSec.UID = iSid.Value;
                                iSec.technicalName = iK.Properties["distinguishedname"][0].ToString();
                                iSec.targetTyp = Convert.ToInt32(IAM_TargetType.FileSystem);
                                iSec.Parent = sec.UID;
                                var iGroup = GroupPrincipal.FindByIdentity(ctx, IdentityType.Sid, iSid.Value);
                                iSec.Scope = (GroupScope)iGroup.GroupScope;
                                if (!secGroups.IAM_SecurityGroups.Any(s => s.technicalName.Equals(iSec.technicalName)))
                                {
                                    secGroups.IAM_SecurityGroups.Add(iSec);
                                    DefaultLogger.LogEntry(LogLevels.Debug, $"Found security group: {iSec.technicalName}");
                                }
                            }
                        }
                        
                        else
                            if (!secGroups.IAM_SecurityGroups.Any(s => s.technicalName.Equals(sec.technicalName)))
                            {
                                secGroups.IAM_SecurityGroups.Add(sec);
                                DefaultLogger.LogEntry(LogLevels.Debug, $"Found security group: {sec.technicalName}");
                            }
                    }
                }



                ctx.Dispose();
                entry.Dispose();
                dSearch.Dispose();
                sr.Dispose();

                SetSecGroupXML(secGroups);
                DefaultLogger.LogEntry(LogLevels.Debug, $"{secGroups.IAM_SecurityGroups.Count} AD groups retrieved with following lDAPQuery {groupLDAPFilter}");
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw E;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
            void setGroupType(IAM_SecurityGroup sec)
            {
                if (sec.Name.EndsWith(groupOwnerTag))
                    sec.securityGroupType = SecurityGroupType.Owner;
                else if (sec.Name.EndsWith(groupReadTag))
                    sec.securityGroupType = SecurityGroupType.Read;
                else if (sec.Name.EndsWith(groupWriteTag))
                    sec.securityGroupType = SecurityGroupType.Write;
                else if (sec.Name.EndsWith(groupTraverseTag))
                    sec.securityGroupType = SecurityGroupType.Traverse;
            }
        }

        public void SetDataAreaXML(DataArea dA)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            try
            {
                var xmlserializer = new XmlSerializer(typeof(DataArea));
                using (var sww = new StringWriter())
                {

                    XmlWriterSettings setting = new XmlWriterSettings();
                    setting.OmitXmlDeclaration = true;
                    using (XmlWriter writer = XmlWriter.Create(sww, setting))
                    {
                        xmlserializer.Serialize(writer, dA);
                        XmlDocument xml = new XmlDocument();
                        xml.LoadXml(sww.ToString());

                        DataAreasXML = xml.OuterXml;
                    }
                }
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw E;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }

        }

        public void SetSecGroupXML(SecurityGroups secGroups)
        {
            LogMethodBegin(MethodBase.GetCurrentMethod());
            try
            {
                var xmlserializer = new XmlSerializer(typeof(SecurityGroups));

                using (var sww = new StringWriter())
                {
                    XmlWriterSettings setting = new XmlWriterSettings();
                    setting.OmitXmlDeclaration = true;
                    using (XmlWriter writer = XmlWriter.Create(sww, setting))
                    {

                        xmlserializer.Serialize(writer, secGroups);

                        XmlDocument xml = new XmlDocument();

                        xml.LoadXml(sww.ToString());

                        SecGroupXML = xml.OuterXml;
                    }
                }
            }
            catch (Exception E)
            {
                cLogManager.DefaultLogger.LogException(E);
                throw E;
            }
            finally
            {
                LogMethodEnd(MethodBase.GetCurrentMethod());
            }
        }
    }
}
