#Requires -RunAsAdministrator
<#
.SYNOPSIS
    Remotely executes asset collection script on shopfloor PCs using WinRM.

.DESCRIPTION
    This script uses WinRM to remotely execute the Update-PC-CompleteAsset.ps1 script
    on multiple shopfloor PCs. It handles:
    1. WinRM configuration and trusted hosts setup
    2. Credential management for remote connections
    3. Parallel or sequential execution across multiple PCs
    4. Error handling and logging for remote operations
    5. Collection of results from each remote PC

.PARAMETER ComputerList
    Array of computer names or IP addresses to collect data from.

.PARAMETER ComputerListFile
    Path to a text file containing computer names/IPs (one per line).

.PARAMETER Credential
    PSCredential object for authenticating to remote computers.
    If not provided, will prompt for credentials.

.PARAMETER MaxConcurrent
    Maximum number of concurrent remote sessions (default: 5).

.PARAMETER ProxyURL
    URL for the warranty proxy server (passed to remote script).

.PARAMETER DashboardURL
    URL for the dashboard API (passed to remote script).

.PARAMETER SkipWarranty
    Skip warranty lookups on remote PCs (passed to remote script).

.PARAMETER LogPath
    Path for log files (default: .\logs\remote-collection.log).

.PARAMETER TestConnections
    Test remote connections without running the full collection.

.PARAMETER ScriptPath
    Path to the Update-PC-CompleteAsset.ps1 script on remote computers.
    Default: C:\Scripts\Update-PC-CompleteAsset.ps1

.EXAMPLE
    # Collect from specific computers with prompted credentials
    .\Invoke-RemoteAssetCollection.ps1 -ComputerList @("10.48.130.100", "10.48.130.101")

.EXAMPLE
    # Collect from computers listed in file with stored credentials
    $cred = Get-Credential
    .\Invoke-RemoteAssetCollection.ps1 -ComputerListFile ".\shopfloor-pcs.txt" -Credential $cred

.EXAMPLE
    # Test connections only
    .\Invoke-RemoteAssetCollection.ps1 -ComputerList @("10.48.130.100") -TestConnections

.NOTES
    Author: System Administrator
    Date: 2025-09-26
    Version: 1.0

    Prerequisites:
    1. WinRM must be enabled on target computers
    2. PowerShell remoting must be enabled on target computers
    3. Update-PC-CompleteAsset.ps1 must be present on target computers
    4. Credentials with admin rights on target computers

    WinRM Setup Commands (run on management server):
    Enable-PSRemoting -Force
    Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*" -Force

    WinRM Setup Commands (run on target computers):
    Enable-PSRemoting -Force
    Set-NetFirewallRule -DisplayName "Windows Remote Management (HTTP-In)" -Enabled True
#>

param(
    [Parameter(Mandatory=$false)]
    [string[]]$ComputerList = @(),

    [Parameter(Mandatory=$false)]
    [string]$ComputerListFile,

    [Parameter(Mandatory=$false)]
    [PSCredential]$Credential,

    [Parameter(Mandatory=$false)]
    [int]$MaxConcurrent = 5,

    [Parameter(Mandatory=$false)]
    [string]$ProxyURL = "http://10.48.130.158/vendor-api-proxy.php",

    [Parameter(Mandatory=$false)]
    [string]$DashboardURL = "https://tsgwp00525.rd.ds.ge.com/shopdb/api.asp",

    [Parameter(Mandatory=$false)]
    [switch]$SkipWarranty = $true,

    [Parameter(Mandatory=$false)]
    [string]$LogPath = ".\logs\remote-collection.log",

    [Parameter(Mandatory=$false)]
    [switch]$TestConnections = $false,

    [Parameter(Mandatory=$false)]
    [string]$ScriptPath = "C:\Scripts\Update-PC-CompleteAsset.ps1"
)

# =============================================================================
# SSL/TLS Certificate Bypass for HTTPS connections
# =============================================================================
# This allows connections to servers with self-signed or untrusted certificates
try {
    # For PowerShell 5.x - use .NET callback
    if (-not ([System.Management.Automation.PSTypeName]'TrustAllCertsPolicy').Type) {
        Add-Type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem) {
        return true;
    }
}
"@
    }
    [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
} catch {
    # Silently continue if already set
}

