#!powershell

# Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#Requires -Module Ansible.ModuleUtils.ArgvParser
#Requires -Module Ansible.ModuleUtils.CommandUtil
#Requires -Module Ansible.ModuleUtils.Legacy

$ErrorActionPreference = "Stop"

$params = Parse-Args -arguments $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$diff = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false

$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent", "disabled", "present"

$admin_only = Get-AnsibleParam -obj $params -name "admin_only" -type "bool"
$allow_self_service = Get-AnsibleParam -obj $params -name "allow_self_service" -type "bool"
$bypass_proxy = Get-AnsibleParam -obj $params -name "bypass_proxy" -type "bool"
$certificate = Get-AnsibleParam -obj $params -name "certificate" -type "str"
$certificate_password = Get-AnsibleParam -obj $params -name "certificate_password" -type "str"
$priority = Get-AnsibleParam -obj $params -name "priority" -type "int"
$source = Get-AnsibleParam -obj $params -name "source" -type "str" -failifempty ($state -ne "absent")
$source_username = Get-AnsibleParam -obj $params -name "source_username" -type "str"
$source_password = Get-AnsibleParam -obj $params -name "source_password" -type "str" -failifempty ($null -ne $source_username)
$update_password = Get-AnsibleParam -obj $params -name "update_password" -type "str" -default "always" -validateset "always", "on_create"

$result = @{
    changed = $false
}
if ($diff) {
    $result.diff = @{
        before = @{}
        after = @{}
    }
}

Function Get-ChocolateySources {
    param($choco_app)

    $choco_config_path = "$(Split-Path -Path (Split-Path -Path $choco_app.Path))\config\chocolatey.config"
    if (-not (Test-Path -LiteralPath $choco_config_path)) {
        Fail-Json -obj $result -message "Expecting Chocolatey config file to exist at '$choco_config_path'"
    }

    # would prefer to enumerate the existing sources with an actual API but the
    # only stable interface is choco.exe source list and that does not output
    # the sources in an easily parsable list. Using -r will split each entry by
    # | like a psv but does not quote values that have a | already in it making
    # it inadequete for our tasks. Instead we will parse the chocolatey.config
    # file and get the values from there
    try {
        [xml]$choco_config = Get-Content -Path $choco_config_path
    } catch {
        Fail-Json -obj $result -message "Failed to parse Chocolatey config file at '$choco_config_path': $($_.Exception.Message)"
    }

    $sources = [System.Collections.ArrayList]@()
    foreach ($xml_source in $choco_config.chocolatey.sources.GetEnumerator()) {
        $source_username = $xml_source.Attributes.GetNamedItem("user")
        if ($null -ne $source_username) {
            $source_username = $source_username.Value
        }

        # 0.9.9.9+
        $priority = $xml_source.Attributes.GetNamedItem("priority")
        if ($null -ne $priority) {
            $priority = [int]$priority.Value
        }

        # 0.9.10+
        $certificate = $xml_source.Attributes.GetNamedItem("certificate")
        if ($null -ne $certificate) {
            $certificate = $certificate.Value
        }

        # 0.10.4+
        $bypass_proxy = $xml_source.Attributes.GetNamedItem("bypassProxy")
        if ($null -ne $bypass_proxy) {
            $bypass_proxy = [System.Convert]::ToBoolean($bypass_proxy.Value)
        }
        $allow_self_service = $xml_source.Attributes.GetNamedItem("selfService")
        if ($null -ne $allow_self_service) {
            $allow_self_service = [System.Convert]::ToBoolean($allow_self_service.Value)
        }

        # 0.10.8+
        $admin_only = $xml_source.Attributes.GetNamedItem("adminOnly")
        if ($null -ne $admin_only) {
            $admin_only = [System.Convert]::ToBoolean($admin_only.Value)
        }

        $source_info = @{
            name = $xml_source.id
            source = $xml_source.value
            disabled = [System.Convert]::ToBoolean($xml_source.disabled)
            source_username = $source_username
            priority = $priority
            certificate = $certificate
            bypass_proxy = $bypass_proxy
            allow_self_service = $allow_self_service
            admin_only = $admin_only
        }
        $sources.Add($source_info) > $null
    }
    return ,$sources
}

Function New-ChocolateySource {
    param(
        $choco_app,
        $name,
        $source,
        $source_username,
        $source_password,
        $certificate,
        $certificate_password,
        $priority,
        $bypass_proxy,
        $allow_self_service,
        $admin_only
    )
    # build the base arguments
    $arguments = [System.Collections.ArrayList]@($choco_app.Path,
        "source", "add", "--name", $name, "--source", $source
    )

    # add optional arguments from user input
    if ($null -ne $source_username) {
        $arguments.Add("--user") > $null
        $arguments.Add($source_username) > $null
        $arguments.Add("--password") > $null
        $arguments.Add($source_password) > $null
    }
    if ($null -ne $certificate) {
        $arguments.Add("--cert") > $null
        $arguments.Add($certificate) > $null
    }
    if ($null -ne $certificate_password) {
        $arguments.Add("--certpassword") > $null
        $arguments.Add($certificate_password) > $null
    }
    if ($null -ne $priority) {
        $arguments.Add("--priority") > $null
        $arguments.Add($priority) > $null
    } else {
        $priority = 0
    }
    if ($bypass_proxy -eq $true) {
        $arguments.Add("--bypass-proxy") > $null
    } else {
        $bypass_proxy = $false
    }
    if ($allow_self_service -eq $true) {
        $arguments.Add("--allow-self-service") > $null
    } else {
        $allow_self_service = $false
    }
    if ($admin_only -eq $true) {
        $arguments.Add("--admin-only") > $null
    } else {
        $admin_only = $false
    }

    if ($check_mode) {
        $arguments.Add("--what-if") > $null
    }

    $command = Argv-ToString -arguments $arguments
    $res = Run-Command -command $command
    if ($res.rc -ne 0) {
        Fail-Json -obj $result -message "Failed to add Chocolatey source '$name': $($res.stderr)"
    }

    $source_info = @{
        name = $name
        source = $source
        disabled = $false
        source_username = $source_username
        priority = $priority
        certificate = $certificate
        bypass_proxy = $bypass_proxy
        allow_self_service = $allow_self_service
        admin_only = $admin_only
    }
    return ,$source_info
}

