#####################################################
# HelloID-Conn-Prov-Target-AFAS-Profit-Employees-Update
# PowerShell V2
#####################################################

# Set to true at start, because only when an error occurs it is set to false
$outputContext.Success = $true

# AccountReference must have a value for dryRun
$aRef = $actionContext.References.Account

# Set debug logging
switch ($($actionContext.Configuration.isDebug)) {
    $true { $VerbosePreference = 'Continue' }
    $false { $VerbosePreference = 'SilentlyContinue' }
}

# Enable TLS1.2
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12

#region functions
function Resolve-HTTPError {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
            ValueFromPipeline
        )]
        [object]$ErrorObject
    )
    process {
        $httpErrorObj = [PSCustomObject]@{
            FullyQualifiedErrorId = $ErrorObject.FullyQualifiedErrorId
            MyCommand             = $ErrorObject.InvocationInfo.MyCommand
            RequestUri            = $ErrorObject.TargetObject.RequestUri
            ScriptStackTrace      = $ErrorObject.ScriptStackTrace
            ErrorMessage          = ''
        }
        if ($ErrorObject.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') {
            $httpErrorObj.ErrorMessage = $ErrorObject.ErrorDetails.Message
        }
        elseif ($ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException') {
            $httpErrorObj.ErrorMessage = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream()).ReadToEnd()
        }
        Write-Output $httpErrorObj
    }
}

function Resolve-AFASErrorMessage {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
            ValueFromPipeline
        )]
        [object]$ErrorObject
    )
    process {
        try {
            $errorObjectConverted = $ErrorObject | ConvertFrom-Json -ErrorAction Stop

            if ($null -ne $errorObjectConverted.externalMessage) {
                $errorMessage = $errorObjectConverted.externalMessage
            }
            else {
                $errorMessage = $errorObjectConverted
            }
        }
        catch {
            $errorMessage = "$($ErrorObject.Exception.Message)"
        }

        Write-Output $errorMessage
    }
}

