using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

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

namespace C4IT.LIAM
{
    public static class LiamExchangeProviderRegistry
    {
        /// <summary>
        /// This registry helps us manage Exchange providers since they can't be registered in the enum
        /// </summary>
        private static Dictionary<Guid, cLiamProviderExchange> _registeredProviders = new Dictionary<Guid, cLiamProviderExchange>();

        /// <summary>
        /// Register an Exchange provider
        /// </summary>
        public static void RegisterProvider(Guid configId, cLiamProviderExchange provider)
        {
            if (_registeredProviders.ContainsKey(configId))
            {
                _registeredProviders[configId] = provider;
            }
            else
            {
                _registeredProviders.Add(configId, provider);
            }
        }

        /// <summary>
        /// Get a registered Exchange provider
        /// </summary>
        public static cLiamProviderExchange GetProvider(Guid configId)
        {
            if (_registeredProviders.ContainsKey(configId))
            {
                return _registeredProviders[configId];
            }
            return null;
        }
    }

    public static class LiamInitializer
    {
        public static Guid exchangeModuleId = new Guid("f8a23b4c-2d92-ec11-5437-00155d500102");

        static public cLiamProviderBase CreateInstance(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData)
        {
            return new cLiamProviderExchange(LiamConfiguration, ProviderData);
        }
    }

    public class cLiamProviderExchange : cLiamProviderBase
    {
        private ExchangeManager _exchangeManager = null;
        private bool _isOnline = false;
        public string LastErrorMessage { get; private set; } = null;
        public Exception LastException { get; private set; } = null;

        public cLiamProviderExchange(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData) :
            base(LiamConfiguration, ProviderData)
        {
        }

        public override async Task<bool> LogonAsync()
        {
            if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(LiamInitializer.exchangeModuleId))
            {
                LogEntry($"Error: License not valid", LogLevels.Error);
                return false;
            }
            return await LogonAsync(true);
        }

        public async Task<bool> LogonAsync(bool force = false)
        {
            if (!force && _isOnline)
                return true;

            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (_exchangeManager != null)
                {
                    _exchangeManager.Dispose();
                    _exchangeManager = null;
                }

                SecureString securePassword = new SecureString();
                if (this.Credential?.Secret != null)
                {
                    foreach (char c in this.Credential.Secret)
                    {
                        securePassword.AppendChar(c);
                    }
                    securePassword.MakeReadOnly();
                }

                _exchangeManager = new ExchangeManager(
                    this.RootPath, // Exchange Server FQDN
                    this.Credential?.Identification,
                    securePassword
                );

                _isOnline = true;
                return true;
            }
            catch (Exception E)
            {
                LogException(E);
                LastException = E;
                LastErrorMessage = $"Exchange login failed: {E.Message}";
                _isOnline = false;
                return false;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override string GetLastErrorMessage()
        {
            return LastErrorMessage;
        }

        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(LiamInitializer.exchangeModuleId))
                {
                    LogEntry($"Error: License not valid", LogLevels.Error);
                    return new List<cLiamDataAreaBase>();
                }

                if (!await LogonAsync())
                    return null;

                var DataAreas = new List<cLiamDataAreaBase>();

                // Get Shared Mailboxes
                var sharedMailboxes = _exchangeManager.GetSharedMailboxes();
                foreach (var mailbox in sharedMailboxes)
                {
                    string displayName = mailbox.Properties["DisplayName"]?.Value?.ToString() ?? "N/A";
                    string email = mailbox.Properties["PrimarySmtpAddress"]?.Value?.ToString() ?? "N/A";
                    string alias = mailbox.Properties["Alias"]?.Value?.ToString() ?? "N/A";

                    if (!string.IsNullOrEmpty(this.DataAreaRegEx) && !Regex.Match(displayName, this.DataAreaRegEx).Success)
                        continue;

                    var sharedMailbox = new cLiamExchangeSharedMailbox(this, new cExchangeResultSharedMailbox
                    {
                        DisplayName = displayName,
                        EmailAddress = email,
                        Alias = alias,
                        PSObject = mailbox
                    });

                    DataAreas.Add(sharedMailbox);
                }

