using C4IT.FASD.Base;
using C4IT.Logging;
using C4IT.Security;
using C4IT.XML;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml;
using static C4IT.Logging.cLogManager;

namespace C4IT.DataHistoryProvider
{
    public class cDataHistoryConfigActiveDirectory : IConfigNodeValidation
    {
        public bool IsValid { get; private set; } = false;

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

        public cDataHistoryScanTiming ScanTiming { get; private set; } = null;


        public Dictionary<string, cDataHistoryAdDomain> Domains { get; private set; } = new Dictionary<string, cDataHistoryAdDomain>();
        public Dictionary<string, cDataHistoryAdScan> Scans { get; private set; } = new Dictionary<string, cDataHistoryAdScan>();

        public cDataHistoryConfigActiveDirectory(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                var XNode2 = XNode.SelectSingleNode("Active-Directory");
                if (!(XNode2 is XmlElement XRoot))
                {
                    Parser.AddMessage(XNode, "Could not find a valid <Active-Directory> element.");
                    return;
                }

                Parser.EnterElement("Active-Directory");
                try
                {
                    ScanPhoneNumbers = cXmlParser.GetBoolFromXmlAttribute(XNode2, "ScanPhoneNumbers");

                    ScanTiming = new cDataHistoryScanTiming(XRoot, TimeSpan.FromHours(24), TimeSpan.FromMinutes(10), Parser);

                    Domains = cDataHistoryAdDomain.LoadFromXml(XRoot, Credentials, Parser);
                    if (Domains == null || Domains.Count == 0)
                    {
                        Parser.AddMessage(XNode, "The element <Active-Directory> has no valid <AD-Domain> entries.");
                        return;
                    }

                    Scans = cDataHistoryAdScan.LoadFromXml(XRoot, this, Parser);
                    if (Scans == null || Scans.Count == 0)
                    {
                        Parser.AddMessage(XNode, "The element <Active-Directory> has no valid <AD-Scan> entries.");
                        return;
                    }

                    IsValid = true;
                }
                finally
                {
                    Parser.LeaveElement("Active-Directory");
                }

            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }
    }


    public class cDataHistoryAdScan : cConfigNodeNamed
    {
        public enumFasdInformationClass Type { get; private set; }

        public List<cDataHistoryAdScanNode> ScanNodes { get; private set; } = new List<cDataHistoryAdScanNode>();

        internal cDataHistoryAdScan(XmlElement XNode, cDataHistoryConfigActiveDirectory AdConfig, cXmlParser Parser) :
            base(XNode, Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (!IsValid)
                    return;

                IsValid = false;

                Type = (enumFasdInformationClass)cXmlParser.GetEnumFromAttribute<enumFasdInformationClass>(XNode, "Type", enumFasdInformationClass.Unknown);
                if (Type != enumFasdInformationClass.User && Type != enumFasdInformationClass.Computer)
                {
                    Parser.AddInvalidAttribute(XNode, Name, "AD-Type");
                    return;
                }

                ScanNodes = cDataHistoryAdScanNode.LoadFromXml(XNode, AdConfig, this, Parser);

                if (ScanNodes == null || ScanNodes.Count == 0)
                {
                    Parser.AddMessage(XNode, "The <AD-Scan> element has no valid <AD-Scan-Node> entries.");
                    return;
                }

                IsValid = true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }

