Imports System.IO
Imports DigitalData.Modules.Logging
Imports DigitalData.Modules.Base
Imports Limilabs.Mail
Imports Limilabs.Mail.MIME
Imports Limilabs.Mail.MSG
Imports Limilabs.Client.IMAP
Imports Limilabs.Client

Public Class Email2
    Private ReadOnly Logger As Logger
    Private ReadOnly LoggerMail As Logger
    Private ReadOnly LogConfig As LogConfig
    Private ReadOnly FileEx As FilesystemEx
    Private ReadOnly MailBuilder As New MailBuilder()
    Private ReadOnly TempFiles As New List(Of String)

    Private DisableExcessiveLogging As Boolean = False

    Public Sub New(pLogConfig As LogConfig)
        LogConfig = pLogConfig
        Logger = pLogConfig.GetLogger()
        LoggerMail = pLogConfig.GetLogger("Limilabs.Mail")
        FileEx = New FilesystemEx(pLogConfig)

        ' Turn on internal Mail.dll logging
        Limilabs.Mail.Log.Enabled = True

        AddHandler Limilabs.Mail.Log.WriteLine, AddressOf ProcessMailLog
    End Sub

    Public Enum EmailSecurity
        SSL
        START_TLS
    End Enum

    Private Sub ProcessMailLog(pMessage As String)
        If DisableExcessiveLogging = False Then
            LoggerMail.Debug(pMessage)
        End If
    End Sub

    Public Function New_SMTPConnection(pSMTP As Limilabs.Client.SMTP.Smtp, pServer As String, pUsername As String, pPassword As String, pSecurity As EmailSecurity, Optional pPort As Integer = 0) As Limilabs.Client.SMTP.Smtp
        Try
            Logger.Info("Connecting to SMTP server [{0}:{1}] with user [{2}]", pServer, pPort, pUsername)

            Dim oPort As Integer
            If pPort = 0 Then
                If pSecurity = EmailSecurity.SSL Then
                    oPort = Limilabs.Client.SMTP.Smtp.DefaultSSLPort

                Else
                    oPort = Limilabs.Client.SMTP.Smtp.DefaultPort

                End If

            Else
                oPort = pPort

            End If
            Logger.Debug("Using Port [{0}] for connection", oPort)

            If pSecurity = EmailSecurity.SSL Then
                Logger.Debug("Using SSL/TLS as Security Option")
                pSMTP.ConnectSSL(pServer, oPort)
            Else
                Logger.Debug("Using STARTTLS as Security Option")
                pSMTP.Connect(pServer, oPort)
                pSMTP.StartTLS()
            End If
            Logger.Debug("Connection to SMTP Server [{0}] established!", pServer)
            If pUsername <> String.Empty Then
                Logger.Debug("Logging in with user [{0}]", pUsername)
                pSMTP.UseBestLogin(pUsername, pPassword)
            End If

            Return pSMTP

        Catch ex As Exception
            Logger.Warn("Could not connect to server [{0}] with user [{1}]", pServer, pUsername)
            Logger.Error(ex)
            Return pSMTP

        End Try
    End Function

    Public Function New_IMAPConnection(pServer As String, pUsername As String, pPassword As String, pSecurity As EmailSecurity, Optional pPort As Integer = 0) As Imap
        Try
            Logger.Info("Connecting to IMAP server [{0}:{1}] with user [{2}]", pServer, pPort, pUsername)

            Dim oIMAP As New Imap()
            Dim oPort As Integer
            If pPort = 0 Then
                If pSecurity = EmailSecurity.SSL Then
                    oPort = Imap.DefaultSSLPort

                Else
                    oPort = Imap.DefaultPort

                End If

            Else
                oPort = pPort

            End If
            Logger.Debug("Using Port [{0}] for connection", oPort)

            If pSecurity = EmailSecurity.SSL Then
                Logger.Debug("Using SSL/TLS as Security Option")
                oIMAP.ConnectSSL(pServer, oPort)
            Else
                Logger.Debug("Using STARTTLS as Security Option")
                oIMAP.Connect(pServer, oPort)
                oIMAP.StartTLS()
            End If
            Logger.Debug("Connection to IMAP Server [{0}] established!", pServer)

            Logger.Debug("Logging in with user [{0}]", pUsername)
            oIMAP.UseBestLogin(pUsername, pPassword)

            Return oIMAP

        Catch ex As Exception
            Logger.Warn("Could not connect to server [{0}] with user [{1}]", pServer, pUsername)
            Logger.Error(ex)
            Throw ex

        End Try
    End Function

    Public Function Test_IMAPLogin(pClient As Imap, Optional pFolder As String = "Inbox") As Boolean
        Logger.Info("Testing Login to IMAP Server")

        Try
            Logger.Debug("Fetching Inbox")
            Dim oStatus As FolderStatus = pClient.SelectInbox()

            Logger.Debug("Test succeeded!")

            Return True
        Catch ex As Exception
            Logger.Warn("Login failed for IMAP server!")
            Logger.Error(ex)

            Return False
        End Try
    End Function

    Public Function Test_SMTPLogin(pClient As Limilabs.Client.SMTP.Smtp) As Boolean
        Logger.Info("Testing Login to IMAP Server")

        Try
            Dim oExtensions = pClient.SupportedExtensions()
            Dim oExtensionArray = oExtensions.Select(Function(ex) ex.ToString).ToArray
            Logger.Debug("Supported Extensions: ")
            Logger.Debug(String.Join(", ", oExtensionArray))
            Logger.Debug("Test succeeded!")

            Return True
        Catch ex As Exception
            Logger.Warn("Login failed for SMTP server!")
            Logger.Error(ex)

            Return False
        End Try
    End Function

    Public Function Get_IMAPMessages(pClient As Imap, Optional pFolder As String = "Inbox") As List(Of IMail)
        Logger.Info("Fetching Messages in Folder [{0}]", pFolder)
        Dim oMails As New List(Of IMail)

        Try
            Logger.Debug("Fetching Folder [{0}]", pFolder)
            Dim oStatus As FolderStatus = pClient.Select(pFolder)

            Logger.Debug("Fetching Unseen UUIDs")

            DisableExcessiveLogging = True
            Dim oUUIDs As List(Of Long) = pClient.Search(Flag.Unseen)
            DisableExcessiveLogging = False

            Logger.Debug("Fetching Unseen Mails")
            For Each oUUID As Long In oUUIDs
                DisableExcessiveLogging = True
                Dim oEmlFile As Byte() = pClient.GetMessageByUID(oUUID)
                DisableExcessiveLogging = False

                Dim oEmail As IMail = MailBuilder.CreateFromEml(oEmlFile)
                oMails.Add(oEmail)

            Next

            Logger.Debug("Emails fetched!")

            Return oMails

        Catch ex As Exception
            Logger.Warn("Fetching messages for folder [{0}] failed!", pFolder)
            Logger.Error(ex)
            Return New List(Of IMail)

        End Try
    End Function

    Public Function Get_IMAPMessage(pClient As Imap, pMessageId As String, Optional pFolder As String = "Inbox") As IMail
        Logger.Info("Fetching Message [{0}]", pMessageId)

        Dim oMail As IMail = Nothing

        Try
            Logger.Debug("Fetching Folder [{0}]", pFolder)
            Dim oStatus As FolderStatus = pClient.Select(pFolder)

            Logger.Debug("Fetching UUIDs")
            Dim oUUIDs As List(Of Long) = pClient.Search(Flag.All)
            Logger.Debug("Fetching Mails")

            DisableExcessiveLogging = True
            Dim oInfos As List(Of MessageInfo) = pClient.GetMessageInfoByUID(oUUIDs)
            DisableExcessiveLogging = False

            Dim oMailInfo = oInfos.Where(Function(i) i.Envelope.MessageID = pMessageId).FirstOrDefault()

            If oMailInfo IsNot Nothing Then
                DisableExcessiveLogging = True
                Dim oMailData As Byte() = pClient.GetMessageByUID(oMailInfo.UID)
                DisableExcessiveLogging = False
                oMail = MailBuilder.CreateFromEml(oMailData)
            End If

            Logger.Debug("Email fetched!")

            Return oMail

        Catch ex As Exception
            Logger.Warn("Fetching message with MessageID [{0}] failed!", pMessageId)
            Logger.Error(ex)

            Return Nothing

        End Try
    End Function

    Public Function Send_SMTPMessage(pClient As Limilabs.Client.SMTP.Smtp, pSender As String, pReceiver As String, pSubject As String, pBody As String) As Boolean
        Logger.Info("Sending Message with Subject [{0}]", pSubject)

        Try
            Dim oBuilder As New MailBuilder()
            oBuilder.From.Add(New Headers.MailBox(pSender))
            oBuilder.To.Add(New Headers.MailBox(pReceiver))
            oBuilder.Subject = pSubject
            oBuilder.Text = pBody

            Dim oMail As IMail = oBuilder.Create()
            Dim oResult As Limilabs.Client.SMTP.ISendMessageResult = pClient.SendMessage(oMail)

            If oResult.Status = Limilabs.Client.SMTP.SendMessageStatus.Success Then
                Logger.Debug("Message sent successful!")
                Return True

            Else
                Logger.Warn("Message sending failed. Status: [{0}]", oResult.Status)
                Return False

            End If

        Catch ex As Exception
            Logger.Warn("Message sending failed.")
            Logger.Error(ex)
            Return False

        End Try
    End Function

    ''' <summary>
    ''' Loads an eml file from disk and returns the IMail representation of it
    ''' </summary>
    ''' <param name="pFileName">Path to the eml file</param>
    ''' <returns>The IMail object</returns>
    Public Function Load_Email(pFileName As String) As IMail
        Dim oFileName As String = MaybeConvert_MsgToEml(pFileName)
        Return MailBuilder.CreateFromEmlFile(oFileName)
    End Function

    ''' <summary>
    ''' Removes all attachments from an EML file and saves it to a temporary file.
    ''' </summary>
    ''' <param name="pFileName">The EML file to process.</param>
    ''' <param name="pSuffix">The optional suffix to add to the original filename.</param>
    ''' <returns>The path of the new EML without attachments.</returns>
    Public Function Remove_AttachmentsFromEmail(pFileName As String, Optional pSuffix As String = "") As String
        Try
            ' Convert possible msg file to eml
            Dim oEmlPath As String = MaybeConvert_MsgToEml(pFileName)

            ' Clean and version new path
            Dim oTempPath As String = Path.Combine(Path.GetTempPath(), Add_FilenameSuffix(oEmlPath, pSuffix, ".eml"))
            Dim oCleanedPath As String = FileEx.GetCleanPath(oTempPath)
            Dim oVersionedPath As String = FileEx.GetVersionedFilename(oCleanedPath)

            ' Load eml file
            Dim oMail = MailBuilder.CreateFromEmlFile(oEmlPath)

            ' Remove attachments and save
            oMail.RemoveAttachments()
            oMail.Save(oVersionedPath)

            Return oVersionedPath
        Catch ex As Exception
            Logger.Error(ex)

            Return Nothing
        End Try
    End Function

    Public Function Save_AttachmentsToDisk(pFileName As String) As List(Of String)
        Try
            Dim oAttachmentPaths As New List(Of String)
            Dim oEmlFile As String = MaybeConvert_MsgToEml(pFileName)
            Dim oMail = MailBuilder.CreateFromEmlFile(oEmlFile)
            Dim oTempPath As String = IO.Path.GetTempPath()

            If oMail.Attachments.Count = 0 Then
                Return New List(Of String)
            End If

            For Each oAttachment As MimeData In oMail.Attachments
                Dim oPath As String = IO.Path.Combine(oTempPath, oAttachment.SafeFileName)
                Dim oVersionedPath As String = FileEx.GetVersionedFilename(oPath)

                oAttachment.Save(oVersionedPath)
                oAttachmentPaths.Add(oVersionedPath)
                TempFiles.Add(oVersionedPath)
            Next

            Return oAttachmentPaths
        Catch ex As Exception
            Logger.Error(ex)

            Return New List(Of String)
        End Try
    End Function

    Public Sub Clear_TempFiles()
        Logger.Info("Cleaning [{0}] email temp files", TempFiles.Count)
        For Each oFile In TempFiles
            Try
                IO.File.Delete(oFile)
            Catch ex As Exception
                Logger.Warn("Could not clean temp file [{0}]", oFile)
                Logger.Error(ex)
            End Try
        Next

        TempFiles.Clear()
    End Sub

    Public Function Get_MessageDate(Mail As IMail) As Date
        Try
            Dim oDate = Mail.Date
            Return oDate
        Catch ex As Exception
            Logger.Warn("Could not get Date from Mail [{0}]", Mail.MessageID)
            Logger.Error(ex)
            Return Date.MinValue
        End Try
    End Function

    Public Function Get_MessageSender(Mail As IMail) As String
        Try
            Dim oAddress = Mail.From.First()

            If oAddress Is Nothing Then
                Logger.Warn("Could not get MessageSender from Mail [{0}]", Mail.MessageID)
                Return Nothing
            End If

            Return oAddress.Address

        Catch ex As Exception
            Logger.Warn("Could not get MessageSender from Mail [{0}]", Mail.MessageID)
            Logger.Error(ex)
            Return Nothing

        End Try
    End Function

    Public Function Get_MessageReceiver(Mail As IMail) As String
        Try
            Dim oAddress = Mail.To.FirstOrDefault()

            If oAddress Is Nothing Then
                Logger.Warn("Could not get MessageReceiver from Mail [{0}]", Mail.MessageID)
                Return Nothing
            End If

            Dim oMailBox = oAddress.GetMailboxes().First()

            If oMailBox Is Nothing Then
                Logger.Warn("Could not get MessageReceiver from Mail [{0}]", Mail.MessageID)
                Return Nothing
            End If

            Return oMailBox.Address

        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing

        End Try
    End Function

    Public Function Convert_ToEml(pFilePath As String) As String
        Return MaybeConvert_MsgToEml(pFilePath)
    End Function

    Private Function MaybeConvert_MsgToEml(pEmailFilePath As String) As String
        Dim oInfo As New FileInfo(pEmailFilePath)

        If oInfo.Extension.ToUpper = ".EML" Then
            Return pEmailFilePath

        ElseIf oInfo.Extension.ToUpper = ".MSG" Then
            Return DoConvertMsgToEmlFile(pEmailFilePath)

        Else
            Return Nothing

        End If
    End Function

    Private Function DoConvertMsgToEmlFile(pMsgFile As String) As String
        Try
            Logger.Debug("Converting Msg file to Eml: [{0}]", pMsgFile?.ToString)

            Dim oTempPath As String = IO.Path.GetTempPath()
            Dim oFileNameWithoutExtension = Path.GetFileNameWithoutExtension(pMsgFile)
            Dim oFileNameWithoutInvalidChars = StringEx.RemoveInvalidCharacters(oFileNameWithoutExtension)
            Dim oEmlPath As String = IO.Path.Combine(oTempPath, $"{oFileNameWithoutInvalidChars}.eml")
            Dim oVersionedPath As String = FileEx.GetVersionedFilename(oEmlPath)

            Logger.Debug("New Path for Eml file: [{0}]", oVersionedPath)

            Using oConverter As New MsgConverter(pMsgFile)
                Logger.Debug("Converter created")
                Dim oEmail As IMail = oConverter.CreateMessage()
                Logger.Debug("Message created")
                Dim oData As Byte() = oEmail.Render()
                Logger.Debug("Message rendered")

                oEmail.Save(oVersionedPath)
                Logger.Debug("Message saved")
            End Using

            Logger.Info("Msg File successfully converted to Eml: [{0}]", oVersionedPath)
            TempFiles.Add(oVersionedPath)
            Return oVersionedPath

        Catch ex As Exception
            Logger.Warn("Converting Msg to Eml file failed!")
            Logger.Error(ex)

            Return Nothing
        End Try
    End Function

    Private Function Add_FilenameSuffix(pFilename As String, pSuffix As String, Optional pExtension As String = Nothing)
        Dim oFileInfo As New FileInfo(pFilename)
        Dim oExtension As String = oFileInfo.Extension

        If pExtension IsNot Nothing Then
            If pExtension.StartsWith(".") = False Then
                oExtension = $".{pExtension}"
            Else
                oExtension = pExtension
            End If
        End If

        Dim oFileNameWithoutExtension = Path.GetFileNameWithoutExtension(pFilename)

        Return $"{oFileNameWithoutExtension}{pSuffix}{oExtension}"
    End Function
End Class
