function Write-TokenReplacement
{
    param (
        [Parameter()]
        [System.String]
        $Token,

        # Parameter help description
        [Parameter()]
        [System.String]
        $Value,

        # Parameter help description
        [Parameter()]
        [System.String]
        $FilePath
    )

    $content = Get-Content -Path $FilePath
    $content = $content.Replace($Token, $Value)
    Set-Content -Path $FilePath -Value $content
}

function Get-M365DSCHashAsString
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param(
        [Parameter(Mandatory = $true)]
        [System.Collections.Hashtable]
        $Values
    )
    $sb = [System.Text.StringBuilder]::New()
    foreach ($key in $Values.Keys)
    {
        switch ($Values.$key.GetType().Name)
        {
            'String'
            {
                $sb.AppendLine("                        $key = `"$($Values.$key)`"") | Out-Null
            }
            'Int32'
            {
                $sb.AppendLine("                        $key = $($Values.$key)") | Out-Null
            }
            'UInt32'
            {
                $sb.AppendLine("                        $key = $($Values.$key)") | Out-Null
            }
            'Boolean'
            {
                $sb.AppendLine("                        $key = `$$($Values.$key)") | Out-Null
            }
            'String[]'
            {
                $stringValue = ''
                foreach ($item in $Values.$key)
                {
                    $stringValue += "`"$item`","
                }
                $stringValue = $stringValue.Substring(0, $stringValue.Length - 1)
                $sb.AppendLine("                        $key = `@($stringValue)") | Out-Null
            }
        }
    }
    return $sb.ToString()
}

function Get-M365DSCFakeValues
{
    [OutputType([System.Collections.Hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [System.Object[]]
        $ParametersInformation,

        [Parameter()]
        [System.Boolean]
        $IntroduceDrift = $false
    )

    $result = @{}
    foreach ($parameter in $parametersInformation)
    {
        switch ($parameter.Type.Name)
        {
            'String'
            {
                if ($IntroduceDrift)
                {
                    if ($parameter.ValidValues.Length -eq 0)
                    {
                        $result.Add($parameter.Name, 'FakeStringValue')
                    }
                    else
                    {
                        $result.Add($parameter.Name, $parameter.ValidValues[1])
                    }
                }
                else
                {
                    if ($parameter.ValidValues.Length -eq 0)
                    {
                        $result.Add($parameter.Name, 'FakeStringValue2')
                    }
                    else
                    {
                        $result.Add($parameter.Name, $parameter.ValidValues[0])
                    }
                }
            }
            'String[]'
            {
                if ($IntroduceDrift)
                {
                    if ($parameter.ValidValues.Length -eq 0)
                    {
                        $result.Add($parameter.Name, @('FakeStringArrayValue1'))
                    }
                    else
                    {
                        $result.Add($parameter.Name, @($parameter.ValidValues[0]))
                    }
                }
                else
                {
                    if ($parameter.ValidValues.Length -eq 0)
                    {
                        $result.Add($parameter.Name, @('FakeStringArrayValue1', 'FakeStringArrayValue2'))
                    }
                    else
                    {
                        $result.Add($parameter.Name, @($parameter.ValidValues[0], $parameter.ValidValues[1]))
                    }
                }
            }
            'Int32'
            {
                if ($IntroduceDrift)
                {
                    $result.Add($parameter.Name, 7)
                }
                else
                {
                    $result.Add($parameter.Name, 25)
                }
            }
            'UInt32'
            {
                if ($IntroduceDrift)
                {
                    $result.Add($parameter.Name, 7)
                }
                else
                {
                    $result.Add($parameter.Name, 25)
                }
            }
            'Boolean'
            {
                if ($IntroduceDrift)
                {
                    $result.Add($parameter.Name, $false)
                }
                else
                {
                    $result.Add($parameter.Name, $true)
                }
            }
        }
    }
    return $result
}

function New-ExoUnitTest
{
    param (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ResourceName,

        [Parameter(Mandatory = $true)]
        [System.String]
        $OutputPath,

        [Parameter(Mandatory = $false)]
        [System.String]
        $CmdletNoun = ''
    )

    if ($ResourceName.StartsWith('MSFT_'))
    {
        $ResourceName = $ResourceName.Substring(5)
    }

    if ($CmdletNoun -eq '')
    {
        $CmdletNoun = $ResourceName.Substring(3)
    }

    # Get properties of resource
    $ignoredProperties = @('ErrorVariable', `
            'ErrorAction', `
            'InformationVariable', `
            'InformationAction', `
            'WarningVariable', `
            'WarningAction', `
            'OutVariable', `
            'OutBuffer', `
            'PipelineVariable', `
            'Verbose', `
            'WhatIf', `
            'Debug',
        'Credential',
        'ApplicationId',
        'Ensure',
        'TenantId',
        'CertificateThumbprint',
        'CertificatePath',
        'CertificatePassword',
        'IsSingleInstance')

    try
    {
        Import-Module $('..\DSCResources\MSFT_' + $ResourceName + '\MSFT_' + $ResourceName + '.psm1') -Force -ErrorAction Stop
    }
    catch
    {
        Write-Host "DSC resource $ResourceName not found!"
        break;
    }

    # Copy unit test template
    try
    {
        $unitTestPath = "$OutputPath\Microsoft365DSC.$($ResourceName).Tests.ps1"
        Copy-Item -Path ..\..\..\ResourceGenerator\UnitTest.Template.ps1 -Destination $unitTestPath -ErrorAction Stop
    }
    catch
    {
        Write-Host 'Cannot create unit test file!'
        break;
    }

    $parameterInformation = @()
    $resourceProperties = (Get-Command Set-TargetResource -Module $("MSFT_$ResourceName")).Parameters
    foreach ($key in $resourceProperties.Keys)
    {
        if ($resourceProperties[$key].Name -notin $ignoredProperties)
        {
            $param = @{Name = $resourceProperties[$key].Name; Type = $resourceProperties[$key].ParameterType; ValidValues = $resourceProperties[$key].Attributes.ValidValues }
            $parameterInformation += $param
        }
    }
    Remove-Module -Name $("MSFT_$ResourceName") -Force -Confirm:$false


    # build unit test
    $fakeValues = Get-M365DSCFakeValues -ParametersInformation $parameterInformation -IntroduceDrift $false
    $fakeValuesString = Get-M365DSCHashAsString -Values $fakeValues
    Write-TokenReplacement -Token '<FakeValues>' -value $fakeValuesString -FilePath $unitTestPath

    $fakeDriftValues = Get-M365DSCFakeValues -ParametersInformation $parameterInformation -IntroduceDrift $true
    $fakeDriftValuesString = Get-M365DSCHashAsString -Values $fakeDriftValues
    Write-TokenReplacement -Token '<DriftValues>' -value $fakeDriftValuesString -FilePath $unitTestPath

    Write-TokenReplacement -Token '<ResourceName>' -value $ResourceName -FilePath $unitTestPath

    Write-TokenReplacement -Token '<GetCmdletName>' -value "Get-$($CmdletNoun)" -FilePath $unitTestPath
    Write-TokenReplacement -Token '<SetCmdletName>' -value "Set-$($CmdletNoun)" -FilePath $unitTestPath
    Write-TokenReplacement -Token '<RemoveCmdletName>' -value "Remove-$($CmdletNoun)" -FilePath $unitTestPath
    Write-TokenReplacement -Token '<NewCmdletName>' -value "New-$($CmdletNoun)" -FilePath $unitTestPath
}