                // Get Distribution Groups
                var distributionGroups = _exchangeManager.GetDistributionGroups();
                foreach (var group in distributionGroups)
                {
                    string displayName = group.Properties["DisplayName"]?.Value?.ToString() ?? "N/A";
                    string email = group.Properties["PrimarySmtpAddress"]?.Value?.ToString() ?? "N/A";
                    string alias = group.Properties["Alias"]?.Value?.ToString() ?? "N/A";

                    if (!string.IsNullOrEmpty(this.DataAreaRegEx) && !Regex.Match(displayName, this.DataAreaRegEx).Success)
                        continue;

                    var distributionGroup = new cLiamExchangeDistributionGroup(this, new cExchangeResultDistributionGroup
                    {
                        DisplayName = displayName,
                        EmailAddress = email,
                        Alias = alias,
                        PSObject = group
                    });

                    DataAreas.Add(distributionGroup);
                }

                return DataAreas;
            }
            catch (Exception E)
            {
                LogException(E);
                LastException = E;
                LastErrorMessage = $"Failed to get Exchange data areas: {E.Message}";
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override async Task<cLiamDataAreaBase> LoadDataArea(string UID)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (!cC4ITLicenseM42ESM.Instance.IsValid || !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(LiamInitializer.exchangeModuleId))
                {
                    LogEntry($"Error: License not valid", LogLevels.Error);
                    return null;
                }

                if (!await LogonAsync())
                    return null;

                // Parse the UID to determine the type and ID
                var parts = UID.Split('|');
                if (parts.Length < 2)
                    return null;

                string type = parts[0];
                string id = parts[1];

                if (type.Equals("SharedMailbox", StringComparison.OrdinalIgnoreCase))
                {
                    // Implement loading a specific shared mailbox
                    // This would require a command like Get-Mailbox -Identity <id>
                    return null; // Placeholder, implement actual loading
                }
                else if (type.Equals("DistributionGroup", StringComparison.OrdinalIgnoreCase))
                {
                    // Implement loading a specific distribution group
                    // This would require a command like Get-DistributionGroup -Identity <id>
                    return null; // Placeholder, implement actual loading
                }

                return null;
            }
            catch (Exception E)
            {
                LogException(E);
                LastException = E;
                LastErrorMessage = $"Failed to load Exchange data area: {E.Message}";
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override async Task<List<cLiamDataAreaBase>> getSecurityGroupsAsync(string groupFilter)
        {
            await Task.Delay(0); // Just to make the method async
            return new List<cLiamDataAreaBase>(); // Not implemented for Exchange provider
        }

        // Method to create a new shared mailbox
        public async Task<bool> CreateSharedMailbox(string name, string alias, string userPrincipalName, string organizationalUnit = null)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (!await LogonAsync())
                    return false;

                _exchangeManager.CreateSharedMailbox(name, alias, userPrincipalName, organizationalUnit);
                return true;
            }
            catch (Exception E)
            {
                LogException(E);
                LastException = E;
                LastErrorMessage = $"Failed to create shared mailbox: {E.Message}";
                return false;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        // Method to create a new distribution group
        public async Task<bool> CreateDistributionGroup(string name, string alias, string organizationalUnit = null, string managedBy = null)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (!await LogonAsync())
                    return false;

                _exchangeManager.CreateDistributionGroup(name, alias, organizationalUnit, managedBy);
                return true;
            }
            catch (Exception E)
            {
                LogException(E);
                LastException = E;
                LastErrorMessage = $"Failed to create distribution group: {E.Message}";
                return false;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        // Method to add member to distribution group
        public async Task<bool> AddMemberToDistributionGroup(string groupIdentity, string memberIdentity)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (!await LogonAsync())
                    return false;

                _exchangeManager.AddMemberToDistributionGroup(groupIdentity, memberIdentity);
                return true;
            }
            catch (Exception E)
            {
                LogException(E);
                LastException = E;
                LastErrorMessage = $"Failed to add member to distribution group: {E.Message}";
                return false;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        // Method to add full access permission to a shared mailbox
        public async Task<bool> AddFullAccessPermission(string mailboxIdentity, string userIdentity)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (!await LogonAsync())
                    return false;

                _exchangeManager.AddFullAccessPermission(mailboxIdentity, userIdentity);
                return true;
            }
            catch (Exception E)
            {
                LogException(E);
                LastException = E;
                LastErrorMessage = $"Failed to add full access permission: {E.Message}";
                return false;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        // Dispose pattern to clean up Exchange manager
        private bool disposed = false;

        public void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    if (_exchangeManager != null)
                    {
                        _exchangeManager.Dispose();
                        _exchangeManager = null;
                    }
                }
                disposed = true;
            }
        }

        ~cLiamProviderExchange()
        {
            Dispose(false);
        }
    }

    // Base class for Exchange results
    public class cExchangeResultBase
    {
        public string DisplayName { get; set; }
        public string EmailAddress { get; set; }
        public string Alias { get; set; }
        public PSObject PSObject { get; set; }
    }

    // Class for Shared Mailbox results
    public class cExchangeResultSharedMailbox : cExchangeResultBase
    {
    }

    // Class for Distribution Group results
    public class cExchangeResultDistributionGroup : cExchangeResultBase
    {
    }

    // Class representing an Exchange Shared Mailbox
    public class cLiamExchangeSharedMailbox : cLiamDataAreaBase
    {
        public new readonly cLiamProviderExchange Provider = null;
        private readonly cExchangeResultSharedMailbox Mailbox = null;

        public cLiamExchangeSharedMailbox(cLiamProviderExchange Provider, cExchangeResultSharedMailbox Mailbox) :
            base(Provider)
        {
            this.Provider = Provider;
            this.Mailbox = Mailbox;

            this.DisplayName = Mailbox.DisplayName;
            this.TechnicalName = Mailbox.EmailAddress;
            this.UID = $"SharedMailbox|{Mailbox.Alias}";
            this.Level = 0;
            this.DataType = eLiamDataAreaTypes.Unknown; // No specific type for Exchange in the enum
            this.SupportsPermissions = true;
        }

        public override async Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1)
        {
            await Task.Delay(0); // Just to make the method async
            return new List<cLiamDataAreaBase>(); // Shared mailboxes don't have children
        }

        public override async Task<List<cLiamPermissionInfo>> GetPermissionsAsync(bool force = false)
        {
            // Implementation would retrieve permissions for the mailbox
            await Task.Delay(0);
            return null; // Placeholder, actual implementation would query Exchange
        }

        public override async Task<cLiamPermissionResult> GrantPermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role)
        {
            var result = new cLiamPermissionResult { Valid = false };

            try
            {
                // Convert LIAM access role to Exchange permission
                if (Role == eLiamAccessRoles.Owner || Role == eLiamAccessRoles.Write)
                {
                    // Add full access permission
                    bool success = await Provider.AddFullAccessPermission(Mailbox.Alias, User.UserPrincipalName);
                    if (success)
                    {
                        result.Valid = true;
                        result.UserReference = User.UserPrincipalName;
                    }
                }
                // Could also implement Send-As permissions here
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return result;
        }
    }

    // Class representing an Exchange Distribution Group
    public class cLiamExchangeDistributionGroup : cLiamDataAreaBase
    {
        public new readonly cLiamProviderExchange Provider = null;
        private readonly cExchangeResultDistributionGroup Group = null;

        public cLiamExchangeDistributionGroup(cLiamProviderExchange Provider, cExchangeResultDistributionGroup Group) :
            base(Provider)
        {
            this.Provider = Provider;
            this.Group = Group;

            this.DisplayName = Group.DisplayName;
            this.TechnicalName = Group.EmailAddress;
            this.UID = $"DistributionGroup|{Group.Alias}";
            this.Level = 0;
            this.DataType = eLiamDataAreaTypes.Unknown; // No specific type for Exchange in the enum
            this.SupportsPermissions = true;
        }

        public override async Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1)
        {
            await Task.Delay(0); // Just to make the method async
            return new List<cLiamDataAreaBase>(); // Groups could potentially have members as children
        }

        public override async Task<List<cLiamPermissionInfo>> GetPermissionsAsync(bool force = false)
        {
            // Implementation would retrieve members of the group
            await Task.Delay(0);
            return null; // Placeholder, actual implementation would query Exchange
        }

        public override async Task<cLiamPermissionResult> GrantPermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role)
        {
            var result = new cLiamPermissionResult { Valid = false };

            try
            {
                // Add user to the distribution group
                bool success = await Provider.AddMemberToDistributionGroup(Group.Alias, User.UserPrincipalName);
                if (success)
                {
                    result.Valid = true;
                    result.UserReference = User.UserPrincipalName;
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return result;
        }
    }
}