using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Security;
using System.Reflection;

using C4IT.Logging;
using static C4IT.Logging.cLogManager;

namespace C4IT.LIAM
{
    /// <summary>
    /// Manages connections and operations for a local Microsoft Exchange Server
    /// using PowerShell Remoting with explicit credentials.
    /// </summary>
    public class ExchangeManager : IDisposable
    {
        private readonly string _exchangeServerFqdn;
        private readonly PSCredential _credential;
        private Runspace _runspace;
        private bool _isConnected = false;

        /// <summary>
        /// Initializes the ExchangeManager for connection with explicit credentials.
        /// </summary>
        /// <param name="exchangeServerFqdn">Fully qualified domain name of the Exchange Server.</param>
        /// <param name="userName">Username for the connection (e.g. user@domain.com or DOMAIN\user).</param>
        /// <param name="password">The user's password as a SecureString.</param>
        public ExchangeManager(string exchangeServerFqdn, string userName, SecureString password)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                _exchangeServerFqdn = exchangeServerFqdn ?? throw new ArgumentNullException(nameof(exchangeServerFqdn));
                if (string.IsNullOrEmpty(userName)) throw new ArgumentNullException(nameof(userName));
                _credential = new PSCredential(userName, password ?? throw new ArgumentNullException(nameof(password)));

                InitializeRunspace();
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        // Initializes the PowerShell Runspace for remote connection
        private void InitializeRunspace()
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                // Use HTTPS for Basic Authentication!
                var connectionUri = new Uri($"https://{_exchangeServerFqdn}/powershell?serializationLevel=Full");

                var connectionInfo = new WSManConnectionInfo(
                    connectionUri,
                    "http://schemas.microsoft.com/powershell/Microsoft.Exchange",
                    _credential
                )
                {
                    // Set authentication to Basic (or Negotiate/Kerberos depending on server config)
                    // Basic Auth MUST be enabled on the Exchange Server for the PowerShell vDir!
                    AuthenticationMechanism = AuthenticationMechanism.Basic
                };

                _runspace = RunspaceFactory.CreateRunspace(connectionInfo);