# Set TLS 1.2 as minimum (required for modern HTTPS)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Initialize logging
function Initialize-Logging {
    param([string]$LogPath)

    $logDir = Split-Path $LogPath -Parent
    if (-not (Test-Path $logDir)) {
        New-Item -ItemType Directory -Path $logDir -Force | Out-Null
    }

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    Add-Content -Path $LogPath -Value "[$timestamp] Remote asset collection started"
}

function Write-Log {
    param([string]$Message, [string]$LogPath, [string]$Level = "INFO")

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logEntry = "[$timestamp] [$Level] $Message"

    Add-Content -Path $LogPath -Value $logEntry

    switch ($Level) {
        "ERROR" { Write-Host $logEntry -ForegroundColor Red }
        "WARN"  { Write-Host $logEntry -ForegroundColor Yellow }
        "SUCCESS" { Write-Host $logEntry -ForegroundColor Green }
        default { Write-Host $logEntry -ForegroundColor White }
    }
}

function Get-ComputerTargets {
    param([string[]]$ComputerList, [string]$ComputerListFile)

    $targets = @()

    # Add computers from direct list
    if ($ComputerList.Count -gt 0) {
        $targets += $ComputerList
    }

    # Add computers from file
    if (-not [string]::IsNullOrEmpty($ComputerListFile)) {
        if (Test-Path $ComputerListFile) {
            $fileTargets = Get-Content $ComputerListFile | Where-Object { $_.Trim() -ne "" -and -not $_.StartsWith("#") }
            $targets += $fileTargets
        } else {
            Write-Log "Computer list file not found: $ComputerListFile" $LogPath "ERROR"
        }
    }

    # Remove duplicates and return
    return ($targets | Sort-Object -Unique)
}

function Test-WinRMConnection {
    param([string]$ComputerName, [PSCredential]$Credential)

    try {
        $session = New-PSSession -ComputerName $ComputerName -Credential $Credential -ErrorAction Stop
        Remove-PSSession $session
        return $true
    }
    catch {
        return $false
    }
}

function Test-RemoteScriptExists {
    param([string]$ComputerName, [PSCredential]$Credential, [string]$ScriptPath)

    try {
        $result = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
            param($Path)
            Test-Path $Path
        } -ArgumentList $ScriptPath

        return $result
    }
    catch {
        return $false
    }
}

function Invoke-RemoteAssetScript {
    param(
        [string]$ComputerName,
        [PSCredential]$Credential,
        [string]$ScriptPath,
        [string]$ProxyURL,
        [string]$DashboardURL,
        [bool]$SkipWarranty,
        [string]$LogPath
    )

    try {
        Write-Log "Starting asset collection on $ComputerName" $LogPath "INFO"

        # Execute the script remotely
        $result = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
            param($ScriptPath, $ProxyURL, $DashboardURL, $SkipWarranty)

            # Change to script directory
            $scriptDir = Split-Path $ScriptPath -Parent
            Set-Location $scriptDir

            # Build parameters
            $params = @{
                ProxyURL = $ProxyURL
                DashboardURL = $DashboardURL
            }

            if ($SkipWarranty) {
                $params.SkipWarranty = $true
            }

            # Execute the script and capture output
            try {
                & $ScriptPath @params
                return @{
                    Success = $true
                    Output = "Script completed successfully"
                    Error = $null
                }
            }
            catch {
                return @{
                    Success = $false
                    Output = $null
                    Error = $_.Exception.Message
                }
            }
        } -ArgumentList $ScriptPath, $ProxyURL, $DashboardURL, $SkipWarranty

        if ($result.Success) {
            Write-Log "Asset collection completed successfully on $ComputerName" $LogPath "SUCCESS"

            # Update WinRM status in database - WinRM connection was successful
            # Get the actual hostname from the remote PC (in case we connected via IP)
            try {
                $remoteHostname = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
                    $env:COMPUTERNAME
                } -ErrorAction SilentlyContinue

                if ([string]::IsNullOrEmpty($remoteHostname)) {
                    $remoteHostname = $ComputerName
                }

                $winrmBody = @{
                    action = "updateWinRMStatus"
                    hostname = $remoteHostname
                    hasWinRM = "1"
                }
                Write-Log "Updating WinRM status for hostname: $remoteHostname (connected as: $ComputerName)" $LogPath "INFO"
                $winrmResponse = Invoke-RestMethod -Uri $DashboardURL -Method Post -Body $winrmBody -ErrorAction Stop
                if ($winrmResponse.success) {
                    Write-Log "WinRM status updated for $remoteHostname" $LogPath "INFO"
                } else {
                    Write-Log "WinRM update response: $($winrmResponse | ConvertTo-Json -Compress)" $LogPath "WARN"
                }
            }
            catch {
                Write-Log "Failed to update WinRM status for $ComputerName - $($_.Exception.Message)" $LogPath "WARN"
            }

            return @{ Success = $true; Computer = $ComputerName; Message = $result.Output }
        } else {
            Write-Log "Asset collection failed on $ComputerName: $($result.Error)" $LogPath "ERROR"
            return @{ Success = $false; Computer = $ComputerName; Message = $result.Error }
        }
    }
    catch {
        $errorMsg = "Failed to execute on $ComputerName: $($_.Exception.Message)"
        Write-Log $errorMsg $LogPath "ERROR"
        return @{ Success = $false; Computer = $ComputerName; Message = $errorMsg }
    }
}

