Imports DigitalData.Modules.Database
Imports DigitalData.Modules.Logging
Imports DigitalData.Modules.Language

Namespace SyncUsers
    Public Class SyncUsersMSSQL
        Implements ISyncUsers

        Private _logConfig As LogConfig
        Private _logger As Logger
        Private _mssql As MSSQLServer

        Private Const ADDED_WHO = "Active Directory Sync"

        Public Sub New(LogConfig As LogConfig, MSSQL As MSSQLServer)
            _logConfig = LogConfig
            _logger = LogConfig.GetLogger()
            _mssql = MSSQL
        End Sub

        Public Function SyncUsers(GroupName As String, Users As List(Of ADUser), PropertyMapping As List(Of AttributeMapping)) As List(Of ADUser) Implements ISyncUsers.SyncUsers
            Dim oGroupId As Integer
            Dim oSyncedUsers As New List(Of ADUser)
            Dim oSyncedUserIds As New List(Of Int64)

            Dim oCreatedUsers As New List(Of ADUser)
            Dim oUpdatedUsers As New List(Of ADUser)

            Try
                _logger.Debug("Getting group Id for group {0}", GroupName)
                oGroupId = GetGroupId(GroupName)

                If oGroupId = 0 Then
                    _logger.Debug("Group {0} does not exist in database. Exiting.", GroupName)
                    Return oSyncedUsers
                End If

                _logger.Debug("Using group Id {0}", oGroupId)
            Catch ex As Exception
                _logger.Error(ex)
                Return oSyncedUsers
            End Try

            For Each oUser In Users
                Dim oUserId As Int64
                Dim oUserExists As Boolean

                ' Check if user already exists
                Try
                    _logger.Debug("Checking if user [{0}] exists", oUser)
                    oUserId = GetUserId(oUser.samAccountName)
                    oUserExists = oUserId > 0
                    _logger.Debug("User [{0}] exists in database: [{1}]", oUser, oUserExists)
                Catch ex As Exception
                    _logger.Error(ex)
                    _logger.Warn("Could not get UserId for user. Skipping.")
                    Continue For
                End Try

                ' Collect user ids from existing users
                If oUserExists Then
                    oSyncedUserIds.Add(oUserId)
                End If

                ' Create or update user
                Try
                    If Not oUserExists Then
                        _logger.Debug("Creating new user for [{0}]", oUser)
                        oUserId = CreateUser(oUser)
                        _logger.Debug("User created with Id [{0}]", oUserId)
                        _logger.Info("Added new User [{0}]", oUser.samAccountName)

                        oCreatedUsers.Add(oUser)
                    Else
                        _logger.Debug("Updating user [{0}]", oUser)
                        oUserId = UpdateUser(oUser)
                        If oUserId <> 0 Then
                            _logger.Debug("User created with Id [{0}]", oUserId)
                            _logger.Info("Updated User [{0}]", oUser.samAccountName)

                            oUpdatedUsers.Add(oUser)
                        End If
                    End If

                Catch ex As Exception
                    _logger.Error(ex)
                    _logger.Warn("Could Not create/update user [{0}]. Skipping.", oUser.samAccountName)
                    Continue For
                End Try

                ' Add custom attributes to user
                Try
                    AddCustomAttributesToUser(oUser, oUserId)
                Catch ex As Exception
                    _logger.Error(ex)
                    _logger.Debug("Could Not add custom attributes to user {0}. Continuing.", oUser)
                End Try

                ' Add the user to group
                Try
                    If AddUserToGroup(oUserId, oGroupId) Then
                        _logger.Info("User [{0}] added to group [{1}]", oUser.samAccountName, GroupName)
                    End If
                Catch ex As Exception
                    _logger.Error(ex)
                    _logger.Warn("Could Not add user {0} to group {1}. Skipping.", oUser, GroupName)
                    Continue For
                End Try

                oSyncedUsers.Add(oUser)
            Next

            ' Delete users that are assigned to the group but no longer exist in active directory
            Dim oUserIdString = String.Join(",", oSyncedUserIds)
            If oSyncedUserIds.Count = 0 Then
                oUserIdString = 0
            End If
            Dim oSQL As String = $"DELETE FROM TBDD_GROUPS_USER WHERE USER_ID NOT IN ({oUserIdString}) AND GROUP_ID = {oGroupId}"
            Dim oDeletedRelations = _mssql.GetScalarValue(oSQL)
            If oCreatedUsers.Count > 0 Then
                _logger.Info("Created [{0}] new users", oCreatedUsers.Count)
            End If
            _logger.Info("Updated [{0}] users", oUpdatedUsers.Count)
            If oDeletedRelations > 0 Then
                _logger.Info("Removed [{0}] users from Group [{1}]", oDeletedRelations, GroupName)
            End If


            Return oSyncedUsers
        End Function

        Private Function AddUserToGroup(UserId As Integer, GroupId As Integer) As Boolean Implements ISyncUsers.AddUserToGroup
            Try
                Dim oSQL = $"SELECT COUNT(*) FROM TBDD_GROUPS_USER WHERE USER_ID = {UserId} And GROUP_ID = {GroupId}"
                Dim oResult = True
                If _mssql.GetScalarValue(oSQL) = 0 Then
                    oSQL = $"INSERT INTO TBDD_GROUPS_USER (USER_ID, GROUP_ID, ADDED_WHO) VALUES ({UserId}, {GroupId}, '{ADDED_WHO}')"
                    oResult = _mssql.ExecuteNonQuery(oSQL)
                Else
                    _logger.Debug($"UserGroup-Relation [{UserId}/{GroupId}] already existing")
                    Return False
                End If

                If oResult = False Then
                    Throw New Exception("Error while adding user to group!")
                End If

                Return True
            Catch ex As Exception
                _logger.Error(ex)
                Throw ex
            End Try
        End Function

        Private Function GetGroupId(GroupName As String) As Integer Implements ISyncUsers.GetGroupId
            Try
                Dim oSQL As String = $"SELECT GUID FROM TBDD_GROUPS WHERE UPPER(NAME) = UPPER('{GroupName}') AND AD_SYNC = 1 AND ACTIVE = 1"
                Dim oGroupId = _mssql.GetScalarValue(oSQL)

                If IsDBNull(oGroupId) OrElse oGroupId = 0 Then
                    _logger.Debug("Group {0} not found in database.", GroupName)
                    Return 0
                End If

                Return oGroupId
            Catch ex As Exception
                _logger.Error(ex)
                Throw ex
            End Try
        End Function

        Private Function GetUserId(UserName As String) As Integer Implements ISyncUsers.GetUserId
            Try
                Dim oSQL As String = $"SELECT GUID FROM TBDD_USER WHERE UPPER(USERNAME) = UPPER('{UserName}')"
                Dim oUserId = _mssql.GetScalarValue(oSQL)

                If IsDBNull(oUserId) OrElse oUserId = 0 Then
                    Return 0
                End If

                Return oUserId
            Catch ex As Exception
                _logger.Error(ex)
                Throw ex
            End Try
        End Function

        Private Function CreateUser(User As ADUser) As Integer Implements ISyncUsers.CreateUser
            Try
                If User Is Nothing Then
                    _logger.Warn("Argument [User] is nothing. Exiting.")
                    Throw New ArgumentNullException("User")
                End If

                Dim oUserId As Integer = GetUserId(User.samAccountName)

                If oUserId = 0 Then
                    Dim oSQL As String = $"INSERT INTO TBDD_USER (PRENAME, NAME, USERNAME, EMAIL, ADDED_WHO) VALUES ('{User?.GivenName}', '{User?.Surname?.Replace("'", "''")}', UPPER('{User?.samAccountName?.Replace("'", "''")}'), '{User?.Email?.Replace("'", "''")}', '{ADDED_WHO}')"
                    Dim oResult = _mssql.ExecuteNonQuery(oSQL)

                    If oResult = True Then
                        oUserId = _mssql.GetScalarValue("SELECT MAX(GUID) FROM TBDD_USER")
                        Return oUserId
                    Else
                        Throw New Exception($"Error while inserting user {User.samAccountName}!")
                    End If
                Else
                    Return oUserId
                End If

            Catch ex As Exception
                _logger.Error(ex)
                Throw ex
            End Try
        End Function

        Private Function UpdateUser(User As ADUser) As Integer
            Try
                If User Is Nothing Then
                    _logger.Warn("Error in UpdateUser - User object is nothing")
                    Return 0
                End If

                If User.samAccountName Is Nothing Then
                    _logger.Warn("Error in UpdateUser - User samAccountName is nothing")
                    Return 0
                End If

                Dim oUserId As Integer = GetUserId(User.samAccountName)
                If Not IsNothing(oUserId) Then
                    If oUserId > 0 Then
                        Dim oGivenName As String = EscapeQuotes(User.GivenName)
                        Dim oSurname As String = EscapeQuotes(User.Surname)
                        Dim oEmail As String = EscapeQuotes(User.Email)

                        Dim oSQL As String = $"UPDATE TBDD_USER SET PRENAME = '{oGivenName}', NAME = '{oSurname}', EMAIL = '{oEmail}', CHANGED_WHO = '{ADDED_WHO}' WHERE GUID = {oUserId}"
                        Dim oResult = _mssql.ExecuteNonQuery(oSQL)

                        If oResult = True Then
                            Return oUserId
                        Else
                            Throw New Exception($"Error while updating user {User.samAccountName}!")
                        End If
                    Else
                        Return oUserId
                    End If
                Else
                    _logger.Warn("Error in UpdateUser - Could not get a userid for samAccountName: " + User.samAccountName)
                    Return 0
                End If

            Catch ex As Exception
                _logger.Error(ex)
                Throw ex
            End Try
        End Function

        Private Function EscapeQuotes(pString As String)
            Dim oString = Utils.NotNull(pString, String.Empty)
            Return oString.Replace("'", "''")
        End Function

        Public Sub AddCustomAttributesToUser(User As ADUser, UserId As Integer) Implements ISyncUsers.AddCustomAttributesToUser
            Dim oCustomAttributes = User.CustomAttributes

            _logger.Debug("Adding {0} Custom Attributes to User {1}", oCustomAttributes.Count, User)

            For Each oAttribute In oCustomAttributes
                _logger.Debug("Adding Custom Attribute [{0}] with value [{1}] to User [{2}]", oAttribute.MSSQLColumn, oAttribute.Value, User)
                Dim oSQL As String = $"UPDATE TBDD_USER SET {oAttribute.MSSQLColumn} = '{oAttribute.Value}', CHANGED_WHO = '{ADDED_WHO}' WHERE GUID = {UserId}"
                Dim oResult = _mssql.ExecuteNonQuery(oSQL)

                If oResult = False Then
                    _logger.Debug("Custom Attribute {0} could not be added to user {1}", oAttribute.Name, User.samAccountName)
                    Continue For
                End If
            Next
        End Sub
    End Class

End Namespace


