Shared/Import-XRefLookupData.ps1

#.ExternalHelp StreamXRef-help.xml
function Import-XRefLookupData {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Medium", DefaultParameterSetName = "General")]
    [OutputType([System.Void], [StreamXRef.ImportResults])]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ParameterSetName = "General")]
        [Alias("PSPath")]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "ApiKey")]
        [ValidateNotNullOrEmpty()]
        [string]$ApiKey,

        [Parameter(ParameterSetName = "General")]
        [switch]$PassThru,

        [Parameter()]
        [switch]$Quiet,

        [Parameter()]
        [switch]$Force
    )

    Begin {

        if ($Force -and -not $PSBoundParameters.ContainsKey("Confirm")) {

            $ConfirmPreference = "None"

        }

        # Check for required resources
        if ([StreamXRef.ImportResults] -isnot [type] -or -not (Test-Path Variable:Script:TwitchData)) {

            throw "Missing required internal resources. Ensure module was loaded correctly."

        }

        # Initial states for ShouldContinue
        $YesToAll = $false
        $NoToAll = $false

    }

    Process {

        if ($PSCmdlet.ParameterSetName -eq "General") {

            # Temporarily override ErrorActionPreference during required setup
            $EAPrefSetting = $ErrorActionPreference
            $ErrorActionPreference = "Stop"

            # This will now terminate the script if it fails
            $ConfigStaging = Get-Content $Path -Raw | ConvertFrom-Json

            # Set up counters object
            $Counters = [StreamXRef.ImportResults]::new()
            $Counters.AddCounter("User")
            $Counters.AddCounter("Clip")
            $Counters.AddCounter("Video")

            # Restore ErrorActionPreference
            $ErrorActionPreference = $EAPrefSetting

        }

        # Process ApiKey (Check parameter set first since ConfigStaging won't exist in the ApiKey set)
        if ($PSCmdlet.ParameterSetName -eq "ApiKey" -or ($ConfigStaging.psobject.Properties.Name -contains "ApiKey" -and -not [string]::IsNullOrWhiteSpace($ConfigStaging.ApiKey))) {

            # Check if current API key is not set
            if ([string]::IsNullOrWhiteSpace($script:TwitchData.ApiKey)) {
                # API key is not set

                # Specify "Import" since there's nothing being replaced
                if ($PSCmdlet.ShouldProcess("API key", "Import")) {

                    if ($PSCmdlet.ParameterSetName -eq "ApiKey") {

                        # Handling import via ApiKey parameter
                        $script:TwitchData.ApiKey = $ApiKey

                        if (-not $Quiet) {

                            Write-Host "API key imported."

                        }

                        return

                    }
                    else {

                        # Import API key from input object
                        $script:TwitchData.ApiKey = $ConfigStaging.ApiKey

                        if (-not $Quiet) {

                            Write-Host "API key imported."

                        }

                    }

                }

            }
            else {
                # API key already exists

                if ($PSCmdlet.ParameterSetName -eq "ApiKey") {

                    # Get key via ApiKey parameter
                    $NewApiKey = $ApiKey

                }
                else {

                    # Get key from input object
                    $NewApiKey = $ConfigStaging.ApiKey

                }

                # Check if new key is different
                if ($script:TwitchData.ApiKey -ine $NewApiKey) {

                    # Specify "Replace" since previous value will be replaced
                    if ($PSCmdlet.ShouldProcess("API key", "Replace")) {

                        if ($PSCmdlet.ParameterSetName -eq "ApiKey") {

                            $script:TwitchData.ApiKey = $NewApiKey

                            if (-not $Quiet) {

                                Write-Host "API key replaced."

                            }

                            return

                        }
                        else {

                            $script:TwitchData.ApiKey = $NewApiKey

                            if (-not $Quiet) {

                                Write-Host "API key replaced."

                            }

                        }

                    }

                }
                elseif (-not $Quiet) {

                    Write-Host "API key is unchanged."

                }

            }

        }
        elseif ([string]::IsNullOrWhiteSpace($script:TwitchData.ApiKey) -and $script:TwitchData.GetTotalCount() -eq 0) {

            # Lookup data cache is empty
            # Assume user is trying to restore from a full export
            Write-Error "API key missing from input."

        }
        else {

            # Already contains API key
            # User may have wanted to not keep the key in the JSON file, so not an error
            Write-Warning "API key missing from input."

        }

        # Process UserInfoCache
        if ($ConfigStaging.psobject.Properties.Name -contains "UserInfoCache" -and $ConfigStaging.UserInfoCache.Count -gt 0) {

            # Check for confirm status here instead of for every single entry
            if ($PSCmdlet.ShouldProcess("User ID lookup data", "Import")) {

                $ConfigStaging.UserInfoCache | ForEach-Object {

                    try {

                        # Check if entry already exists
                        if ($script:TwitchData.UserInfoCache.ContainsKey($_.name)) {

                            # If so, is the data the same?
                            if ($script:TwitchData.UserInfoCache[$_.name] -eq $_.id) {

                                # Already exists and can be skipped
                                $Counters.User.Skipped++

                            }
                            else {

                                Write-Warning "For $($_.name): $($_.id) -> $($script:TwitchData.UserInfoCache[$_.name])"

                                # Exists, but data is different
                                # Unless -Force is specified, ask how to continue becuase this should only occur due to data corruption
                                if ($Force -or $PSCmdlet.ShouldContinue("Input data entry differs from existing data", "Overwrite with new value?", [ref]$YesToAll, [ref]$NoToAll)) {

                                    # Overwrite
                                    $script:TwitchData.UserInfoCache[$_.name] = $_.id
                                    $Counters.User.Imported++

                                }
                                else {

                                    $Counters.User.Error++

                                }

                            }

                        }
                        else {

                            # New data to add
                            $script:TwitchData.UserInfoCache[$_.name] = $_.id
                            $Counters.User.Imported++

                        }

                    }
                    catch [System.Management.Automation.PSInvalidCastException], [System.FormatException],
                    [System.Management.Automation.PropertyNotFoundException] {

                        # Data formatting errors
                        Write-Error "(User Data) $($_.Exception.Message)" -Category InvalidData
                        $Counters.User.Error++

                    }
                    catch {

                        # Halt to prevent potential data corruption from unknown error
                        $PSCmdlet.ThrowTerminatingError($_)

                    }

                }

                Write-Verbose "(User Data) $($Counters.User.Imported) entries imported."
                if ($Counters.User.Skipped -gt 0) {
                    Write-Verbose "(User Data) $($Counters.User.Skipped) duplicate entries skipped."
                }
                if ($Counters.User.Error -gt 0) {
                    Write-Verbose "(User Data) $($Counters.User.Error) entries could not be parsed or conflicted with existing data."
                }

            }

        }
        else {

            Write-Error "User lookup data missing from input."

        }

        # Process ClipInfoCache
        if ($ConfigStaging.psobject.Properties.Name -contains "ClipInfoCache" -and $ConfigStaging.ClipInfoCache.Count -gt 0) {

            if ($PSCmdlet.ShouldProcess("Clip info lookup data", "Import")) {

                $ConfigStaging.ClipInfoCache | ForEach-Object {

                    try {

                        # Enforce casting to [int]
                        [int]$NewOffsetValue = $_.offset
                        [int]$NewVideoIDValue = $_.video
                        $ConvertedDateTime = $_.created | ConvertTo-UtcDateTime

                        if ($script:TwitchData.ClipInfoCache.ContainsKey($_.slug)) {

                            # Shorter variable for using in the "if" statements and warning message
                            $ExistingObject = $script:TwitchData.ClipInfoCache[$_.slug]

                            if ($ExistingObject.Offset -eq $NewOffsetValue -and $ExistingObject.VideoID -eq $NewVideoIDValue -and $ExistingObject.Created -eq $ConvertedDateTime) {

                                $Counters.Clip.Skipped++

                            }
                            else {

                                Write-Warning (
                                    "For $($_.slug):`n",
                                    "[new] $NewOffsetValue, $NewVideoIDValue, $ConvertedDateTime`n",
                                    "[old] $($ExistingObject.Offset), $($ExistingObject.VideoID), $($ExistingObject.Created)" -join ""
                                )

                                if ($Force -or $PSCmdlet.ShouldContinue("Input data entry differs from existing data", "Overwrite with new value?", [ref]$YesToAll, [ref]$NoToAll)) {

                                    $script:TwitchData.ClipInfoCache[$_.slug] = [pscustomobject]@{ Offset = $NewOffsetValue; VideoID = $NewVideoIDValue; Created = $ConvertedDateTime }
                                    $Counters.Clip.Imported++

                                }
                                else {

                                    $Counters.Clip.Error++

                                }

                            }

                        }
                        else {

                            # New data to add
                            $script:TwitchData.ClipInfoCache[$_.slug] = [pscustomobject]@{ Offset = $NewOffsetValue; VideoID = $NewVideoIDValue; Created = $ConvertedDateTime }
                            $Counters.Clip.Imported++

                        }

                    }
                    catch [System.Management.Automation.PSInvalidCastException], [System.FormatException],
                    [System.Management.Automation.PropertyNotFoundException] {

                        Write-Error "(Clip Data) $($_.Exception.Message)" -Category InvalidData
                        $Counters.Clip.Error++

                    }
                    catch {

                        # Halt to prevent potential data corruption from unknown error
                        $PSCmdlet.ThrowTerminatingError($_)

                    }

                }

                Write-Verbose "(Clip Data) $($Counters.Clip.Imported) entries imported."
                if ($Counters.Clip.Skipped -gt 0) {
                    Write-Verbose "(Clip Data) $($Counters.Clip.Skipped) duplicate entries skipped."
                }
                if ($Counters.Clip.Error -gt 0) {
                    Write-Verbose "(Clip Data) $($Counters.Clip.Error) entries could not be parsed or conflicted with existing data."
                }

            }

        }
        else {

            Write-Error "Clip lookup data missing from input."

        }

        # Process VideoInfoCache
        if ($ConfigStaging.psobject.Properties.Name -contains "VideoInfoCache" -and $ConfigStaging.VideoInfoCache.Count -gt 0) {

            if ($PSCmdlet.ShouldProcess("Video timestamp lookup data", "Import")) {

                $ConfigStaging.VideoInfoCache | ForEach-Object {

                    try {

                        $ConvertedDateTime = $_.timestamp | ConvertTo-UtcDateTime

                        if ($script:TwitchData.VideoInfoCache.ContainsKey($_.video)) {

                            if ($script:TwitchData.VideoInfoCache[$_.video] -eq $ConvertedDateTime) {

                                $Counters.Video.Skipped++

                            }
                            else {

                                Write-Warning "For $($_.video): $ConvertedDateTime -> $($script:TwitchData.VideoInfoCache[$_.video])"

                                if ($Force -or $PSCmdlet.ShouldContinue("Input data entry differs from existing data", "Overwrite with new value?", [ref]$YesToAll, [ref]$NoToAll)) {

                                    $script:TwitchData.VideoInfoCache[$_.video] = $ConvertedDateTime
                                    $Counters.Video.Imported++

                                }
                                else {

                                    $Counters.Video.Error++

                                }

                            }

                        }
                        else {

                            # New data to add
                            $script:TwitchData.VideoInfoCache[$_.video] = $ConvertedDateTime
                            $Counters.Video.Imported++

                        }

                    }
                    catch [System.Management.Automation.PSInvalidCastException], [System.FormatException],
                    [System.Management.Automation.PropertyNotFoundException] {

                        Write-Error "(Video Data) $($_.Exception.Message)" -Category InvalidData
                        $Counters.Video.Error++

                    }
                    catch {

                        # Halt to prevent potential data corruption from unknown error
                        $PSCmdlet.ThrowTerminatingError($_)

                    }

                }

                Write-Verbose "(Video Data) $($Counters.Video.Imported) entries imported."
                if ($Counters.Video.Skipped -gt 0) {
                    Write-Verbose "(Video Data) $($Counters.Video.Skipped) duplicate entries skipped."
                }
                if ($Counters.Video.Error -gt 0) {
                    Write-Verbose "(Video Data) $($Counters.Video.Error) entries could not be parsed or conflicted with existing data."
                }

            }

        }
        else {

            Write-Error "Video lookup data missing from input."

        }

    }

    End {

        if ($PSCmdlet.ParameterSetName -eq "General") {

            if (-not $Quiet) {

                # Display import results
                $Counters.Values | Format-Table -AutoSize | Out-Host

            }

            if ($PassThru) {

                return $Counters

            }
            else {

                return

            }

        }

    }

}