Imports System.Net.Security
Imports DigitalData.Modules.Base
Imports DigitalData.Modules.Logging
Imports Limilabs.Client
Imports Limilabs.Client.IMAP
Imports Limilabs.Client.SMTP
Imports Microsoft.Identity.Client

Namespace Mail
    Public Class MailSession
        Inherits BaseClass

        Public ReadOnly Client As ClientBase

        Public Const AUTH_SSL = "SSL"
        Public Const AUTH_STARTTLS = "STARTTLS"
        Public Const AUTH_SSLTLS = "SSL/TLS"
        Public Const AUTH_NONE = "NONE"
        Public Const AUTH_OAUTH2 = "OAUTH2"

        Private Const SMTP_IGNORED_ERRORS As SslPolicyErrors = SslPolicyErrors.RemoteCertificateChainErrors Or SslPolicyErrors.RemoteCertificateNameMismatch

        Private _Session As SessionInfo

        Public ReadOnly Property Session As SessionInfo
            Get
                Return _Session
            End Get
        End Property

        Public Sub New(pLogConfig As LogConfig, pClient As ClientBase)
            MyBase.New(pLogConfig)
            Client = pClient
        End Sub

        Public Class SessionInfo
            Public Server As String
            Public Port As Integer
            Public User As String
            Public Password As String
            Public AuthType As String

            Public ClientId As String
            Public TenantId As String
            Public ClientSecret As String

            Public [Error] As Exception
            Public Connected As Boolean = False
        End Class

        Public Class MailSessionOptions
            Public Property EnableDefault As Boolean = True
            Public Property EnableTls1_1 As Boolean = False
            Public Property EnableTls1_2 As Boolean = False

            ' Not available in .NET4.6.2
            'Public Property EnableTls1_3 As Boolean = False
        End Class

        ''' <summary>
        ''' Start a connection with the specified server and return the session info.
        ''' </summary>
        ''' <param name="pServer"></param>
        ''' <param name="pPort"></param>
        ''' <param name="pUser"></param>
        ''' <param name="pPassword"></param>
        ''' <param name="pAuthType"></param>
        ''' <returns></returns>
        Public Function ConnectToServerWithBasicAuth(pServer As String, pPort As Integer, pUser As String, pPassword As String, pAuthType As String, pOptions As MailSessionOptions) As SessionInfo
            Dim oSession = New SessionInfo With {
 .Server = pServer,
 .Port = pPort,
 .User = pUser,
 .Password = pPassword,
 .AuthType = pAuthType
 }

            Logger.Debug("Connecting to Server..")
            Logger.Debug("Server: [{0}]", oSession.Server)
            Logger.Debug("Port: [{0}]", oSession.Port)
            Logger.Debug("User: [{0}]", oSession.User)
            Logger.Debug("AuthType: [{0}]", oSession.AuthType)

            _Session = oSession

            Logger.Debug("Initializing Connection with Auth type [{0}].", oSession.AuthType)

            Return ConnectToServer(oSession, pOptions)
        End Function

        Public Function ConnectToServerWithO365OAuth(pUser As String, pClientId As String, pTenantId As String, pClientSecret As String, pOptions As MailSessionOptions) As SessionInfo
            ' Choose server/port based on the client type
            Dim oServer As String = If(TypeOf Client Is Smtp, "smtp-mail.outlook.com", OAuth2.O365_SERVER_IMAP)
            Dim port As Integer = If(TypeOf Client Is Imap, 993, If(TypeOf Client Is Smtp, 587, 993))

            Dim oSession = New SessionInfo With {
 .Server = oServer,
 .Port = port,
 .ClientId = pClientId,
 .ClientSecret = pClientSecret,
 .TenantId = pTenantId,
 .User = pUser,
 .AuthType = AUTH_OAUTH2
 }

            Logger.Debug("Connecting to Server..")
            Logger.Debug("Server: [{0}]", oSession.Server)
            Logger.Debug("Port: [{0}]", oSession.Port)
            Logger.Debug("User: [{0}]", oSession.User)
            Logger.Debug("ClientId: [{0}]", oSession.ClientId)
            Logger.Debug("TenantId: [{0}]", oSession.TenantId)
            Logger.Debug("AuthType: [{0}]", oSession.AuthType)

            _Session = oSession

            Logger.Debug("Initializing Connection with Auth type [{0}].", oSession.AuthType)

            Return ConnectToServer(oSession, pOptions)
        End Function

        Private Function ConnectToServer(pSession As SessionInfo, pOptions As MailSessionOptions) As SessionInfo
            AddHandler Client.ServerCertificateValidate, AddressOf Session_ServerCertificateValidate

            If pOptions.EnableDefault Then
                Logger.Debug("Enabling Default TLS Version")
                Client.SSLConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Default
            Else
                Logger.Debug("Disabling Default TLS Version")
                Client.SSLConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.None
            End If

            ' Set TLS Version manually if requested
            If pOptions.EnableTls1_1 Then
                Logger.Debug("Enabling TLS Version1.1")
                Client.SSLConfiguration.EnabledSslProtocols = Client.SSLConfiguration.EnabledSslProtocols Or System.Security.Authentication.SslProtocols.Tls11
            End If

            If pOptions.EnableTls1_2 Then
                Logger.Debug("Enabling TLS Version1.2")
                Client.SSLConfiguration.EnabledSslProtocols = Client.SSLConfiguration.EnabledSslProtocols Or System.Security.Authentication.SslProtocols.Tls12
            End If

            Logger.Debug("Enabled Encryption Protocols: [{0}]", Client.SSLConfiguration.EnabledSslProtocols)

            If pSession.AuthType = AUTH_OAUTH2 Then
                Try
                    If TypeOf Client Is Imap Then
                        Dim oClient As Imap = DirectCast(Client, Imap)
                        Logger.Debug("Connecting with [OAuth2/IMAP/ConnectSSL] on [{0}/{1}]", pSession.Server, If(pSession.Port > 0, pSession.Port, 993))
                        oClient.ConnectSSL(pSession.Server) ', If(pSession.Port > 0, pSession.Port, 993)

                    ElseIf TypeOf Client Is Smtp Then
                        Dim s As Smtp = DirectCast(Client, Smtp)
                        Logger.Debug("Connecting with [OAuth2/SMTP/STARTTLS] on [{0}/{1}]", pSession.Server, If(pSession.Port > 0, pSession.Port, 587))
                        s.Connect(pSession.Server, If(pSession.Port > 0, pSession.Port, 587))
                        s.StartTLS()
                    End If

                Catch ex As Exception
                    Logger.Warn("Unexpected Error in ConnectToServer with Auth type OAuth2!")
                    Logger.Error(ex)

                    pSession.Error = ex
                    Return pSession
                End Try

            ElseIf pSession.AuthType = AUTH_SSL Then
                Try
                    ' SSL: always connect with SSL immediately
                    Logger.Debug("Connecting with [SSL/ConnectSSL] on [{0}/{1}]", pSession.Server, pSession.Port)
                    Client.ConnectSSL(pSession.Server, pSession.Port)
                    Logger.Debug("Connection (AUTH_SSL) Successful!")

                Catch ex As Exception
                    Logger.Warn("Unexpected Error in ConnectToServer with Auth type SSL!")
                    Logger.Warn($"Error-message: {ex.Message}")
                    Logger.Error(ex)

                    pSession.Error = ex
                    Return pSession
                End Try

            ElseIf pSession.AuthType = AUTH_SSLTLS Then

                Try
                    ' SSL/TLS: same as SSL, explicit branch for clarity
                    Logger.Debug("Connecting with [SSL/TLS/ConnectSSL] on [{0}/{1}]", pSession.Server, pSession.Port)
                    Client.ConnectSSL(pSession.Server, pSession.Port)
                    Logger.Debug("Connection (AUTH_SSLTLS) Successful!")

                Catch ex As Exception
                    Logger.Warn("Unexpected Error in ConnectToServer with Auth type SSL/TLS!")
                    Logger.Error(ex)

                    pSession.Error = ex
                    Return pSession
                End Try

            ElseIf pSession.AuthType = AUTH_STARTTLS Then

                Try
                    ' STARTTLS: connect plain and then upgrade
                    Logger.Debug("Connecting with [STARTTLS/Connect] on [{0}/{1}]", pSession.Server, pSession.Port)
                    ' Für Azure Communication Services: Explizite TLS-Konfiguration
                    If pSession.Server.Contains("azurecomm.net") Then
                        Logger.Debug("Azure Communication Services detected - forcing TLS 1.2")
                        ' Überschreibe alle vorherigen Einstellungen
                        Client.SSLConfiguration.EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12
                        Logger.Debug("Forced Encryption Protocols: [{0}]", Client.SSLConfiguration.EnabledSslProtocols)
                    End If


                    Client.Connect(pSession.Server, pSession.Port)

                    Dim oSupportsSTARTTLS As Boolean = SupportsSTARTTLS(Client)
                    Logger.Debug("Server supports STARTTLS: [{0}]", oSupportsSTARTTLS)

                    If oSupportsSTARTTLS Then
                        DoSTARTTLS(Client)
                        Logger.Debug("STARTTLS Successful!")
                    Else
                        Throw New ApplicationException("Server does not support STARTTLS on this endpoint.")
                    End If

                Catch ex As Exception
                    Logger.Warn("Unexpected Error in ConnectToServer with Auth type STARTTLS!")
                    Logger.Error(ex)

                    pSession.Error = ex
                    Return pSession
                End Try

            Else
                Try
                    Logger.Debug("Auth type [{0}]. Using PLAINTEXT connection.", pSession.AuthType)
                    Client.Connect(pSession.Server, pSession.Port, useSSL:=False)

                Catch ex As Exception
                    Logger.Warn("Unexpected Error in ConnectToServer with Auth type [{0}]!", pSession.AuthType)
                    Logger.Error(ex)

                    pSession.Error = ex
                    Return pSession
                End Try

            End If

            Try
                If pSession.User <> String.Empty Then
                    Logger.Info("Logging in with user [{0}] and Auth Type [{1}]", pSession.User, pSession.AuthType)

                    If pSession.AuthType = AUTH_OAUTH2 Then
                        ' Use OAuth2 token
                        DoUseBestLogin_OAuth2(Client, pSession)
                    Else
                        DoUseBestLogin_BasicAuth(Client, pSession.User, pSession.Password)
                    End If

                End If
            Catch ex As Exception
                Logger.Warn("Unexpected Error in ConnectToServer with Auth type [{0}]!", pSession.AuthType)
                Logger.Error(ex)

                pSession.Error = ex
                Return pSession
            End Try

            pSession.Connected = True

            Return pSession
        End Function

        Public Function DisconnectFromServer() As Boolean
            Try
                If Client IsNot Nothing Then
                    Logger.Debug("Closing connection to Server [{0}].", Session.Server)
                    DoClose(Client)
                    Session.Connected = False
                    Logger.Info("Connection to Server [{0}] closed.", Session.Server)
                Else
                    Logger.Debug("No connection currently open. Exiting.")
                End If

                Return True

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

            End Try
        End Function

        Public Function TestLogin(pServer As String, pPort As Integer, pUser As String, pPassword As String, pAuthType As String, pOptions As MailSessionOptions) As Boolean
            Dim oInfo = ConnectToServerWithBasicAuth(pServer, pPort, pUser, pPassword, pAuthType, pOptions)
            If oInfo.Connected Then
                If DisconnectFromServer() Then
                    Return True
                Else
                    Logger.Warn("Login Test failed while disconnecting.")
                    Return False
                End If
            Else
                Logger.Warn("Login Test Failed while connecting.")
                Return False
            End If
        End Function

        Private Function SupportsSTARTTLS(pClient As ClientBase) As Boolean
            If TypeOf pClient Is Smtp Then
                Return DirectCast(pClient, Smtp).SupportedExtensions.Contains(SmtpExtension.StartTLS)
            ElseIf TypeOf pClient Is Imap Then
                Return DirectCast(pClient, Imap).SupportedExtensions.Contains(ImapExtension.StartTLS)
            Else
                Logger.Error("Unknown session type: [{0}]", pClient.GetType.ToString)
                Return False
            End If
        End Function

        Private Sub DoClose(pClient As ClientBase)
            If TypeOf pClient Is Smtp Then
                DirectCast(pClient, Smtp).Close()
            ElseIf TypeOf pClient Is Imap Then
                DirectCast(pClient, Imap).Close()
            Else
                Logger.Error("Unknown session type: [{0}]", pClient.GetType.ToString)
            End If
        End Sub

        Private Sub DoUseBestLogin_BasicAuth(pClient As ClientBase, pUserName As String, pPassword As String)
            Logger.Debug("Logging in with Simple Auth")
            Logger.Debug("Username format: [{0}]", If(pUserName.Contains("@"), "Email format", "Other format"))

            If TypeOf pClient Is Smtp Then
                Dim smtp = DirectCast(pClient, Smtp)

                ' Zeige unterstützte Auth-Methoden (nur Logging, keine Funktionsänderung)
                Try
                    Dim supportedExtensions = smtp.SupportedExtensions()
                    Logger.Debug("Server supported extensions count: [{0}]", supportedExtensions.Count)
                    If supportedExtensions.Count > 0 Then
                        Logger.Debug("Server supported extensions: [{0}]", String.Join(", ", supportedExtensions.Select(Function(x) x.ToString())))
                    End If
                Catch ex As Exception
                    Logger.Debug("Could not retrieve supported extensions: {0}", ex.Message)
                End Try

                ' Azure Communication Services Detection (nur Logging)
                If _Session?.Server?.Contains("azurecomm.net") Then
                    Logger.Debug("Azure Communication Services detected")
                    Logger.Debug("Note: Username should be '<endpoint>.communication.azure.com'")
                    Logger.Debug("Note: Password should be the Access Key from Azure Portal")
                End If

                ' Ursprüngliche Funktionalität bleibt unverändert
                smtp.UseBestLogin(pUserName, pPassword)

            ElseIf TypeOf pClient Is Imap Then
                DirectCast(pClient, Imap).UseBestLogin(pUserName, pPassword)
            Else
                Logger.Error("Unknown session type: [{0}]", pClient.GetType.ToString)
            End If
        End Sub

        Private Sub DoUseBestLogin_OAuth2(pClient As ClientBase, pSession As SessionInfo)
            Logger.Debug("Logging in with O365 OAuth2")

            Dim oOAuth = New OAuth2(LogConfig, pSession.TenantId, pSession.ClientId, pSession.ClientSecret)
            Dim oAccessToken = oOAuth.GetAccessToken()

            ' Diagnose token shape (delegated vs app-only)
            Try
                Dim parts = oAccessToken.Split("."c)
                If parts.Length = 3 Then
                    Dim payloadJson = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(PadBase64(parts(1))))
                    If payloadJson.Contains("""scp""") Then
                        Logger.Debug("OAuth2 token contains 'scp' (delegated) – IMAP App-only erwartet 'roles'.")
                    ElseIf payloadJson.Contains("""roles""") Then
                        Logger.Debug("OAuth2 token contains 'roles' (application permissions).")
                    Else
                        Logger.Debug("OAuth2 token payload hat weder 'scp' noch 'roles'.")
                    End If
                End If
            Catch ex As Exception
                Logger.Debug("Token-Payload konnte nicht gelesen werden: {0}", ex.Message)
            End Try

            If TypeOf pClient Is Imap Then
                Dim i = DirectCast(pClient, Imap)

                ' Login mit Hilfsmethode; bei Abbruch explizit SASL XOAUTH2 probieren
                Try
                    ' Direkt versuchen, falls die verwendete Version LoginOAUTH2 unterstützt.
                    i.LoginOAUTH2(pSession.User, oAccessToken)
                Catch ex As Exception
                    Logger.Warn("LoginOAUTH2 (IMAP) fehlgeschlagen: {0}", ex.Message)
                    Throw
                End Try
            ElseIf TypeOf pClient Is Smtp Then
                Dim s = DirectCast(pClient, Smtp)
                Try
                    s.LoginOAUTH2(pSession.User, oAccessToken)
                Catch ex As Exception
                    Logger.Warn("LoginOAUTH2 (SMTP) fehlgeschlagen: {0}", ex.Message)
                    Throw
                End Try
            Else
                Logger.Error("Unknown session type: [{0}]", pClient.GetType.ToString)
            End If
        End Sub

        Private Shared Function PadBase64(input As String) As String
            ' Normalize Base64 URL-safe and pad to multiple of 4
            If input Is Nothing Then Return String.Empty
            Dim normalized As String = input.Replace("-", "+").Replace("_", "/")
            Dim remainder As Integer = normalized.Length Mod 4
            If remainder > 0 Then
                normalized &= New String("="c, 4 - remainder)
            End If
            Return normalized
        End Function

        Private Sub DoSTARTTLS(pClient As ClientBase)
            If TypeOf pClient Is Smtp Then
                DirectCast(pClient, Smtp).StartTLS()
            ElseIf TypeOf pClient Is Imap Then
                DirectCast(pClient, Imap).StartTLS()
            Else
                Logger.Error("Unknown session type: [{0}]", pClient.GetType.ToString)
            End If
        End Sub

        Private Sub Session_ServerCertificateValidate(sender As Object, e As ServerCertificateValidateEventArgs)
            Logger.Debug("Certificate validation for: [{0}]", e.Certificate?.Subject)
            Logger.Debug("SSL Policy Errors: [{0}]", e.SslPolicyErrors)

            If (e.SslPolicyErrors And Not SMTP_IGNORED_ERRORS) = SslPolicyErrors.None Then
                e.IsValid = True
            Else
                Logger.Warn("Certificate validation failed. Errors: [{0}]", e.SslPolicyErrors)
                e.IsValid = False
            End If
        End Sub
    End Class
End Namespace