Function Remove-ChocolateySource {
    param(
        $choco_app,
        $name
    )
    $arguments = [System.Collections.ArrayList]@($choco_app.Path, "source", "remove", "--name", $name)
    if ($check_mode) {
        $arguments.Add("--what-if") > $null
    }
    $command = Argv-ToString -arguments $arguments
    $res = Run-Command -command $command
    if ($res.rc -ne 0) {
        Fail-Json -obj $result -message "Failed to remove Chocolatey source '$name': $($_.res.stderr)"
    }
}

$choco_app = Get-Command -Name choco.exe -CommandType Application -ErrorAction SilentlyContinue
if (-not $choco_app) {
    Fail-Json -obj $result -message "Failed to find Chocolatey installation, make sure choco.exe is in the PATH env value"
}
$actual_sources = Get-ChocolateySources -choco_app $choco_app
$actual_source = $actual_sources | Where-Object { $_.name -eq $name }
if ($diff) {
    if ($null -ne $actual_source) {
        $before = $actual_source.Clone()
    } else {
        $before = @{}
    }
    $result.diff.before = $before
}

if ($state -eq "absent" -and $null -ne $actual_source) {
    Remove-ChocolateySource -choco_app $choco_app -name $name
    $result.changed = $true
} elseif ($state -in ("disabled", "present")) {
    $change = $false
    if ($null -eq $actual_source) {
        $change = $true
    } else {
        if ($source -ne $actual_source.source) {
            $change = $true
        }
        if ($null -ne $source_username -and $source_username -ne $actual_source.source_username) {
            $change = $true
        }
        if ($null -ne $source_password -and $update_password -eq "always") {
            $change = $true
        }
        if ($null -ne $certificate -and $certificate -ne $actual_source.certificate) {
            $change = $true
        }
        if ($null -ne $certificate_password -and $update_password -eq "always") {
            $change = $true
        }
        if ($null -ne $priority -and $priority -ne $actual_source.priority) {
            $change = $true
        }
        if ($null -ne $bypass_proxy -and $bypass_proxy -ne $actual_source.bypass_proxy) {
            $change = $true
        }
        if ($null -ne $allow_self_service -and $allow_self_service -ne $actual_source.allow_self_service) {
            $change = $true
        }
        if ($null -ne $admin_only -and $admin_only -ne $actual_source.admin_only) {
            $change = $true
        }

        if ($change) {
            Remove-ChocolateySource -choco_app $choco_app -name $name
            $result.changed = $true
        }
    }

    if ($change) {
        $actual_source = New-ChocolateySource -choco_app $choco_app -name $name -source $source `
            -source_username $source_username -source_password $source_password `
            -certificate $certificate -certificate_password $certificate_password `
            -priority $priority -bypass_proxy $bypass_proxy -allow_self_service $allow_self_service `
            -admin_only $admin_only
        $result.changed = $true
    }

    # enable/disable the source if necessary
    if ($state -ne "disabled" -and $actual_source.disabled) {
        $arguments = [System.Collections.ArrayList]@($choco_app.Path, "source", "enable", "--name", $name)
        if ($check_mode) {
            $arguments.Add("--what-if") > $null
        }
        $command = Argv-ToString -arguments $arguments
        $res = Run-Command -command $command
        if ($res.rc -ne 0) {
            Fail-Json -obj $result -message "Failed to enable Chocolatey source '$name': $($res.stderr)"
        }
        $actual_source.disabled = $false
        $result.changed = $true
    } elseif ($state -eq "disabled" -and (-not $actual_source.disabled)) {
        $arguments = [System.Collections.ArrayList]@($choco_app.Path, "source", "disable", "--name", $name)
        if ($check_mode) {
            $arguments.Add("--what-if") > $null
        }
        $command = Argv-ToString -arguments $arguments
        $res = Run-Command -command $command
        if ($res.rc -ne 0) {
            Fail-Json -obj $result -message "Failed to disable Chocolatey source '$name': $($res.stderr)"
        }
        $actual_source.disabled = $true
        $result.changed = $true
    }

    if ($diff) {
        $after = $actual_source
        $result.diff.after = $after
    }
}

# finally remove the diff if there was no change
if (-not $result.changed -and $diff) {
    $result.diff = @{}
}

Exit-Json -obj $result