                LogEntry($"Attempting to connect to {connectionUri} with user '{_credential.UserName}' and method '{connectionInfo.AuthenticationMechanism}'...", LogLevels.Info);
                _runspace.Open();
                _isConnected = true;
                LogEntry("Connection established successfully.", LogLevels.Info);
            }
            catch (Exception E)
            {
                LogException(E);
                LogEntry($"Error opening runspace to Exchange server. Check server name, credentials, and PowerShell remoting settings.", LogLevels.Error);
                throw new InvalidOperationException($"Error opening runspace to Exchange. Details: {E.Message}", E);
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        // Private helper method to execute PowerShell commands
        private IEnumerable<PSObject> ExecuteExchangeCommand(string script, Dictionary<string, object> parameters = null)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                if (_runspace == null || _runspace.RunspaceStateInfo.State != RunspaceState.Opened)
                {
                    throw new InvalidOperationException("Runspace is not open or initialized.");
                }

                LogEntry($"Executing script: {script}", LogLevels.Debug);
                if (parameters != null && parameters.Any())
                {
                    LogEntry("With parameters: " + string.Join(", ", parameters.Select(kvp => $"{kvp.Key}=***")), LogLevels.Debug);
                }

                using (PowerShell ps = PowerShell.Create())
                {
                    ps.Runspace = _runspace;
                    ps.AddScript(script);

                    if (parameters != null)
                    {
                        ps.AddParameters(parameters);
                    }

                    var results = ps.Invoke();

                    // Check for errors (important!)
                    if (ps.Streams.Error.Count > 0)
                    {
                        var errorMessages = ps.Streams.Error.Select(e => e.ToString()).ToList();
                        LogEntry($"Error in PowerShell execution (User: '{_credential.UserName}'):", LogLevels.Error);
                        foreach (var errMsg in errorMessages)
                        {
                            LogEntry($"- {errMsg}", LogLevels.Error);
                        }
                        // Throw exception to surface the error
                        throw new Exception($"Error in Exchange PowerShell command execution. First error message: {errorMessages.FirstOrDefault()}");
                    }

                    LogEntry("Script executed successfully.", LogLevels.Debug);
                    return results;
                }
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        // *** Methods for managing Shared Mailboxes ***

        /// <summary>
        /// Creates a new Shared Mailbox. The associated AD account is disabled by default.
        /// </summary>
        public void CreateSharedMailbox(string name, string alias, string userPrincipalName, string organizationalUnit = null)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                string script = @"New-Mailbox -Name $Name -Alias $Alias -UserPrincipalName $UserPrincipalName -Shared";
                var parameters = new Dictionary<string, object>
                {
                    { "Name", name },
                    { "Alias", alias },
                    { "UserPrincipalName", userPrincipalName }
                };

                if (!string.IsNullOrEmpty(organizationalUnit))
                {
                    script += " -OrganizationalUnit $OrganizationalUnit";
                    parameters.Add("OrganizationalUnit", organizationalUnit);
                }

                ExecuteExchangeCommand(script, parameters);
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /// <summary>
        /// Gets information about all Shared Mailboxes.
        /// </summary>
        public IEnumerable<PSObject> GetSharedMailboxes()
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                return ExecuteExchangeCommand("Get-Mailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited");
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /// <summary>
        /// Adds full access permissions for a user to a mailbox.
        /// </summary>
        public void AddFullAccessPermission(string mailboxIdentity, string userIdentity)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                ExecuteExchangeCommand(
                    "Add-MailboxPermission -Identity $MailboxIdentity -User $UserIdentity -AccessRights FullAccess -InheritanceType All",
                    new Dictionary<string, object> { { "MailboxIdentity", mailboxIdentity }, { "UserIdentity", userIdentity } }
                );
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /// <summary>
        /// Removes full access permissions for a user from a mailbox.
        /// </summary>
        public void RemoveFullAccessPermission(string mailboxIdentity, string userIdentity)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                ExecuteExchangeCommand(
                    "Remove-MailboxPermission -Identity $MailboxIdentity -User $UserIdentity -AccessRights FullAccess -InheritanceType All -Confirm:$false",
                    new Dictionary<string, object> { { "MailboxIdentity", mailboxIdentity }, { "UserIdentity", userIdentity } }
                );
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /// <summary>
        /// Adds "Send As" permissions for a user to a mailbox.
        /// </summary>
        public void AddSendAsPermission(string mailboxIdentity, string userIdentity)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                ExecuteExchangeCommand(
                    "Add-ADPermission -Identity $MailboxIdentity -User $UserIdentity -ExtendedRights 'Send As'",
                    new Dictionary<string, object> { { "MailboxIdentity", mailboxIdentity }, { "UserIdentity", userIdentity } }
                );
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        // *** Methods for managing Distribution Groups (Mailing Lists) ***

        /// <summary>
        /// Creates a new Distribution Group (mailing list).
        /// </summary>
        public void CreateDistributionGroup(string name, string alias, string organizationalUnit = null, string managedBy = null)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                string script = @"New-DistributionGroup -Name $Name -Alias $Alias -Type Distribution";
                var parameters = new Dictionary<string, object> { { "Name", name }, { "Alias", alias } };

                if (!string.IsNullOrEmpty(organizationalUnit))
                {
                    script += " -OrganizationalUnit $OrganizationalUnit";
                    parameters.Add("OrganizationalUnit", organizationalUnit);
                }

                if (!string.IsNullOrEmpty(managedBy))
                {
                    script += " -ManagedBy $ManagedBy";
                    parameters.Add("ManagedBy", managedBy);
                }

                ExecuteExchangeCommand(script, parameters);
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /// <summary>
        /// Gets information about all Distribution Groups.
        /// </summary>
        public IEnumerable<PSObject> GetDistributionGroups()
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                return ExecuteExchangeCommand("Get-DistributionGroup -ResultSize Unlimited");
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /// <summary>
        /// Adds a member to a Distribution Group.
        /// </summary>
        public void AddMemberToDistributionGroup(string groupIdentity, string memberIdentity)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                ExecuteExchangeCommand(
                    "Add-DistributionGroupMember -Identity $GroupIdentity -Member $MemberIdentity",
                    new Dictionary<string, object> { { "GroupIdentity", groupIdentity }, { "MemberIdentity", memberIdentity } }
                );
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /// <summary>
        /// Removes a member from a Distribution Group.
        /// </summary>
        public void RemoveMemberFromDistributionGroup(string groupIdentity, string memberIdentity)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                ExecuteExchangeCommand(
                    "Remove-DistributionGroupMember -Identity $GroupIdentity -Member $MemberIdentity -Confirm:$false",
                    new Dictionary<string, object> { { "GroupIdentity", groupIdentity }, { "MemberIdentity", memberIdentity } }
                );
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /// <summary>
        /// Gets the members of a Distribution Group.
        /// </summary>
        public IEnumerable<PSObject> GetDistributionGroupMembers(string groupIdentity)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                return ExecuteExchangeCommand(
                    "Get-DistributionGroupMember -Identity $GroupIdentity",
                    new Dictionary<string, object> { { "GroupIdentity", groupIdentity } }
                );
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /// <summary>
        /// Gets the permissions for a mailbox.
        /// </summary>
        public IEnumerable<PSObject> GetMailboxPermissions(string mailboxIdentity)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                return ExecuteExchangeCommand(
                    "Get-MailboxPermission -Identity $MailboxIdentity | Where-Object {$_.User -ne 'NT AUTHORITY\\SELF' -and $_.IsInherited -eq $false}",
                    new Dictionary<string, object> { { "MailboxIdentity", mailboxIdentity } }
                );
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /// <summary>
        /// Gets information about a specific mailbox.
        /// </summary>
        public PSObject GetMailbox(string identity)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                var results = ExecuteExchangeCommand(
                    "Get-Mailbox -Identity $Identity",
                    new Dictionary<string, object> { { "Identity", identity } }
                );
                return results.FirstOrDefault();
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        /// <summary>
        /// Gets information about a specific distribution group.
        /// </summary>
        public PSObject GetDistributionGroup(string identity)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                var results = ExecuteExchangeCommand(
                    "Get-DistributionGroup -Identity $Identity",
                    new Dictionary<string, object> { { "Identity", identity } }
                );
                return results.FirstOrDefault();
            }
            catch (Exception E)
            {
                LogException(E);
                throw;
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        // Implementation of IDisposable to clean up the runspace
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing && _runspace != null)
            {
                if (_runspace.RunspaceStateInfo.State == RunspaceState.Opened)
                {
                    LogEntry("Closing Runspace...", LogLevels.Debug);
                    _runspace.Close();
                }
                _runspace.Dispose();
                _runspace = null;
                LogEntry("Runspace closed and resources released.", LogLevels.Debug);
            }
        }

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