function Get-ErrorMessage {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
            ValueFromPipeline
        )]
        [object]$ErrorObject
    )
    process {
        $errorMessage = [PSCustomObject]@{
            VerboseErrorMessage = $null
            AuditErrorMessage   = $null
        }

        if ( $($ErrorObject.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or $($ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException')) {
            $httpErrorObject = Resolve-HTTPError -ErrorObject $ErrorObject
            
            if (-not[String]::IsNullOrEmpty($httpErrorObject.ErrorMessage)) {
                $errorMessage.VerboseErrorMessage = $httpErrorObject.ErrorMessage
                $errorMessage.AuditErrorMessage = Resolve-AFASErrorMessage -ErrorObject $httpErrorObject.ErrorMessage
            }
            else {
                $errorMessage.VerboseErrorMessage = $ErrorObject.Exception.Message
                $errorMessage.AuditErrorMessage = $ErrorObject.Exception.Message
            }
        }
        
        # If error message empty, fall back on $ex.Exception.Message
        if ([String]::IsNullOrEmpty($errorMessage.VerboseErrorMessage)) {
            $errorMessage.VerboseErrorMessage = $ErrorObject.Exception.Message
        }
        if ([String]::IsNullOrEmpty($errorMessage.AuditErrorMessage)) {
            $errorMessage.AuditErrorMessage = $ErrorObject.Exception.Message
        }

        Write-Output $errorMessage
    }
}
#endregion functions
try {
    $account = $actionContext.Data

    $correlationProperty = $actionContext.CorrelationConfiguration.accountField
    $correlationValue = $actionContext.References.Account.Medewerker # Has to match the AFAS value of the specified filter field ($filterfieldid)
       
    $updateAccountFields = @()
    if ($account.PSObject.Properties.Name -Contains 'EmAd') {
        $updateAccountFields += "EmAd"
    }
    if ($account.PSObject.Properties.Name -Contains 'EmailPortal') {
        $updateAccountFields += "EmailPortal"
    }
    if ($account.PSObject.Properties.Name -Contains 'TeNr') {
        $updateAccountFields += "TeNr"
    }
    if ($account.PSObject.Properties.Name -Contains 'MbNr') {
        $updateAccountFields += "MbNr"
    }

    # Verify if [aRef] has a value
    if ([string]::IsNullOrEmpty($($actionContext.References.Account))) {
        $outputContext.AuditLogs.Add([PSCustomObject]@{
                Action  = "UpdateAccount"
                Message = "The account reference could not be found"
                IsError = $true
            })
        
        throw 'The account reference could not be found'
    }
    
    if (($actionContext.Configuration.onlyUpdateOnCorrelate -eq $false) -or ($actionContext.AccountCorrelated -eq $true)) {
        # Get current account and verify if there are changes
        try {
            Write-Verbose "Querying AFAS employee where [$($correlationProperty)] = [$($correlationValue)]"

            # Create authorization headers
            $encodedToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($actionContext.Configuration.Token))
            $authValue = "AfasToken $encodedToken"
            $Headers = @{ Authorization = $authValue }
            $Headers.Add("IntegrationId", "45963_140664") # Fixed value - Tools4ever Partner Integration ID

            $splatWebRequest = @{
                Uri             = "$($actionContext.Configuration.BaseUri)/connectors/$($actionContext.Configuration.GetConnector)?filterfieldids=$($correlationProperty)&filtervalues=$($correlationValue)&operatortypes=1"
                Headers         = $headers
                Method          = 'GET'
                ContentType     = "application/json;charset=utf-8"
                UseBasicParsing = $true
            }
            $currentAccount = (Invoke-RestMethod @splatWebRequest -Verbose:$false).rows

            if ($null -eq $currentAccount.Medewerker) {
                throw "No AFAS employee found AFAS employee where [$($correlationProperty)] = [$($correlationValue)]"
            }


            # Retrieve current account data for properties to be updated
            $previousAccount = [PSCustomObject]@{
                # E-Mail werk  
                'EmAd'        = $currentAccount.Email_werk
                # E-mail toegang
                'EmailPortal' = $currentAccount.Email_portal
                # Telefoonnr. werk
                'TeNr'        = $currentAccount.Telefoonnr_werk
                # Mobiel werk
                'MbNr'        = $currentAccount.Mobielnr_werk
            }


            # Calculate changes between current data and provided data
            $splatCompareProperties = @{
                ReferenceObject  = @($previousAccount.PSObject.Properties | Where-Object { $_.Name -in $updateAccountFields }) # Only select the properties to update
                DifferenceObject = @($account.PSObject.Properties | Where-Object { $_.Name -in $updateAccountFields }) # Only select the properties to update
            }
            $changedProperties = $null
            $changedProperties = (Compare-Object @splatCompareProperties -PassThru)
            $oldProperties = $changedProperties.Where( { $_.SideIndicator -eq '<=' })
            $newProperties = $changedProperties.Where( { $_.SideIndicator -eq '=>' })

            if (($newProperties | Measure-Object).Count -ge 1) {
                Write-Verbose "Changed properties: $($changedProperties | ConvertTo-Json)"

                $updateAction = 'Update'
            }
            else {
                Write-Verbose "No changed properties"
    
                $updateAction = 'NoChanges'
            }
        }
        catch {
            $ex = $PSItem
            $errorMessage = Get-ErrorMessage -ErrorObject $ex

            Write-Verbose "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"

            $outputContext.AuditLogs.Add([PSCustomObject]@{
                    Action  = "UpdateAccount"
                    Message = "Error querying AFAS employee where [$($correlationProperty)] = [$($correlationValue)]. Error Message: $($errorMessage.AuditErrorMessage)"
                    IsError = $true
                })

            # Skip further actions, as this is a critical error
            throw "Error querying AFAS employee"
        }

        switch ($updateAction) {
            'Update' {
                # Update AFAS Employee
                try {
                    # Create custom object with old and new values
                    $changedPropertiesObject = [PSCustomObject]@{
                        OldValues = @{}
                        NewValues = @{}
                    }

                    # Add the old properties to the custom object with old and new values
                    foreach ($oldProperty in ($oldProperties | Where-Object { $_.Name -in $newProperties.Name })) {
                        $changedPropertiesObject.OldValues.$($oldProperty.Name) = $oldProperty.Value
                    }

                    # Add the new properties to the custom object with old and new values
                    foreach ($newProperty in $newProperties) {
                        $changedPropertiesObject.NewValues.$($newProperty.Name) = $newProperty.Value
                    }
                    Write-Verbose "Changed properties: $($changedPropertiesObject | ConvertTo-Json)"

                    # Create custom account object for update and set with default properties and values
                    $updateAccount = [PSCustomObject]@{
                        'AfasEmployee' = @{
                            'Element' = @{
                                '@EmId'   = $currentAccount.Medewerker
                                'Objects' = @(@{
                                        'KnPerson' = @{
                                            'Element' = @{
                                                'Fields' = @{
                                                    # Zoek op BcCo (Persoons-ID)
                                                    'MatchPer' = 0
                                                    # Nummer
                                                    'BcCo'     = $currentAccount.Persoonsnummer
                                                }
                                            }
                                        }
                                    })
                            }
                        }
                    }

                    # Add the updated properties to the custom account object for update - Only add changed properties. AFAS will throw an error when trying to update this with the same value
                    foreach ($newProperty in $newProperties ) {
                        $updateAccount.AfasEmployee.Element.Objects[0].KnPerson.Element.Fields.$($newProperty.Name) = $newProperty.Value
                    }

                    $body = ($updateAccount | ConvertTo-Json -Depth 10)
                    $splatWebRequest = @{
                        Uri             = "$($actionContext.Configuration.BaseUri)/connectors/$($actionContext.Configuration.UpdateConnector)"
                        Headers         = $headers
                        Method          = 'PUT'
                        Body            = ([System.Text.Encoding]::UTF8.GetBytes($body))
                        ContentType     = "application/json;charset=utf-8"
                        UseBasicParsing = $true
                    }

                    if (-Not($actionContext.DryRun -eq $true)) {
                        Write-Verbose "Updating AFAS employee [$($currentAccount.Medewerker)]. Old values: $($changedPropertiesObject.oldValues | ConvertTo-Json -Depth 10). New values: $($changedPropertiesObject.newValues | ConvertTo-Json -Depth 10)"
                    
                        $updatedAccount = Invoke-RestMethod @splatWebRequest -Verbose:$false

                        # Set aRef object for use in futher actions
                        $aRef = [PSCustomObject]@{
                            Medewerker     = $currentAccount.Medewerker
                            Persoonsnummer = $currentAccount.Persoonsnummer
                        }
                    
                        $outputContext.AuditLogs.Add([PSCustomObject]@{
                                Action  = "UpdateAccount"
                                Message = "Successfully updated AFAS employee [$($currentAccount.Medewerker)]. Old values: $($changedPropertiesObject.oldValues | ConvertTo-Json -Depth 10). New values: $($changedPropertiesObject.newValues | ConvertTo-Json -Depth 10)"
                                IsError = $false
                            })
                    }
                    else {
                        Write-Warning "DryRun: Would update AFAS employee [$($currentAccount.Medewerker)]. Old values: $($changedPropertiesObject.oldValues | ConvertTo-Json -Depth 10). New values: $($changedPropertiesObject.newValues | ConvertTo-Json -Depth 10)"
                    }
                }
                catch {
                    $ex = $PSItem
                    $errorMessage = Get-ErrorMessage -ErrorObject $ex
                
                    Write-Verbose "Error at Line '$($ex.InvocationInfo.ScriptLineNumber)': $($ex.InvocationInfo.Line). Error: $($errorMessage.VerboseErrorMessage)"
            
                    $outputContext.AuditLogs.Add([PSCustomObject]@{
                            Action  = "UpdateAccount"
                            Message = "Error updating AFAS employee [$($currentAccount.Medewerker)]. Error Message: $($errorMessage.AuditErrorMessage). Old values: $($changedPropertiesObject.oldValues | ConvertTo-Json -Depth 10). New values: $($changedPropertiesObject.newValues | ConvertTo-Json -Depth 10)"
                            IsError = $true
                        })
                    # Skip further actions, as this is a critical error
                    throw "Error updating AFAS employee"
                }

                break
            }
            'NoChanges' {
                Write-Verbose "No changes needed for AFAS employee [$($currentAccount.Medewerker)]"

                if (-Not($actionContext.DryRun -eq $true)) {
                    # Set aRef object for use in futher actions
                    $aRef = [PSCustomObject]@{
                        Medewerker     = $currentAccount.Medewerker
                        Persoonsnummer = $currentAccount.Persoonsnummer
                    }

                    $outputContext.AuditLogs.Add([PSCustomObject]@{
                            Action  = "UpdateAccount"
                            Message = "No changes needed for AFAS employee [$($currentAccount.Medewerker)]"
                            IsError = $false
                        })
                }
                else {
                    Write-Warning "DryRun: No changes needed for AFAS employee [$($currentAccount.Medewerker)]"
                }

                break
            }
        }
    }
    else {
        $previousAccount = $account
        Write-Verbose "The configuration parameter only update on correlate is [$($actionContext.Configuration.onlyUpdateOnCorrelate)]"
    }
}
catch {
    $ex = $PSItem
    Write-Verbose "ERROR: $ex"
}
finally {
    # Check if auditLogs contains errors, if errors are found, set succes to false
    if ($outputContext.AuditLogs.IsError -contains $true) {
        $outputContext.Success = $false
    }
    # Define ExportData with account fields and correlation property 
    $exportData = $account.PsObject.Copy()
    # Add correlation property to exportdata
    $exportData | Add-Member -MemberType NoteProperty -Name $correlationProperty -Value $correlationValue -Force
    # Add aRef properties to exportdata
    foreach ($aRefProperty in $aRef.PSObject.Properties) {
        $exportData | Add-Member -MemberType NoteProperty -Name $aRefProperty.Name -Value $aRefProperty.Value -Force
    }
    $outputContext.AccountReference = $aRef
    $outputContext.Data = $exportData
    $outputContext.PreviousData = $previousAccount
}