#!powershell

# Copyright: (c) 2020, Brian Scholer <@briantist>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module @{ ModuleName = 'PowerShellGet' ; ModuleVersion = '1.6.0' }

$spec = @{
    supports_check_mode = $true
    options = @{
        name = @{
            type = 'str'
            required = $true
        }
        repository = @{ type = 'str' }
        scope = @{
            type = 'str'
            choices = @('all_users', 'current_user')
            default = 'all_users'
        }
        state = @{
            type = 'str'
            choices = @('present', 'absent', 'latest')
            default = 'present'
        }
        required_version = @{ type = 'str' }
        minimum_version = @{ type = 'str' }
        maximum_version = @{ type = 'str' }
        source_username = @{ type = 'str' }
        source_password = @{
            type = 'str'
            no_log = $true
        }
        allow_prerelease = @{
            type = 'bool'
            default = $false
        }
    }

    mutually_exclusive = @(
        @('required_version', 'minimum_version'),
        @('required_version', 'maximum_version')
    )

    required_together = @(
        , @('source_username', 'source_password')
    )
}

$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$state = $module.Params.state

# Enable TLS1.1/TLS1.2 if they're available but disabled (eg. .NET 4.5)
$security_protocols = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::SystemDefault
if ([System.Net.SecurityProtocolType].GetMember("Tls11").Count -gt 0) {
    $security_protocols = $security_protocols -bor [System.Net.SecurityProtocolType]::Tls11
}
if ([System.Net.SecurityProtocolType].GetMember("Tls12").Count -gt 0) {
    $security_protocols = $security_protocols -bor [System.Net.SecurityProtocolType]::Tls12
}
[System.Net.ServicePointManager]::SecurityProtocol = $security_protocols

function Get-SplattableParameter {
    [CmdletBinding(DefaultParameterSetName = 'All')]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [Ansible.Basic.AnsibleModule]
        $Module ,

        [Parameter(ParameterSetName = 'ForCommand')]
        [Alias('For')]
        [String]
        $ForCommand ,

        [Parameter(ParameterSetName = 'WithCommand', ValueFromPipeline = $true)]
        [Alias('Command')]
        [System.Management.Automation.CommandInfo]
        $WithCommand
    )

    Process {
        $ret = @{}

        $cmd = switch ($PSCmdlet.ParameterSetName) {
            'WithCommand' { $WithCommand }
            'ForCommand' { Get-Command -Name $ForCommand }
        }

        if ($cmd) {
            $commons = [System.Management.Automation.Cmdlet]::CommonParameters + [System.Management.Automation.Cmdlet]::CommonOptionalParameters
            $validParams = $cmd.Parameters.GetEnumerator() | ForEach-Object -Process {
                if ($commons -notcontains $_.Key) {
                    $_.Key
                }
            }
        }

        switch -Wildcard ($Module.Params.Keys) {
            '*' {
                $value = $Module.Params[$_]
                if ($null -eq $value) {
                    continue
                }

                $key = $_.Replace('_', '')
                if ($validParams -and $validParams -notcontains $key) {
                    continue
                }
            }

            'scope' { $value = $value.Replace('_', '') }
            'source_username' { continue } # handled in password block
            'source_password' {
                $key = 'Credential'
                $secure = ConvertTo-SecureString -String $value -AsPlainText -Force
                $value = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Module.Params.source_username, $secure
            }
            '*_version' {
                if ($Module.Params.state -eq 'latest') {
                    $Module.FailJson("version options can't be used with 'state': 'latest'")
                }
            }
            'repository' {
                if ($Module.Params.state -eq 'absent') {
                    $Module.FailJson("'repository' is not valid with 'state': 'absent'")
                }
            }

            '*' { $ret[$key] = $value }
        }

        $ret
    }
}

$pGet, $pUninstall, $pFind, $pInstall = Get-Command -Name @(
    'Get-InstalledScript'
    , 'Uninstall-Script'
    , 'Find-Script'
    , 'Install-Script'
) | Get-SplattableParameter -Module $module

$existing = Get-InstalledScript @pGet -ErrorAction SilentlyContinue
$existing = if ($existing) {
    $existing | Group-Object -Property Name -AsHashTable -AsString
}
else {
    @{}
}

if ($state -eq 'absent') {
    if ($existing.Count) {
        try {
            $module.Result.changed = $true
            if (-not $module.CheckMode) {
                $existing.Values | Uninstall-Script -Force -ErrorAction Stop
            }
        }
        catch {
            $module.FailJson("Error uninstalling scripts.", $_)
        }
    }
}
else {
    # state is 'present' or 'latest'
    try {
        $remote = Find-Script @pFind -ErrorAction Stop
    }
    catch {
        $module.FailJson("Error searching for scripts.", $_)
    }

    try {
        $toInstall = $remote | Where-Object -FilterScript {
            -not $existing.ContainsKey($_.Name) -or (
                $state -eq 'latest' -and
                ($_.Version -as [version]) -gt ($existing[$_.Name].Version -as [version])
            )
        }

        if (($toInstall | Group-Object -Property Name -NoElement | Where-Object -Property Count -gt 1)) {
            $module.FailJson("Multiple scripts found. Please choose a specific repository.")
        }

        $module.Result.changed = $toInstall -as [bool]

        if ($toInstall -and -not $module.CheckMode) {
            $toInstall | Install-Script -Scope:$pInstall.scope -Force -ErrorAction Stop
        }
    }
    catch {
        $module.FailJson("Error installing scripts.", $_)
    }
}

$module.ExitJson()
