modules/ISC.SalesforceConnector.psm1

#Requires -Version 7
Set-StrictMode -Version Latest

$script:SalesforceFeaturePacks = @{
    AgentDiscovery = @{
        Name        = 'AgentDiscovery'
        Label       = 'Machine identity governance - Salesforce Agentforce autonomous agents & bots'
        Description = 'Discovers autonomous Agentforce AI agents, BotDefinition records, BotVersion deployments, and prompt execution identities'
        Permissions = @(
            'View Setup and Configuration'
            'Customize Application'
            'Manage Bot Users and Agents'
            'Read on BotDefinition and BotVersion objects'
            'Manage External Client Applications'
        )
    }
    ActivityInsights = @{
        Name        = 'ActivityInsights'
        Label       = 'Activity Insights - Salesforce user & agent audit activity'
        Description = 'Gathers user and machine activity events for access reviews'
        Permissions = @(
            'View Event Log Files'
            'View All Data'
        )
    }
}

function Get-SalesforceFeaturePacks {
    return $script:SalesforceFeaturePacks
}

function Get-SalesforceAgentDiscoveryFeaturePack {
    return $script:SalesforceFeaturePacks['AgentDiscovery']
}

function Get-SalesforceLoginHost {
    param([string]$EnvironmentType = 'Production')
    switch ($EnvironmentType) {
        'Sandbox' { return 'https://test.salesforce.com' }
        default   { return 'https://login.salesforce.com' }
    }
}

function Get-SalesforceRunRecord {
    param([hashtable]$Context)
    if (-not $Context) { return $null }
    if (-not $Context.ContainsKey('SalesforceRunRecord') -or $null -eq $Context['SalesforceRunRecord']) {
        $Context['SalesforceRunRecord'] = @{
            InstanceUrl          = $null
            Username             = $null
            ConsumerKey          = $null
            ConsumerSecret       = $null
            ConnectedAppResult   = $null
            AgentforceResult     = $null
            SettingsPath         = $null
        }
    }
    return $Context['SalesforceRunRecord']
}

function Invoke-SalesforceCli {
    param(
        [Parameter(Mandatory)][string[]]$CliArgs,
        [switch]$IgnoreError
    )
    $output = & sf @CliArgs --json 2>&1
    $exit = $LASTEXITCODE
    $errors = @($output | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] } | ForEach-Object { $_.ToString() })
    $text = (@($output | Where-Object { $_ -isnot [System.Management.Automation.ErrorRecord] } | ForEach-Object { [string]$_ }) -join "`n").Trim()
    if ($exit -ne 0 -and -not $IgnoreError) {
        throw "sf $($CliArgs -join ' ') failed: $((@($errors) + @($text) | Where-Object { $_ }) -join ' ')"
    }
    if (-not $text) { return $null }
    try {
        return ($text | ConvertFrom-Json -AsHashtable -Depth 20)
    }
    catch {
        return $text
    }
}

function Connect-SalesforceOrg {
    param(
        [string]$InstanceUrl,
        [string]$Alias,
        [switch]$NonInteractive
    )
    # Check if CLI has an active auth org
    try {
        $orgList = Invoke-SalesforceCli -CliArgs @('org', 'list') -IgnoreError
        if ($orgList -and $orgList.status -eq 0 -and $orgList.result -and $orgList.result.nonScratchOrgs) {
            $matched = @($orgList.result.nonScratchOrgs | Where-Object {
                ($Alias -and $_.alias -eq $Alias) -or ($InstanceUrl -and $_.instanceUrl -eq $InstanceUrl) -or $_.isDefaultUsername
            }) | Select-Object -First 1
            if ($matched) { return $matched }
        }
    }
    catch {}

    $loginUrl = if ($InstanceUrl) { $InstanceUrl } else { 'https://login.salesforce.com' }
    if ($NonInteractive) {
        throw "The Salesforce CLI (sf) is not authenticated for $loginUrl. Run: sf org login web --instance-url $loginUrl"
    }
    Write-Info "Signing in to Salesforce org ($loginUrl). A browser window opens."
    & sf org login web --instance-url $loginUrl
    if ($LASTEXITCODE -ne 0) { throw "Salesforce sign-in failed. Run: sf org login web --instance-url $loginUrl" }
}

function Get-SalesforceIscFeatureChecklist {
    param([string[]]$FeatureNames = @())
    $items = [System.Collections.Generic.List[string]]::new()
    $features = @($FeatureNames | Where-Object { $_ })

    if ($features -contains 'AgentDiscovery') {
        $items.Add('Machine Identity Governance: Enable Salesforce Agentforce AI Agent Discovery on the source.')
        $items.Add('Connected App Scopes: Ensure `api`, `refresh_token`, and `offline_access` are authorized.')
        $items.Add('Agentforce Bots: Assign "View Setup and Configuration" and Read on BotDefinition/BotVersion to the integration user.')
        $items.Add('Agent Users: Ensure Agentforce Bot users are mapped to machine accounts in SailPoint ISC.')
        $items.Add('License: Machine Identity & Agent Discovery requires the SailPoint Agentic Fabric license on the tenant.')
    }
    if ($features -contains 'ActivityInsights') {
        $items.Add('Activity Insights: Ensure "View Event Log Files" and "View All Data" are enabled on the integration user profile.')
    }
    return @($items)
}

function Build-SalesforceConnectionSettings {
    param(
        [Parameter(Mandatory)][string]$InstanceUrl,
        [Parameter(Mandatory)][string]$ConsumerKey,
        [string]$Username,
        [string]$LoginUrl = 'https://login.salesforce.com',
        [string[]]$FeatureNames = @()
    )
    $sb = [System.Text.StringBuilder]::new()
    [void]$sb.AppendLine('# SailPoint ISC Salesforce SaaS Connection Settings')
    [void]$sb.AppendLine("Instance URL: $InstanceUrl")
    [void]$sb.AppendLine("OAuth Login URL: $LoginUrl")
    [void]$sb.AppendLine("Client ID (Consumer Key): $ConsumerKey")
    if ($Username) {
        [void]$sb.AppendLine("Integration Username: $Username")
    }
    [void]$sb.AppendLine("OAuth Scopes: api, refresh_token, offline_access")

    $checklist = Get-SalesforceIscFeatureChecklist -FeatureNames $FeatureNames
    if ($checklist.Count -gt 0) {
        [void]$sb.AppendLine('')
        [void]$sb.AppendLine('## Machine Identity & Feature Checklist')
        foreach ($item in $checklist) {
            [void]$sb.AppendLine("- $item")
        }
    }
    return $sb.ToString().Trim()
}

Export-ModuleMember -Function @(
    'Get-SalesforceFeaturePacks'
    'Get-SalesforceAgentDiscoveryFeaturePack'
    'Get-SalesforceLoginHost'
    'Get-SalesforceRunRecord'
    'Invoke-SalesforceCli'
    'Connect-SalesforceOrg'
    'Get-SalesforceIscFeatureChecklist'
    'Build-SalesforceConnectionSettings'
)