function Show-WinRMSetupInstructions {
    Write-Host "=== WinRM Setup Instructions ===" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "On Management Server (this computer):" -ForegroundColor Yellow
    Write-Host "  Enable-PSRemoting -Force" -ForegroundColor White
    Write-Host "  Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force" -ForegroundColor White
    Write-Host ""
    Write-Host "On Target Computers:" -ForegroundColor Yellow
    Write-Host "  Enable-PSRemoting -Force" -ForegroundColor White
    Write-Host "  Set-NetFirewallRule -DisplayName 'Windows Remote Management (HTTP-In)' -Enabled True" -ForegroundColor White
    Write-Host ""
    Write-Host "For specific computer trust:" -ForegroundColor Yellow
    Write-Host "  Set-Item WSMan:\localhost\Client\TrustedHosts -Value '10.48.130.100,10.48.130.101' -Force" -ForegroundColor White
    Write-Host ""
}

# Main execution
try {
    Write-Host "=== Remote Asset Collection Script ===" -ForegroundColor Cyan
    Write-Host "Starting at $(Get-Date)" -ForegroundColor Gray
    Write-Host ""

    # Initialize logging
    Initialize-Logging -LogPath $LogPath

    # Get target computers
    $computers = Get-ComputerTargets -ComputerList $ComputerList -ComputerListFile $ComputerListFile

    if ($computers.Count -eq 0) {
        Write-Log "No target computers specified. Use -ComputerList or -ComputerListFile parameter." $LogPath "ERROR"
        Show-WinRMSetupInstructions
        exit 1
    }

    Write-Log "Target computers: $($computers -join ', ')" $LogPath "INFO"

    # Get credentials if not provided
    if (-not $Credential) {
        Write-Host "Enter credentials for remote computer access:" -ForegroundColor Yellow
        $Credential = Get-Credential
        if (-not $Credential) {
            Write-Log "No credentials provided" $LogPath "ERROR"
            exit 1
        }
    }

    # Test connections if requested
    if ($TestConnections) {
        Write-Host "`nTesting connections only..." -ForegroundColor Yellow
        foreach ($computer in $computers) {
            Write-Host "Testing $computer..." -NoNewline
            if (Test-WinRMConnection -ComputerName $computer -Credential $Credential) {
                Write-Host " [OK]" -ForegroundColor Green
                Write-Log "Connection test successful for $computer" $LogPath "SUCCESS"
            } else {
                Write-Host " [FAIL]" -ForegroundColor Red
                Write-Log "Connection test failed for $computer" $LogPath "ERROR"
            }
        }
        exit 0
    }

    # Validate all connections and script existence before starting collection
    Write-Host "`nValidating remote connections and script availability..." -ForegroundColor Yellow
    $validComputers = @()

    foreach ($computer in $computers) {
        Write-Host "Validating $computer..." -NoNewline

        if (-not (Test-WinRMConnection -ComputerName $computer -Credential $Credential)) {
            Write-Host " [CONNECTION FAILED]" -ForegroundColor Red
            Write-Log "Cannot connect to $computer via WinRM" $LogPath "ERROR"
            continue
        }

        if (-not (Test-RemoteScriptExists -ComputerName $computer -Credential $Credential -ScriptPath $ScriptPath)) {
            Write-Host " [SCRIPT NOT FOUND]" -ForegroundColor Red
            Write-Log "Script not found on $computer at $ScriptPath" $LogPath "ERROR"
            continue
        }

        Write-Host " [OK]" -ForegroundColor Green
        $validComputers += $computer
    }

    if ($validComputers.Count -eq 0) {
        Write-Log "No valid computers found for data collection" $LogPath "ERROR"
        Show-WinRMSetupInstructions
        exit 1
    }

    Write-Log "Valid computers for collection: $($validComputers -join ', ')" $LogPath "INFO"

    # Execute asset collection
    Write-Host "`nStarting asset collection on $($validComputers.Count) computers..." -ForegroundColor Cyan
    Write-Host "Max concurrent sessions: $MaxConcurrent" -ForegroundColor Gray
    Write-Host ""

    $results = @()
    $jobs = @()
    $completed = 0

    # Process computers in batches
    for ($i = 0; $i -lt $validComputers.Count; $i += $MaxConcurrent) {
        $batch = $validComputers[$i..($i + $MaxConcurrent - 1)]

        Write-Host "Processing batch: $($batch -join ', ')" -ForegroundColor Yellow

        # Start jobs for current batch
        foreach ($computer in $batch) {
            $job = Start-Job -ScriptBlock {
                param($Computer, $Credential, $ScriptPath, $ProxyURL, $DashboardURL, $SkipWarranty, $LogPath, $Functions)

                # Import functions into job scope
                Invoke-Expression $Functions

                Invoke-RemoteAssetScript -ComputerName $Computer -Credential $Credential `
                    -ScriptPath $ScriptPath -ProxyURL $ProxyURL -DashboardURL $DashboardURL `
                    -SkipWarranty $SkipWarranty -LogPath $LogPath

            } -ArgumentList $computer, $Credential, $ScriptPath, $ProxyURL, $DashboardURL, $SkipWarranty, $LogPath, (Get-Content $PSCommandPath | Out-String)

            $jobs += $job
        }

        # Wait for batch to complete
        $jobs | Wait-Job | Out-Null

        # Collect results
        foreach ($job in $jobs) {
            $result = Receive-Job $job
            $results += $result
            Remove-Job $job
            $completed++

            $computer = $result.Computer
            if ($result.Success) {
                Write-Host "[✓] $computer - Completed successfully" -ForegroundColor Green
            } else {
                Write-Host "[✗] $computer - Failed: $($result.Message)" -ForegroundColor Red
            }
        }

        $jobs = @()
        Write-Host "Batch completed. Progress: $completed/$($validComputers.Count)" -ForegroundColor Gray
        Write-Host ""
    }

    # Summary
    $successful = ($results | Where-Object { $_.Success }).Count
    $failed = ($results | Where-Object { -not $_.Success }).Count

    Write-Host "=== Collection Summary ===" -ForegroundColor Cyan
    Write-Host "Total computers: $($validComputers.Count)" -ForegroundColor White
    Write-Host "Successful: $successful" -ForegroundColor Green
    Write-Host "Failed: $failed" -ForegroundColor Red

    if ($failed -gt 0) {
        Write-Host "`nFailed computers:" -ForegroundColor Yellow
        $results | Where-Object { -not $_.Success } | ForEach-Object {
            Write-Host "  $($_.Computer): $($_.Message)" -ForegroundColor Red
        }
    }

    Write-Log "Collection completed. Success: $successful, Failed: $failed" $LogPath "INFO"

} catch {
    Write-Log "Fatal error: $($_.Exception.Message)" $LogPath "ERROR"
    exit 1
}