Imports DigitalData.Modules.Base
Imports DigitalData.Modules.Logging
Imports Microsoft.Identity.Client

Namespace Mail
    Public Class OAuth2
        Inherits BaseClass

        Private ReadOnly _tenantId As String
        Private ReadOnly _clientId As String
        Private ReadOnly _clientSecret As String

        Public Const O365_SERVER_IMAP As String = "outlook.office365.com"
        Public Const O365_SERVER_SMTP As String = "smtp-mail.outlook.com"
        Public Const O365_SCOPE As String = "https://outlook.office365.com/.default"
        Public Const O365_AUTHORITY_PREFIX As String = "https://login.microsoftonline.com/"

        Public Sub New(pLogConfig As LogConfig, tenantId As String, clientId As String, clientSecret As String)
            MyBase.New(pLogConfig)
            _tenantId = tenantId
            _clientId = clientId
            _clientSecret = clientSecret
        End Sub

        Public Function GetAccessToken() As String
            ' input hardening and better diagnostics
            Dim tenantId = If(_tenantId, String.Empty).Trim()
            Dim clientId = If(_clientId, String.Empty).Trim()
            Dim clientSecret = If(_clientSecret, String.Empty).Trim()

            If String.IsNullOrWhiteSpace(tenantId) Then
                Logger.Error("OAuth2: tenantId is empty.")
                Throw New ArgumentException("tenantId is empty")
            End If
            If String.IsNullOrWhiteSpace(clientId) Then
                Logger.Error("OAuth2: clientId is empty.")
                Throw New ArgumentException("clientId is empty")
            End If
            If String.IsNullOrWhiteSpace(clientSecret) Then
                Logger.Error("OAuth2: clientSecret is empty.")
                Throw New ArgumentException("clientSecret is empty")
            End If

            ' common misconfiguration: using the Secret ID (GUID) instead of the Secret VALUE
            Dim tmpGuid As Guid
            If Guid.TryParse(clientSecret, tmpGuid) Then
                Logger.Error("OAuth2: clientSecret looks like a GUID (likely the secret ID). Use the secret VALUE instead.")
                Throw New ApplicationException("Invalid client secret: looks like Secret ID (GUID), not the secret value.")
            End If

            Try
                Dim authority = O365_AUTHORITY_PREFIX & tenantId

                Dim app = ConfidentialClientApplicationBuilder.Create(clientId).
                    WithClientSecret(clientSecret).
                    WithAuthority(authority).
                    Build()

                Dim scopes = New String() {O365_SCOPE}
                Dim result = app.AcquireTokenForClient(scopes).ExecuteAsync().GetAwaiter().GetResult()

                Logger.Info("OAuth2 token acquired for tenant {0}. ExpiresOn={1:u}", tenantId, result.ExpiresOn.UtcDateTime)
                Return result.AccessToken
            Catch ex As MsalServiceException
                ' richer diagnostics without depending on specific MSAL version members
                Logger.Error("MSAL service error. Code={0}. Message={1}", ex.ErrorCode, ex.Message)

                ' Try to log StatusCode, ResponseBody if available (via reflection to avoid version dependency)
                Try
                    Dim t = GetType(MsalServiceException)
                    Dim scProp = t.GetProperty("StatusCode")
                    If scProp IsNot Nothing Then
                        Dim sc = scProp.GetValue(ex, Nothing)
                        If sc IsNot Nothing Then Logger.Error("MSAL StatusCode={0}", sc)
                    End If
                    Dim rbProp = t.GetProperty("ResponseBody")
                    If rbProp IsNot Nothing Then
                        Dim rb = rbProp.GetValue(ex, Nothing)
                        If rb IsNot Nothing Then Logger.Error("MSAL ResponseBody={0}", rb)
                    End If
                    Dim ciProp = t.GetProperty("Claims")
                    If ciProp IsNot Nothing Then
                        Dim claims = ciProp.GetValue(ex, Nothing)
                        If claims IsNot Nothing AndAlso claims.ToString().Length > 0 Then Logger.Error("MSAL Claims={0}", claims)
                    End If
                Catch
                    ' ignore reflection diagnostics failures
                End Try

                Throw
            Catch ex As Exception
                Logger.Error(ex)
                Throw
            End Try
        End Function
    End Class
End Namespace
