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

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

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

    public class cLiamProviderExchange : cLiamProviderBase
    {
        public static Guid exchangeModuleId = new Guid("A1E213C3-6517-EA11-4881-000C2980FD95");
        internal readonly ExchangeManager exchangeManager;
        private string exchangeUri;
        private PSCredential credential;
        private string organizationalUnit;
        private string lastErrorMessage;
        private bool isLoggedOn = false;

        public cLiamProviderExchange(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData) :
            base(LiamConfiguration, ProviderData)
        {
            exchangeUri = ProviderData.RootPath;

            if (!string.IsNullOrEmpty(ProviderData.GroupPath))
                organizationalUnit = ProviderData.GroupPath;
            else
                organizationalUnit = ProviderData.Domain;

            // Use the credential from the provider data
            var securePassword = new SecureString();
            foreach (char c in ProviderData.Credential.Secret)
            {
                securePassword.AppendChar(c);
            }
            credential = new PSCredential(ProviderData.Credential.Identification, securePassword);

            // Create the Exchange manager
            exchangeManager = new ExchangeManager(exchangeUri, credential, organizationalUnit);
        }

        public override async Task<bool> LogonAsync()
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (!cC4ITLicenseM42ESM.Instance.IsValid /*|| !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(exchangeModuleId)*/)
                {
                    LogEntry($"Error: License not valid or Exchange module not licensed", LogLevels.Error);
                    lastErrorMessage = "License not valid or Exchange module not licensed";
                    return false;
                }

                // Test connection by getting a simple list
                try
                {
                    var testMailboxes = exchangeManager.GetSharedMailboxes("Name -like '*'");
                    if (testMailboxes != null)
                    {
                        LogEntry($"Successfully connected to Exchange", LogLevels.Info);
                        isLoggedOn = true;
                        return true;
                    }
                }
                catch (Exception ex)
                {
                    LogException(ex);
                    lastErrorMessage = $"Failed to connect to Exchange: {ex.Message}";
                    isLoggedOn = false;
                    return false;
                }

                lastErrorMessage = "Unknown error connecting to Exchange";
                return false;
            }
            catch (Exception E)
            {
                LogException(E);
                lastErrorMessage = $"Exception during Exchange logon: {E.Message}";
                return false;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override string GetLastErrorMessage()
        {
            return lastErrorMessage;
        }

        public override async Task<List<cLiamDataAreaBase>> getDataAreasAsync(int MaxDepth = -1)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (!cC4ITLicenseM42ESM.Instance.IsValid /*|| !cC4ITLicenseM42ESM.Instance.Modules.ContainsKey(exchangeModuleId)*/)
                {
                    LogEntry($"Error: License not valid or Exchange module not licensed", LogLevels.Error);
                    return new List<cLiamDataAreaBase>();
                }

                if (!isLoggedOn && !await LogonAsync())
                    return null;

                var DataAreas = new List<cLiamDataAreaBase>();

                // Get Shared Mailboxes
                try
                {
                    var sharedMailboxes = exchangeManager.GetSharedMailboxes();
                    foreach (var mailbox in sharedMailboxes)
                    {
                        var displayName = mailbox.Properties["DisplayName"]?.Value?.ToString();
                        var alias = mailbox.Properties["Alias"]?.Value?.ToString();
                        var primarySmtpAddress = mailbox.Properties["PrimarySmtpAddress"]?.Value?.ToString();

                        // Skip if it doesn't match the regex filter (if provided)
                        if (!string.IsNullOrEmpty(this.DataAreaRegEx) &&
                            !System.Text.RegularExpressions.Regex.Match(displayName, this.DataAreaRegEx).Success)
                            continue;

                        var exchangeMailbox = new cLiamExchangeSharedMailbox(this, displayName, primarySmtpAddress, alias);
                        DataAreas.Add(exchangeMailbox);
                    }
                }
                catch (Exception ex)
                {
                    LogException(ex);
                }

                // Get Distribution Groups
                try
                {
                    var distributionGroups = exchangeManager.GetDistributionGroups();
                    foreach (var group in distributionGroups)
                    {
                        var displayName = group.Properties["DisplayName"]?.Value?.ToString();
                        var alias = group.Properties["Alias"]?.Value?.ToString();
                        var primarySmtpAddress = group.Properties["PrimarySmtpAddress"]?.Value?.ToString();

                        // Skip if it doesn't match the regex filter (if provided)
                        if (!string.IsNullOrEmpty(this.DataAreaRegEx) &&
                            !System.Text.RegularExpressions.Regex.Match(displayName, this.DataAreaRegEx).Success)
                            continue;

                        var exchangeGroup = new cLiamExchangeDistributionGroup(this, displayName, primarySmtpAddress, alias);
                        DataAreas.Add(exchangeGroup);
                    }
                }
                catch (Exception ex)
                {
                    LogException(ex);
                }

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

            return null;
        }

        public override async Task<cLiamDataAreaBase> LoadDataArea(string UID)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var dataType = cLIAMHelper.getUidItem(ref UID);
                if (string.IsNullOrEmpty(dataType))
                    return null;

                int dataTypeInt;
                if (!int.TryParse(dataType, out dataTypeInt))
                    return null;

                var primarySmtpAddress = cLIAMHelper.getUidItem(ref UID);
                if (string.IsNullOrEmpty(primarySmtpAddress))
                    return null;

                if (!isLoggedOn && !await LogonAsync())
                    return null;

                switch ((eLiamDataAreaTypes)dataTypeInt)
                {
                    case eLiamDataAreaTypes.ExchangeSharedMailbox:
                        return await cLiamExchangeSharedMailbox.Load(this, primarySmtpAddress);
                    case eLiamDataAreaTypes.ExchangeDistributionGroup:
                        return await cLiamExchangeDistributionGroup.Load(this, primarySmtpAddress);
                    default:
                        return null;
                }
            }
            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(exchangeModuleId)*/)
                {
                    LogEntry($"Error: License not valid or Exchange module not licensed", LogLevels.Error);
                    return new List<cLiamDataAreaBase>();
                }

                if (!isLoggedOn && !await LogonAsync())
                    return null;

                // For Exchange, we need to use the same AD security groups as from the AD provider
                // This is just a placeholder implementation to return some groups from the exchange environment
                var securityGroups = new List<cLiamDataAreaBase>();

                try
                {
                    // Use the Exchange PowerShell to get AD groups
                    var groups = exchangeManager.GetSecurityGroups(groupFilter);
                    foreach (var group in groups)
                    {
                        var displayName = group.Properties["DisplayName"]?.Value?.ToString();
                        var sid = group.Properties["Sid"]?.Value?.ToString();
                        var dn = group.Properties["DistinguishedName"]?.Value?.ToString();

                        // Skip if it doesn't match the regex filter (if provided)
                        if (!string.IsNullOrEmpty(this.GroupRegEx) &&
                            !System.Text.RegularExpressions.Regex.Match(displayName, this.GroupRegEx).Success)
                            continue;

                        var securityGroup = new cLiamExchangeSecurityGroup(this, displayName, sid, dn);
                        securityGroups.Add(securityGroup);
                    }
                }
                catch (Exception ex)
                {
                    LogException(ex);
                }

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

            return null;
        }

        // Helper methods to interact with Exchange
        internal async Task<bool> CreateSharedMailbox(string displayName, string alias, string ownerGroupName, string writeGroupName)
        {
            try
            {
                exchangeManager.CreateSharedMailbox(displayName, alias, null);

                // Create the owner (FullAccess) and write (SendAs) groups if they don't exist already
                exchangeManager.CreateSecurityGroup(ownerGroupName, $"{displayName} Full Access");
                exchangeManager.CreateSecurityGroup(writeGroupName, $"{displayName} Send As");

                // Grant permissions to the groups
                exchangeManager.AddMailboxPermission(displayName, ownerGroupName, "FullAccess");
                exchangeManager.AddSendAsPermission(displayName, writeGroupName);

                return true;
            }
            catch (Exception ex)
            {
                LogException(ex);
                lastErrorMessage = $"Error creating shared mailbox: {ex.Message}";
                return false;
            }
        }

        internal async Task<bool> CreateDistributionGroup(string displayName, string alias, List<string> initialMembers = null)
        {
            try
            {
                exchangeManager.CreateDistributionGroup(displayName, alias, initialMembers);
                return true;
            }
            catch (Exception ex)
            {
                LogException(ex);
                lastErrorMessage = $"Error creating distribution group: {ex.Message}";
                return false;
            }
        }

        internal async Task<bool> AddMemberToGroup(string groupName, string member, bool isSharedMailbox)
        {
            try
            {
                if (isSharedMailbox)
                {
                    exchangeManager.AddMailboxPermission(groupName, member);
                }
                else
                {
                    exchangeManager.AddMemberToDistributionGroup(groupName, member);
                }
                return true;
            }
            catch (Exception ex)
            {
                LogException(ex);
                lastErrorMessage = $"Error adding member to group: {ex.Message}";
                return false;
            }
        }

        internal async Task<bool> RemoveMemberFromGroup(string groupName, string member, bool isSharedMailbox)
        {
            try
            {
                if (isSharedMailbox)
                {
                    exchangeManager.RemoveMailboxPermission(groupName, member);
                }
                else
                {
                    exchangeManager.RemoveMemberFromDistributionGroup(groupName, member);
                }
                return true;
            }
            catch (Exception ex)
            {
                LogException(ex);
                lastErrorMessage = $"Error removing member from group: {ex.Message}";
                return false;
            }
        }
    }

    public class cLiamExchangeSecurityGroup : cLiamDataAreaBase
    {
        public new readonly cLiamProviderExchange Provider = null;
        public readonly string sid = null;
        public readonly string dn = null;

        public cLiamExchangeSecurityGroup(cLiamProviderExchange Provider, string displayName, string sid, string dn) : base(Provider)
        {
            this.Provider = Provider;
            this.TechnicalName = displayName;
            this.UID = sid;
            this.sid = sid;
            this.dn = dn;
        }

        public override Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1)
        {
            // Security groups don't have children in this context
            return Task.FromResult(new List<cLiamDataAreaBase>());
        }
    }

    public class cLiamExchangeSharedMailbox : cLiamDataAreaBase
    {
        public new readonly cLiamProviderExchange Provider = null;
        public readonly string PrimarySmtpAddress = null;
        public readonly string Alias = null;
        public string OwnerGroupIdentifier = null;  // Full Access group
        public string WriteGroupIdentifier = null;  // Send As group

        public cLiamExchangeSharedMailbox(cLiamProviderExchange Provider, string displayName, string primarySmtpAddress, string alias) : base(Provider)
        {
            this.Provider = Provider;
            this.TechnicalName = displayName;
            this.DisplayName = displayName;
            this.PrimarySmtpAddress = primarySmtpAddress;
            this.Alias = alias;
            this.UID = getUID(primarySmtpAddress);
            this.Level = 0;
            this.DataType = eLiamDataAreaTypes.ExchangeSharedMailbox;
            this.SupportsOwners = true;
            this.SupportsPermissions = true;

            // Set standard naming convention for access groups
            // These will be resolved to actual groups later as needed
            this.OwnerGroupIdentifier = $"EXCH_FA_{alias}";  // Full Access group
            this.WriteGroupIdentifier = $"EXCH_SA_{alias}";  // Send As group
        }

        internal static string getUID(string primarySmtpAddress)
        {
            return $"{(int)eLiamDataAreaTypes.ExchangeSharedMailbox}|{primarySmtpAddress}";
        }

        public static async Task<cLiamExchangeSharedMailbox> Load(cLiamProviderExchange Provider, string primarySmtpAddress)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                // Get the mailbox details from Exchange
                var mailbox = Provider.exchangeManager.GetSharedMailboxByAddress(primarySmtpAddress);
                if (mailbox == null)
                    return null;

                var displayName = mailbox.Properties["DisplayName"]?.Value?.ToString();
                var alias = mailbox.Properties["Alias"]?.Value?.ToString();

                return new cLiamExchangeSharedMailbox(Provider, displayName, primarySmtpAddress, alias);
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override async Task<List<cLiamUserInfo>> GetOwnersAsync()
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                // Get members of the Full Access group
                var members = Provider.exchangeManager.GetMailboxPermissionMembers(this.DisplayName);
                if (members == null)
                    return new List<cLiamUserInfo>();

                var ownersList = new List<cLiamUserInfo>();
                foreach (var member in members)
                {
                    var userInfo = new cLiamUserInfo
                    {
                        DisplayName = member.Properties["DisplayName"]?.Value?.ToString(),
                        UserPrincipalName = member.Properties["UserPrincipalName"]?.Value?.ToString(),
                        EMail = member.Properties["PrimarySmtpAddress"]?.Value?.ToString(),
                        SID = member.Properties["Sid"]?.Value?.ToString()
                    };
                    ownersList.Add(userInfo);
                }

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

        public override async Task<cLiamPermissionResult> GrantPermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                bool success = false;

                switch (Role)
                {
                    case eLiamAccessRoles.Owner:
                        // Add to Full Access group
                        success = await Provider.AddMemberToGroup(this.DisplayName, User.UserPrincipalName, true);
                        break;
                    case eLiamAccessRoles.Write:
                        // Add Send As permission
                        success = await Provider.exchangeManager.AddSendAsPermission(this.DisplayName, User.UserPrincipalName);
                        break;
                    default:
                        LogEntry($"Unsupported permission role for Exchange mailbox: {Role}", LogLevels.Warning);
                        return new cLiamPermissionResult { Valid = false };
                }

                return new cLiamPermissionResult
                {
                    Valid = success,
                    UserReference = User.UserPrincipalName
                };
            }
            catch (Exception E)
            {
                LogException(E);
                return new cLiamPermissionResult { Valid = false };
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override async Task<bool> RevokePermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                bool success = false;

                switch (Role)
                {
                    case eLiamAccessRoles.Owner:
                        // Remove from Full Access group
                        success = await Provider.RemoveMemberFromGroup(this.DisplayName, User.UserPrincipalName, true);
                        break;
                    case eLiamAccessRoles.Write:
                        // Remove Send As permission
                        success = await Provider.exchangeManager.RemoveSendAsPermission(this.DisplayName, User.UserPrincipalName);
                        break;
                    default:
                        LogEntry($"Unsupported permission role for Exchange mailbox: {Role}", LogLevels.Warning);
                        return false;
                }

                return success;
            }
            catch (Exception E)
            {
                LogException(E);
                return false;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override async Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1)
        {
            // Shared mailboxes don't have children in this context
            return new List<cLiamDataAreaBase>();
        }
    }

    public class cLiamExchangeDistributionGroup : cLiamDataAreaBase
    {
        public new readonly cLiamProviderExchange Provider = null;
        public readonly string PrimarySmtpAddress = null;
        public readonly string Alias = null;

        public cLiamExchangeDistributionGroup(cLiamProviderExchange Provider, string displayName, string primarySmtpAddress, string alias) : base(Provider)
        {
            this.Provider = Provider;
            this.TechnicalName = displayName;
            this.DisplayName = displayName;
            this.PrimarySmtpAddress = primarySmtpAddress;
            this.Alias = alias;
            this.UID = getUID(primarySmtpAddress);
            this.Level = 0;
            this.DataType = eLiamDataAreaTypes.ExchangeDistributionGroup;
            this.SupportsOwners = true;
            this.SupportsPermissions = true;
        }

        internal static string getUID(string primarySmtpAddress)
        {
            return $"{(int)eLiamDataAreaTypes.ExchangeDistributionGroup}|{primarySmtpAddress}";
        }

        public static async Task<cLiamExchangeDistributionGroup> Load(cLiamProviderExchange Provider, string primarySmtpAddress)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                // Get the group details from Exchange
                var group = Provider.exchangeManager.GetDistributionGroupByAddress(primarySmtpAddress);
                if (group == null)
                    return null;

                var displayName = group.Properties["DisplayName"]?.Value?.ToString();
                var alias = group.Properties["Alias"]?.Value?.ToString();

                return new cLiamExchangeDistributionGroup(Provider, displayName, primarySmtpAddress, alias);
            }
            catch (Exception E)
            {
                LogException(E);
                return null;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override async Task<List<cLiamUserInfo>> GetOwnersAsync()
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                // Get members of the distribution group
                var members = Provider.exchangeManager.GetDistributionGroupMembers(this.DisplayName);
                if (members == null)
                    return new List<cLiamUserInfo>();

                var membersList = new List<cLiamUserInfo>();
                foreach (var member in members)
                {
                    var userInfo = new cLiamUserInfo
                    {
                        DisplayName = member.Properties["DisplayName"]?.Value?.ToString(),
                        UserPrincipalName = member.Properties["UserPrincipalName"]?.Value?.ToString(),
                        EMail = member.Properties["PrimarySmtpAddress"]?.Value?.ToString(),
                        SID = member.Properties["Sid"]?.Value?.ToString()
                    };
                    membersList.Add(userInfo);
                }

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

        public override async Task<cLiamPermissionResult> GrantPermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                // For distribution groups, we only support adding members (which is essentially Owner role)
                if (Role != eLiamAccessRoles.Owner)
                {
                    LogEntry($"Only Owner role is supported for distribution groups, requested: {Role}", LogLevels.Warning);
                    return new cLiamPermissionResult { Valid = false };
                }

                bool success = await Provider.AddMemberToGroup(this.DisplayName, User.UserPrincipalName, false);

                return new cLiamPermissionResult
                {
                    Valid = success,
                    UserReference = User.UserPrincipalName
                };
            }
            catch (Exception E)
            {
                LogException(E);
                return new cLiamPermissionResult { Valid = false };
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override async Task<bool> RevokePermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                // For distribution groups, we only support removing members (which is essentially Owner role)
                if (Role != eLiamAccessRoles.Owner)
                {
                    LogEntry($"Only Owner role is supported for distribution groups, requested: {Role}", LogLevels.Warning);
                    return false;
                }

                return await Provider.RemoveMemberFromGroup(this.DisplayName, User.UserPrincipalName, false);
            }
            catch (Exception E)
            {
                LogException(E);
                return false;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public override async Task<List<cLiamDataAreaBase>> getChildrenAsync(int Depth = -1)
        {
            // Distribution groups don't have children in this context
            return new List<cLiamDataAreaBase>();
        }
    }
}