        internal static Dictionary<string, cDataHistoryAdScan> LoadFromXml(XmlElement XNode, cDataHistoryConfigActiveDirectory AdConfig, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            var RetVal = new Dictionary<string, cDataHistoryAdScan>();

            try
            {
                var XRoot = XNode.SelectSingleNode("AD-Scans");
                if (XRoot == null)
                {
                    Parser.AddMessage(XNode, $"No <AD-Scans> element found within element <{XNode.Name}>.");
                    return null;
                }

                Parser.EnterElement("AD-Scans");
                try
                {
                    var XList = XRoot.SelectNodes("AD-Scan");
                    if (XList != null && XList.Count > 0)
                    {
                        Parser.EnterElement("AD-Scan");
                        try
                        {
                            foreach (var Entry in XList)
                            {
                                if (!(Entry is XmlElement XEntry))
                                    continue;

                                var Node = new cDataHistoryAdScan(XEntry, AdConfig, Parser);
                                if (Node.IsValid)
                                {
                                    if (RetVal.ContainsKey(Node.Name))
                                    {
                                        Parser.AddDuplicateName(XEntry, Node.Name, LogLevels.Warning);
                                    }
                                    else
                                        RetVal.Add(Node.Name, Node);
                                }
                                Parser.SelectElementNext();
                            }
                        }
                        finally
                        {
                            Parser.LeaveElement("AD-Scan");
                        }
                    }
                }
                finally
                {
                    Parser.LeaveElement("AD-Scans");
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return RetVal;
        }
    }


    public class cDataHistoryAdScanNode : IConfigNodeValidation
    {
        public enum enumFilterPropery { unknown = 0, commonName, distinguishedName, samAccountName }

        public bool IsValid { get; private set; } = false;
        public cDataHistoryAdDomain AdDomain { get; private set; }
        public string Path { get; private set; }
        public string LdapFilter { get; private set; }
        public enumFilterPropery FilterProperty { get; private set; } = enumFilterPropery.unknown;
        public bool WildcardFilterNot { get; private set; } = false;
        public Regex WildcardFilter { get; private set; }
        public Regex RegExFilter { get; private set; }

        public cDataHistoryAdScan parentScan { get; private set; }

        internal cDataHistoryAdScanNode(XmlElement XNode, cDataHistoryConfigActiveDirectory AdConfig, cDataHistoryAdScan Scan, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                parentScan = Scan;
                var strAdDomain = cXmlParser.GetStringFromXmlAttribute(XNode, "AD-Domain");
                if (!AdConfig.Domains.TryGetValue(strAdDomain, out var AdD))
                {
                    Parser.AddInvalidAttribute(XNode, null, "AD-Domain");
                    return;
                }
                AdDomain = AdD;

                Path = cXmlParser.GetStringFromXmlAttribute(XNode, "Path", "");

                LdapFilter = cXmlParser.GetStringFromXmlAttribute(XNode, "LDAP-Filter");

                FilterProperty = cXmlParser.GetEnumFromAttribute<enumFilterPropery>(XNode, "Filter-Property", enumFilterPropery.unknown);

                var strWildcardFilter = cXmlParser.GetStringFromXmlAttribute(XNode, "Wildcard-Filter");
                if (!string.IsNullOrEmpty(strWildcardFilter))
                {
                    if (strWildcardFilter.StartsWith("!"))
                    {
                        WildcardFilterNot = true;
                        strWildcardFilter.Remove(0, 1);
                    }
                    var _expression = "^" + Regex.Escape(strWildcardFilter)
                        .Replace("\\\\\\?", "??").Replace("\\?", ".").Replace("??", "\\?")
                        .Replace("\\\\\\*", "**").Replace("\\*", ".*").Replace("**", "\\*") + "$";
                    WildcardFilter = new Regex(_expression, RegexOptions.Compiled);
                }

                var strRexExFilter = cXmlParser.GetStringFromXmlAttribute(XNode, "RegEx-Filter");
                if (!string.IsNullOrEmpty(strRexExFilter))
                    RegExFilter = new Regex(strRexExFilter, RegexOptions.Compiled);

                IsValid = true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }

        internal static List<cDataHistoryAdScanNode> LoadFromXml(XmlElement XNode, cDataHistoryConfigActiveDirectory AdConfig, cDataHistoryAdScan Scan, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            var RetVal = new List<cDataHistoryAdScanNode>();
            try
            {
                var XList = XNode.SelectNodes("AD-Scan-Node");
                if (XList != null && XList.Count > 0)
                {
                    Parser.EnterElement("AD-Scan-Node");
                    try
                    {
                        foreach (var Entry in XList)
                        {
                            if (!(Entry is XmlElement XEntry))
                                continue;

                            var Node = new cDataHistoryAdScanNode(XEntry, AdConfig, Scan, Parser);
                            if (Node.IsValid)
                                RetVal.Add(Node);

                            Parser.SelectElementNext();
                        }
                    }
                    finally
                    {
                        Parser.LeaveElement("AD-Scan-Node");
                    }
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return RetVal;
        }
    }

    public class cDataHistoryAdDomain : cConfigNodeNamed
    {
        public string FQDN { get; private set; } = null;
        public cCredential Credential { get; private set; } = null;

        public List<cDataHistoryAdServer> Servers { get; private set; } = new List<cDataHistoryAdServer>();

        internal cDataHistoryAdDomain(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser) :
            base(XNode, Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                if (!IsValid)
                    return;

                IsValid = false;

                FQDN = cXmlParser.GetStringFromXmlAttribute(XNode, "FQDN");
                if (string.IsNullOrWhiteSpace(FQDN))
                {
                    Parser.AddInvalidAttribute(XNode, null, "FQDN");
                    return;
                }



                var strCreds = cXmlParser.GetStringFromXmlAttribute(XNode, "Credential");
                if (!string.IsNullOrWhiteSpace(strCreds))
                {
                    if (Credentials.TryGetValue(strCreds, out var Cred))
                        Credential = Cred;
                }

                if (Credential == null)
                {
                    Parser.AddInvalidAttribute(XNode, Name, "Credential");
                    return;
                }

                Servers = cDataHistoryAdServer.LoadFromXml(XNode, Parser);
                if (Servers?.Count == 0)
                {
                    Parser.AddInvalidAttribute(XNode, Name, "No valid <AD-Server> element could be found.");
                    return;
                }

                IsValid = true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }

        internal static Dictionary<string, cDataHistoryAdDomain> LoadFromXml(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            var RetVal = new Dictionary<string, cDataHistoryAdDomain>();

            try
            {
                var XRoot = XNode.SelectSingleNode("AD-Domains");
                if (XRoot == null)
                {
                    Parser.AddMessage(XNode, $"No <AD-Domains> element found within element <{XNode.Name}>.");
                    return null;
                }

                Parser.EnterElement("AD-Domains");
                try
                {
                    var XList = XRoot.SelectNodes("AD-Domain");
                    if (XList != null && XList.Count > 0)
                    {
                        Parser.EnterElement("AD-Domain");
                        try
                        {
                            foreach (var Entry in XList)
                            {
                                if (!(Entry is XmlElement XEntry))
                                    continue;

                                var Node = new cDataHistoryAdDomain(XEntry, Credentials, Parser);
                                if (Node.IsValid)
                                {
                                    if (RetVal.ContainsKey(Node.Name))
                                    {
                                        Parser.AddDuplicateName(XEntry, Node.Name, LogLevels.Warning);
                                    }
                                    else
                                        RetVal.Add(Node.Name, Node);
                                }
                                Parser.SelectElementNext();
                            }
                        }
                        finally
                        {
                            Parser.LeaveElement("AD-Domain");
                        }
                    }
                }
                finally
                {
                    Parser.LeaveElement("AD-Domains");
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return RetVal;
        }
    }

    public class cDataHistoryAdServer : IConfigNodeValidation
    {
        public bool IsValid { get; private set; } = false;
        public string FQDN { get; private set; }
        public int Port { get; private set; }
        public bool useSSL { get; private set; }

        internal cDataHistoryAdServer(XmlElement XNode, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                FQDN = cXmlParser.GetStringFromXmlAttribute(XNode, "FQDN");
                if (string.IsNullOrWhiteSpace(FQDN))
                {
                    Parser.AddInvalidAttribute(XNode, null, "FQDN");
                    return;
                }

                Port = cXmlParser.GetIntegerFromXmlAttribute(XNode, "Port");
                useSSL = cXmlParser.GetBoolFromXmlAttribute(XNode, "UseSSL");

                IsValid = true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }

        internal static List<cDataHistoryAdServer> LoadFromXml(XmlElement XNode, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            var RetVal = new List<cDataHistoryAdServer>();
            try
            {
                var XList = XNode.SelectNodes("AD-Server");
                if (XList != null && XList.Count > 0)
                {
                    Parser.EnterElement("AD-Server");
                    try
                    {
                        foreach (var Entry in XList)
                        {
                            if (!(Entry is XmlElement XEntry))
                                continue;

                            var Node = new cDataHistoryAdServer(XEntry, Parser);
                            if (Node.IsValid)
                                RetVal.Add(Node);

                            Parser.SelectElementNext();
                        }
                    }
                    finally
                    {
                        Parser.LeaveElement("AD-Server");
                    }
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return RetVal;
        }
    }

    public class cDataHistoryAzureTenant : IConfigNodeValidation
    {
        public bool IsValid { get; private set; } = false;

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

        public Guid TenantID { get; private set; } = Guid.Empty;

        public cCredential Credential { get; private set; } = null;
        public bool ScanIntuneDevices { get; private set; } = false;
        public bool WithMobileDevices { get; private set; } = false;

        public string ScanFilterUser { get; private set; } = "";
        public bool ConsistencyLevelEventualUserFilter { get; private set; } = false;

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

        internal cDataHistoryAzureTenant(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
            try
            {
                Domain = cXmlParser.GetStringFromXmlAttribute(XNode, "Domain");
                if (string.IsNullOrWhiteSpace(Domain))
                {
                    Parser.AddInvalidAttribute(XNode, null, "Domain");
                    return;
                }
                TenantID = cXmlParser.GetGuidFromXmlAttribute(XNode, "TenantID");
                if (TenantID == Guid.Empty)
                {
                    Parser.AddInvalidAttribute(XNode, Domain, "TenantID");
                    return;
                }

                var strCreds = cXmlParser.GetStringFromXmlAttribute(XNode, "Credential");
                if (!string.IsNullOrWhiteSpace(strCreds))
                {
                    if (Credentials.TryGetValue(strCreds, out var Cred))
                        Credential = Cred;
                }

                if (Credential == null)
                {
                    Parser.AddInvalidAttribute(XNode, Domain, "Credential");
                    return;
                }
                ScanIntuneDevices = cXmlParser.GetBoolFromXmlAttribute(XNode, "ScanIntuneDevices");
                WithMobileDevices = cXmlParser.GetBoolFromXmlAttribute(XNode, "WithMobileDevices");

                var XFilterNode = XNode.SelectSingleNode("Azure-Filter-User");
                if (XFilterNode is XmlElement XFilter)
                {
                    Parser.EnterElement("Azure-Filter-User");
                    try
                    {
                        ScanFilterUser = XFilter.SelectSingleNode("text()")?.Value;
                        ConsistencyLevelEventualUserFilter = cXmlParser.GetBoolFromXmlAttribute(XFilter, "UseConsistencyLevelEventual");
                        UseBetaUserFilter = cXmlParser.GetBoolFromXmlAttribute(XFilter, "UseBetaEnpoint");
                    }
                    finally
                    {
                        Parser.LeaveElement("Azure-Filter-User");
                    }
                }


                IsValid = true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }
        }

        internal static Dictionary<string, cDataHistoryAzureTenant> LoadFromXml(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser)
        {
            MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }

            var RetVal = new Dictionary<string, cDataHistoryAzureTenant>();

            try
            {
                var XRoot = XNode.SelectSingleNode("Azure-AD");
                if (XRoot == null)
                {
                    return null;
                }

                Parser.EnterElement("Azure-AD");
                try
                {
                    var XList = XRoot.SelectNodes("Azure-Tenant");
                    if (XList != null && XList.Count > 0)
                    {
                        Parser.EnterElement("Azure-Tenant");
                        try
                        {
                            foreach (var Entry in XList)
                            {
                                if (!(Entry is XmlElement XEntry))
                                    continue;

                                var Node = new cDataHistoryAzureTenant(XEntry, Credentials, Parser);
                                if (Node.IsValid)
                                {
                                    if (RetVal.ContainsKey(Node.Domain))
                                    {
                                        Parser.AddDuplicateName(XEntry, Node.Domain, LogLevels.Warning);
                                    }
                                    else
                                        RetVal.Add(Node.Domain, Node);
                                }
                                Parser.SelectElementNext();
                            }
                        }
                        finally
                        {
                            Parser.LeaveElement("Azure-Tenant");
                        }
                    }
                }
                finally
                {
                    Parser.LeaveElement("Azure-AD");
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                if (CM != null) LogMethodEnd(CM);
            }

            return RetVal;
        }

    }

    public class cWildcardPattern
    {
        private readonly string _expression;
        private readonly Regex _regex;

        public cWildcardPattern(string pattern)
        {
            if (string.IsNullOrEmpty(pattern)) throw new ArgumentNullException(nameof(pattern));

            _expression = "^" + Regex.Escape(pattern)
                .Replace("\\\\\\?", "??").Replace("\\?", ".").Replace("??", "\\?")
                .Replace("\\\\\\*", "**").Replace("\\*", ".*").Replace("**", "\\*") + "$";
            _regex = new Regex(_expression, RegexOptions.Compiled);
        }

        public bool IsMatch(string value)
        {
            return _regex.IsMatch(value);
        }
    }


}
