<#
.SYNOPSIS
    Remotely collects PC information from shopfloor PCs via WinRM and updates ShopDB.

.DESCRIPTION
    This script uses WinRM (Invoke-Command) to remotely execute commands on shopfloor PCs,
    collect system information, and POST it to the ShopDB API.

.PARAMETER ComputerName
    Single computer name or array of computer names to update.

.PARAMETER All
    Query ShopDB for all shopfloor PCs and update them.

.PARAMETER Credential
    PSCredential object for authentication. If not provided, will prompt or use current user.

.PARAMETER ApiUrl
    URL to the ShopDB API. Defaults to http://192.168.122.151:8080/api.asp

.EXAMPLE
    # Update a single PC
    .\Update-ShopfloorPCs-Remote.ps1 -ComputerName "SHOPFLOOR-PC01"

.EXAMPLE
    # Update multiple PCs
    .\Update-ShopfloorPCs-Remote.ps1 -ComputerName "PC01","PC02","PC03"

.EXAMPLE
    # Update all shopfloor PCs from ShopDB
    .\Update-ShopfloorPCs-Remote.ps1 -All

.EXAMPLE
    # With specific credentials
    $cred = Get-Credential
    .\Update-ShopfloorPCs-Remote.ps1 -ComputerName "SHOPFLOOR-PC01" -Credential $cred

.NOTES
    Requires:
    - WinRM enabled on target PCs (Enable-PSRemoting)
    - Admin credentials on target PCs
    - Network access to target PCs on port 5985 (HTTP) or 5986 (HTTPS)
#>

[CmdletBinding(DefaultParameterSetName='ByName')]
param(
    [Parameter(ParameterSetName='ByName', Position=0)]
    [string[]]$ComputerName,

    [Parameter(ParameterSetName='All')]
    [switch]$All,

    [Parameter(ParameterSetName='SetupTrust')]
    [switch]$SetupTrustedHosts,

    [Parameter()]
    [PSCredential]$Credential,

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

    [Parameter()]
    [string]$DnsSuffix = "logon.ds.ge.com",

    [Parameter()]
    [switch]$SkipDnsLookup,

    [Parameter()]
    [switch]$UseSSL,

    [Parameter()]
    [int]$ThrottleLimit = 10,

    [Parameter()]
    [switch]$WhatIf
)

#region Functions

function Write-Log {
    param([string]$Message, [string]$Level = "INFO")
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $color = switch ($Level) {
        "ERROR"   { "Red" }
        "WARNING" { "Yellow" }
        "SUCCESS" { "Green" }
        default   { "White" }
    }
    Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color
}

function Get-ShopfloorPCsFromApi {
    <#
    .SYNOPSIS
        Queries ShopDB API to get list of shopfloor PCs with details.
    #>
    param([string]$ApiUrl)

    try {
        Write-Log "Querying API: $ApiUrl`?action=getShopfloorPCs" -Level "INFO"
        $response = Invoke-RestMethod -Uri "$ApiUrl`?action=getShopfloorPCs" -Method Get -ErrorAction Stop

        if ($response.success -and $response.data) {
            Write-Log "API returned $($response.count) shopfloor PCs" -Level "SUCCESS"

            # Return full PC objects, not just hostnames
            return $response.data
        } else {
            Write-Log "No shopfloor PCs returned from API" -Level "WARNING"
            return @()
        }
    } catch {
        Write-Log "Failed to query API for shopfloor PCs: $_" -Level "ERROR"
        return @()
    }
}

function Get-PCConnectionInfo {
    <#
    .SYNOPSIS
        Builds FQDN and resolves IP for a PC hostname.
    #>
    param(
        [Parameter(Mandatory)]
        [string]$Hostname,

        [Parameter()]
        [string]$DnsSuffix = "logon.ds.ge.com",

        [Parameter()]
        [switch]$SkipDnsLookup
    )

    $result = @{
        Hostname = $Hostname
        FQDN = $null
        IPAddress = $null
        Reachable = $false
        Error = $null
    }

    # Build FQDN - if hostname already contains dots, assume it's already an FQDN
    if ($Hostname -like "*.*") {
        $result.FQDN = $Hostname
    } else {
        $result.FQDN = "$Hostname.$DnsSuffix"
    }

    if (-not $SkipDnsLookup) {
        try {
            # Resolve DNS to get current IP
            $dnsResult = Resolve-DnsName -Name $result.FQDN -Type A -ErrorAction Stop
            $result.IPAddress = ($dnsResult | Where-Object { $_.Type -eq 'A' } | Select-Object -First 1).IPAddress

            if ($result.IPAddress) {
                # Quick connectivity test (WinRM port 5985)
                $tcpTest = Test-NetConnection -ComputerName $result.FQDN -Port 5985 -WarningAction SilentlyContinue
                $result.Reachable = $tcpTest.TcpTestSucceeded
            }
        } catch {
            $result.Error = "DNS lookup failed: $($_.Exception.Message)"
        }
    } else {
        # Skip DNS lookup, just use FQDN directly
        $result.Reachable = $true  # Assume reachable, let WinRM fail if not
    }

    return $result
}

function Test-WinRMConnectivity {
    <#
    .SYNOPSIS
        Tests WinRM connectivity to a remote PC.
    #>
    param(
        [Parameter(Mandatory)]
        [string]$ComputerName,

        [Parameter()]
        [PSCredential]$Credential
    )

    $testParams = @{
        ComputerName = $ComputerName
        ErrorAction = 'Stop'
    }
    if ($Credential) {
        $testParams.Credential = $Credential
    }

    try {
        $result = Test-WSMan @testParams
        return @{ Success = $true; Error = $null }
    } catch {
        return @{ Success = $false; Error = $_.Exception.Message }
    }
}

function Add-TrustedHosts {
    <#
    .SYNOPSIS
        Adds computers to WinRM TrustedHosts list.
    #>
    param(
        [Parameter()]
        [string[]]$ComputerNames,

        [Parameter()]
        [string]$DnsSuffix = "logon.ds.ge.com",

        [Parameter()]
        [switch]$TrustAllInDomain
    )

    # Check if running as admin
    $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if (-not $isAdmin) {
        Write-Log "ERROR: Must run as Administrator to modify TrustedHosts" -Level "ERROR"
        return $false
    }

    try {
        # Get current trusted hosts
        $currentHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts -ErrorAction SilentlyContinue).Value
        Write-Log "Current TrustedHosts: $(if ($currentHosts) { $currentHosts } else { '(empty)' })" -Level "INFO"

        if ($TrustAllInDomain) {
            # Trust all hosts in the domain (wildcard)
            $newValue = "*.$DnsSuffix"
            Write-Log "Adding wildcard trust for: $newValue" -Level "INFO"
        } else {
            # Build list of FQDNs to add
            $fqdnsToAdd = @()
            foreach ($computer in $ComputerNames) {
                if ($computer -like "*.*") {
                    $fqdnsToAdd += $computer
                } else {
                    $fqdnsToAdd += "$computer.$DnsSuffix"
                }
            }

            # Merge with existing
            $existingList = if ($currentHosts) { $currentHosts -split ',' } else { @() }
            $mergedList = ($existingList + $fqdnsToAdd) | Select-Object -Unique
            $newValue = $mergedList -join ','

            Write-Log "Adding to TrustedHosts: $($fqdnsToAdd -join ', ')" -Level "INFO"
        }

        # Set the new value
        Set-Item WSMan:\localhost\Client\TrustedHosts -Value $newValue -Force
        Write-Log "TrustedHosts updated successfully" -Level "SUCCESS"

        # Show new value
        $updatedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
        Write-Log "New TrustedHosts: $updatedHosts" -Level "INFO"

        return $true
    } catch {
        Write-Log "Failed to update TrustedHosts: $_" -Level "ERROR"
        return $false
    }
}

function Show-TrustedHostsHelp {
    <#
    .SYNOPSIS
        Shows help for setting up TrustedHosts.
    #>

    Write-Host ""
    Write-Host "========================================" -ForegroundColor Cyan
    Write-Host "  WinRM TrustedHosts Setup Guide" -ForegroundColor Cyan
    Write-Host "========================================" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "Since you're not on the same domain as the shopfloor PCs," -ForegroundColor White
    Write-Host "you need to add them to your TrustedHosts list." -ForegroundColor White
    Write-Host ""
    Write-Host "OPTION 1: Trust all PCs in the domain (Recommended)" -ForegroundColor Yellow
    Write-Host "  Run as Administrator:" -ForegroundColor Gray
    Write-Host '  Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*.logon.ds.ge.com" -Force' -ForegroundColor Green
    Write-Host ""
    Write-Host "OPTION 2: Trust specific PCs" -ForegroundColor Yellow
    Write-Host "  Run as Administrator:" -ForegroundColor Gray
    Write-Host '  Set-Item WSMan:\localhost\Client\TrustedHosts -Value "PC01.logon.ds.ge.com,PC02.logon.ds.ge.com" -Force' -ForegroundColor Green
    Write-Host ""
    Write-Host "OPTION 3: Use this script to set it up" -ForegroundColor Yellow
    Write-Host "  # Trust all in domain:" -ForegroundColor Gray
    Write-Host "  .\Update-ShopfloorPCs-Remote.ps1 -SetupTrustedHosts" -ForegroundColor Green
    Write-Host ""
    Write-Host "  # Trust specific PCs:" -ForegroundColor Gray
    Write-Host '  .\Update-ShopfloorPCs-Remote.ps1 -SetupTrustedHosts -ComputerName "PC01","PC02"' -ForegroundColor Green
    Write-Host ""
    Write-Host "VIEW CURRENT SETTINGS:" -ForegroundColor Yellow
    Write-Host '  Get-Item WSMan:\localhost\Client\TrustedHosts' -ForegroundColor Green
    Write-Host ""
    Write-Host "CLEAR TRUSTEDHOSTS:" -ForegroundColor Yellow
    Write-Host '  Set-Item WSMan:\localhost\Client\TrustedHosts -Value "" -Force' -ForegroundColor Green
    Write-Host ""
    Write-Host "========================================" -ForegroundColor Cyan
    Write-Host ""
}

function Get-RemotePCInfo {
    <#
    .SYNOPSIS
        Script block to execute on remote PC to collect system information.
    #>

    # This scriptblock runs on the REMOTE computer
    $scriptBlock = {
        $result = @{
            Success = $false
            Hostname = $env:COMPUTERNAME
            Error = $null
        }

        try {
            # Get basic system info
            $computerSystem = Get-CimInstance -ClassName Win32_ComputerSystem
            $bios = Get-CimInstance -ClassName Win32_BIOS
            $os = Get-CimInstance -ClassName Win32_OperatingSystem

            $result.SerialNumber = $bios.SerialNumber
            $result.Manufacturer = $computerSystem.Manufacturer
            $result.Model = $computerSystem.Model
            $result.LoggedInUser = $computerSystem.UserName
            $result.OSVersion = $os.Caption
            $result.LastBootUpTime = if ($os.LastBootUpTime) { $os.LastBootUpTime.ToString("yyyy-MM-dd HH:mm:ss") } else { $null }

            # Get network interfaces
            $networkAdapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration |
                Where-Object { $_.IPEnabled -eq $true }

            $networkInterfaces = @()
            foreach ($adapter in $networkAdapters) {
                if ($adapter.IPAddress) {
                    foreach ($i in 0..($adapter.IPAddress.Count - 1)) {
                        $ip = $adapter.IPAddress[$i]
                        # Only include IPv4 addresses
                        if ($ip -match '^\d+\.\d+\.\d+\.\d+$') {
                            # 10.134.*.* is always primary for shopfloor PCs
                            $isPrimary = ($ip -like "10.134.*")
                            # Secondary/equipment IPs: 192.168.*, 10.0.*, 100.* or any non-10.134 private IP
                            $isMachineNetwork = (
                                ($ip -like "192.168.*") -or
                                ($ip -like "10.0.*") -or
                                ($ip -like "100.*") -or
                                (($ip -like "10.*") -and ($ip -notlike "10.134.*"))
                            )
                            $networkInterfaces += @{
                                IPAddress = $ip
                                MACAddress = $adapter.MACAddress
                                SubnetMask = if ($adapter.IPSubnet) { $adapter.IPSubnet[$i] } else { "" }
                                DefaultGateway = if ($adapter.DefaultIPGateway) { $adapter.DefaultIPGateway[0] } else { "" }
                                InterfaceName = $adapter.Description
                                IsPrimary = $isPrimary
                                IsMachineNetwork = $isMachineNetwork
                            }
                        }
                    }
                }
            }
            # Sort so 10.134.*.* (primary) comes first
            $result.NetworkInterfaces = $networkInterfaces | Sort-Object -Property @{Expression={$_.IsPrimary}; Descending=$true}

            # Get DNC configuration if it exists
            $dncConfig = @{}
            $dncIniPath = "C:\DNC\DNC.ini"
            if (Test-Path $dncIniPath) {
                $iniContent = Get-Content $dncIniPath -Raw
                # Parse INI file for common DNC settings
                if ($iniContent -match 'Site\s*=\s*(.+)') { $dncConfig.Site = $matches[1].Trim() }
                if ($iniContent -match 'Cnc\s*=\s*(.+)') { $dncConfig.CNC = $matches[1].Trim() }
                if ($iniContent -match 'NcIF\s*=\s*(.+)') { $dncConfig.NCIF = $matches[1].Trim() }
                if ($iniContent -match 'MachineNo\s*=\s*(.+)') { $dncConfig.MachineNo = $matches[1].Trim() }
                if ($iniContent -match 'FtpHostPrimary\s*=\s*(.+)') { $dncConfig.FTPHostPrimary = $matches[1].Trim() }
            }
            $result.DNCConfig = $dncConfig

            # Get machine number from registry or DNC config
            $machineNo = $null
            # Try registry first - GE Aircraft Engines DNC location
            $regPaths = @(
                "HKLM:\SOFTWARE\GE Aircraft Engines\DNC\General",
                "HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General"
            )
            foreach ($regPath in $regPaths) {
                if (Test-Path $regPath) {
                    $machineNo = (Get-ItemProperty -Path $regPath -Name "MachineNo" -ErrorAction SilentlyContinue).MachineNo
                    if ($machineNo) { break }
                }
            }
            # Fall back to DNC config
            if (-not $machineNo -and $dncConfig.MachineNo) {
                $machineNo = $dncConfig.MachineNo
            }

            # Check if machine number is generic (don't send to API)
            $genericTypeHint = $null
            if ($machineNo) {
                $genericMachineTypes = @{
                    "^WJPRT"  = "Measuring"    # Generic printer/measuring tool
                    "^WJCMM"  = "CMM"          # Generic CMM
                    "^WJMEAS" = "Measuring"    # Generic measuring
                    "^0600$"  = "Wax Trace"    # Wax trace machines
                    "^0612$"  = "Part Marker"  # Part markers
                    "^0613$"  = "Part Marker"  # Part markers
                    "^0615"   = "Part Marker"  # Part markers
                    "^TEST"   = $null          # Test machines
                    "^TEMP"   = $null          # Temporary
                    "^DEFAULT"= $null          # Default value
                    "^0+$"    = $null          # All zeros
                }

                foreach ($pattern in $genericMachineTypes.Keys) {
                    if ($machineNo -match $pattern) {
                        $genericTypeHint = $genericMachineTypes[$pattern]
                        $result.IsGenericMachineNo = $true
                        $result.GenericTypeHint = $genericTypeHint
                        $machineNo = $null  # Don't send generic machine numbers
                        break
                    }
                }
            }
            $result.MachineNo = $machineNo

            # Get serial port configuration
            $comPorts = @()
            $serialPorts = Get-CimInstance -ClassName Win32_SerialPort -ErrorAction SilentlyContinue
            foreach ($port in $serialPorts) {
                $comPorts += @{
                    PortName = $port.DeviceID
                    Description = $port.Description
                }
            }
            $result.SerialPorts = $comPorts

            # Check for VNC installation
            $hasVnc = $false
            $regPaths = @(
                "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
                "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
            )
            foreach ($path in $regPaths) {
                if (Test-Path $path) {
                    $vncApps = Get-ItemProperty $path -ErrorAction SilentlyContinue |
                        Where-Object { $_.DisplayName -like "*VNC Server*" -or $_.DisplayName -like "*VNC Connect*" -or $_.DisplayName -like "*RealVNC*" }
                    if ($vncApps) {
                        $hasVnc = $true
                        break
                    }
                }
            }
            if (-not $hasVnc) {
                $vncService = Get-Service -Name "vncserver*" -ErrorAction SilentlyContinue
                if ($vncService) { $hasVnc = $true }
            }
            $result.HasVnc = $hasVnc

            # ================================================================
            # Detect installed applications for PC type classification
            # ================================================================

            # Get all installed apps once for efficiency (with version info)
            $installedApps = @()
            $installedAppsWithVersion = @()
            foreach ($path in $regPaths) {
                if (Test-Path $path) {
                    $apps = Get-ItemProperty $path -ErrorAction SilentlyContinue |
                        Where-Object { $_.DisplayName -and $_.DisplayName.Trim() -ne "" }
                    foreach ($app in $apps) {
                        $installedApps += $app.DisplayName
                        $installedAppsWithVersion += @{
                            DisplayName = $app.DisplayName.Trim()
                            Version = if ($app.DisplayVersion) { $app.DisplayVersion.Trim() } else { "" }
                        }
                    }
                }
            }

            # ================================================================
            # Match against tracked applications (embedded from applications.csv)
            # ================================================================
            $trackedAppPatterns = @(
                @{ app_id = 2;  app_name = "UDC"; patterns = @("Universal Data Collection") }
                @{ app_id = 4;  app_name = "CLM"; patterns = @("PPDCS", "CLM") }
                @{ app_id = 6;  app_name = "PC-DMIS"; patterns = @("PC-DMIS", "PCDMIS") }
                @{ app_id = 7;  app_name = "Oracle"; patterns = @("OracleDatabase", "Oracle Database", "Oracle.*Database") }
                @{ app_id = 8;  app_name = "eDNC"; patterns = @("eDNC") }
                @{ app_id = 22; app_name = "OpenText"; patterns = @("OpenText", "CSF") }
                @{ app_id = 30; app_name = "Tanium"; patterns = @("^Tanium Client") }
                @{ app_id = 76; app_name = "FormTracePak"; patterns = @("FormTracePak", "Formtracepak", "Form Trace", "FormTrace") }
                @{ app_id = 69; app_name = "Keyence VR Series"; patterns = @("VR-3000", "VR-5000", "VR-6000", "KEYENCE VR") }
                @{ app_id = 70; app_name = "Genspect"; patterns = @("Genspect") }
                @{ app_id = 71; app_name = "GageCal"; patterns = @("GageCal") }
                @{ app_id = 72; app_name = "NI Software"; patterns = @("^NI-", "National Instruments", "NI System", "NI Measurement", "NI LabVIEW") }
                @{ app_id = 73; app_name = "goCMM"; patterns = @("goCMM") }
                @{ app_id = 74; app_name = "DODA"; patterns = @("Dovetail Digital Analysis", "DODA") }
                @{ app_id = 75; app_name = "FormStatusMonitor"; patterns = @("FormStatusMonitor") }
                @{ app_id = 77; app_name = "HeatTreat"; patterns = @("HeatTreat") }
            )

            $matchedApps = @()
            foreach ($tracked in $trackedAppPatterns) {
                foreach ($installedApp in $installedAppsWithVersion) {
                    $matched = $false
                    foreach ($pattern in $tracked.patterns) {
                        if ($installedApp.DisplayName -match $pattern) {
                            $matched = $true
                            break
                        }
                    }
                    if ($matched) {
                        # Avoid duplicates
                        if (-not ($matchedApps | Where-Object { $_.appid -eq $tracked.app_id })) {
                            $matchedApps += @{
                                appid = $tracked.app_id
                                appname = $tracked.app_name
                                version = $installedApp.Version
                                displayname = $installedApp.DisplayName
                            }
                        }
                        break
                    }
                }
            }
            # Store matched apps as JSON string to survive WinRM serialization
            $result.MatchedAppsCount = $matchedApps.Count
            $result.MatchedAppNames = ($matchedApps | ForEach-Object { $_.appname }) -join ", "
            $result.AllInstalledApps = ($installedApps | Sort-Object) -join "|"
            $result.AllInstalledAppsCount = $installedApps.Count
            if ($matchedApps.Count -gt 0) {
                $result.MatchedAppsJson = ($matchedApps | ConvertTo-Json -Compress)
            } else {
                $result.MatchedAppsJson = ""
            }

            # CMM Detection: PC-DMIS, goCMM, DODA
            $hasPcDmis = $false
            $hasGoCMM = $false
            $hasDODA = $false
            foreach ($app in $installedApps) {
                if ($app -match "PC-DMIS|PCDMIS") { $hasPcDmis = $true }
                if ($app -match "^goCMM") { $hasGoCMM = $true }
                if ($app -match "Dovetail Digital Analysis|DODA") { $hasDODA = $true }
            }
            # Also check common PC-DMIS installation paths
            if (-not $hasPcDmis) {
                $pcDmisPaths = @(
                    "C:\Program Files\Hexagon\PC-DMIS*",
                    "C:\Program Files (x86)\Hexagon\PC-DMIS*",
                    "C:\Program Files\WAI\PC-DMIS*",
                    "C:\Program Files (x86)\WAI\PC-DMIS*",
                    "C:\ProgramData\Hexagon\PC-DMIS*"
                )
                foreach ($dmisPath in $pcDmisPaths) {
                    if (Test-Path $dmisPath) {
                        $hasPcDmis = $true
                        break
                    }
                }
            }
            $result.HasPcDmis = $hasPcDmis
            $result.HasGoCMM = $hasGoCMM
            $result.HasDODA = $hasDODA
            $result.IsCMM = ($hasPcDmis -or $hasGoCMM -or $hasDODA)

            # Wax Trace Detection: FormTracePak, FormStatusMonitor
            $hasFormTracePak = $false
            $hasFormStatusMonitor = $false
            foreach ($app in $installedApps) {
                if ($app -match "FormTracePak|Formtracepak|Form Trace|FormTrace") { $hasFormTracePak = $true }
                if ($app -match "FormStatusMonitor") { $hasFormStatusMonitor = $true }
            }
            # Check file path fallback
            if (-not $hasFormTracePak) {
                $ftPaths = @("C:\Program Files\MitutoyoApp*", "C:\Program Files (x86)\MitutoyoApp*")
                foreach ($ftPath in $ftPaths) {
                    if (Test-Path $ftPath) { $hasFormTracePak = $true; break }
                }
            }
            $result.HasFormTracePak = $hasFormTracePak
            $result.HasFormStatusMonitor = $hasFormStatusMonitor
            $result.IsWaxTrace = ($hasFormTracePak -or $hasFormStatusMonitor)

            # Keyence Detection: VR Series, Keyence VR USB Driver
            $hasKeyence = $false
            foreach ($app in $installedApps) {
                if ($app -match "VR-3000|VR-5000|VR-6000|KEYENCE VR") {
                    $hasKeyence = $true
                    break
                }
            }
            $result.HasKeyence = $hasKeyence
            $result.IsKeyence = $hasKeyence

            # EAS1000 Detection: GageCal, NI Software (National Instruments)
            $hasGageCal = $false
            $hasNISoftware = $false
            foreach ($app in $installedApps) {
                if ($app -match "^GageCal") { $hasGageCal = $true }
                if ($app -match "^NI-|National Instruments|NI System|NI Measurement|NI LabVIEW") { $hasNISoftware = $true }
            }
            $result.HasGageCal = $hasGageCal
            $result.HasNISoftware = $hasNISoftware
            $result.IsEAS1000 = ($hasGageCal -or $hasNISoftware)

            # Genspect Detection
            $hasGenspect = $false
            foreach ($app in $installedApps) {
                if ($app -match "^Genspect") {
                    $hasGenspect = $true
                    break
                }
            }
            $result.HasGenspect = $hasGenspect

            # Heat Treat Detection
            $hasHeatTreat = $false
            foreach ($app in $installedApps) {
                if ($app -match "^HeatTreat") {
                    $hasHeatTreat = $true
                    break
                }
            }
            $result.HasHeatTreat = $hasHeatTreat

            # Determine PC Type based on detected software
            # Priority: CMM > Wax Trace > Keyence > EAS1000 > Genspect > Heat Treat > Generic hint > Shopfloor (default)
            $detectedPcType = "Shopfloor"  # Default for shopfloor PCs
            if ($result.IsCMM) {
                $detectedPcType = "CMM"
            } elseif ($result.IsWaxTrace) {
                $detectedPcType = "Wax Trace"
            } elseif ($result.IsKeyence) {
                $detectedPcType = "Keyence"
            } elseif ($result.IsEAS1000) {
                $detectedPcType = "EAS1000"
            } elseif ($hasGenspect) {
                $detectedPcType = "Genspect"
            } elseif ($hasHeatTreat) {
                $detectedPcType = "Heat Treat"
            } elseif ($result.GenericTypeHint) {
                # Use generic machine number hint when no software detected
                $detectedPcType = $result.GenericTypeHint
            }
            $result.DetectedPcType = $detectedPcType

            $result.Success = $true
        } catch {
            $result.Error = $_.Exception.Message
        }

        return $result
    }

    return $scriptBlock
}

function Send-PCDataToApi {
    <#
    .SYNOPSIS
        Sends collected PC data to the ShopDB API.
    #>
    param(
        [Parameter(Mandatory)]
        [hashtable]$PCData,

        [Parameter(Mandatory)]
        [string]$ApiUrl
    )

    try {
        # Determine PC type - use detected type from software analysis, fallback to machine number
        $pcType = "Measuring"  # Default
        if ($PCData.DetectedPcType) {
            $pcType = $PCData.DetectedPcType
        } elseif ($PCData.MachineNo) {
            $pcType = "Shopfloor"
        }

        # Build the POST body
        $postData = @{
            action = 'updateCompleteAsset'
            hostname = $PCData.Hostname
            serialNumber = $PCData.SerialNumber
            manufacturer = $PCData.Manufacturer
            model = $PCData.Model
            pcType = $pcType
            loggedInUser = $PCData.LoggedInUser
            osVersion = $PCData.OSVersion
        }

        # Add last boot time if available
        if ($PCData.LastBootUpTime) {
            $postData.lastBootUpTime = $PCData.LastBootUpTime
        }

        # Add machine number if available
        if ($PCData.MachineNo) {
            $postData.machineNo = $PCData.MachineNo
        }

        # Add VNC status
        if ($PCData.HasVnc) {
            $postData.hasVnc = "1"
        } else {
            $postData.hasVnc = "0"
        }

        # Add network interfaces as JSON
        if ($PCData.NetworkInterfaces -and $PCData.NetworkInterfaces.Count -gt 0) {
            $postData.networkInterfaces = ($PCData.NetworkInterfaces | ConvertTo-Json -Compress)
        }

        # Add DNC config if available
        if ($PCData.DNCConfig -and $PCData.DNCConfig.Keys.Count -gt 0) {
            $postData.dncConfig = ($PCData.DNCConfig | ConvertTo-Json -Compress)
        }

        # Add matched/tracked applications (already JSON from remote scriptblock)
        if ($PCData.MatchedAppsJson -and $PCData.MatchedAppsJson -ne "") {
            $postData.installedApps = $PCData.MatchedAppsJson
        }

        # Send to API
        $response = Invoke-RestMethod -Uri $ApiUrl -Method Post -Body $postData -ErrorAction Stop

        return @{
            Success = $response.success
            Message = $response.message
            MachineId = $response.machineid
        }
    } catch {
        return @{
            Success = $false
            Message = $_.Exception.Message
            MachineId = $null
        }
    }
}

#endregion Functions

#region Main

# Handle TrustedHosts setup mode
if ($SetupTrustedHosts) {
    Write-Log "=== TrustedHosts Setup Mode ===" -Level "INFO"

    if ($ComputerName -and $ComputerName.Count -gt 0) {
        # Trust specific computers
        $result = Add-TrustedHosts -ComputerNames $ComputerName -DnsSuffix $DnsSuffix
    } else {
        # Trust all in domain (wildcard)
        $result = Add-TrustedHosts -DnsSuffix $DnsSuffix -TrustAllInDomain
    }

    if (-not $result) {
        Show-TrustedHostsHelp
    }
    exit 0
}

Write-Log "=== ShopDB Remote PC Update Script ===" -Level "INFO"
Write-Log "API URL: $ApiUrl" -Level "INFO"
Write-Log "DNS Suffix: $DnsSuffix" -Level "INFO"

# Prompt for credentials if not provided
if (-not $Credential) {
    Write-Log "No credentials provided. Prompting for credentials..." -Level "INFO"
    $Credential = Get-Credential -Message "Enter credentials for remote PCs"
    if (-not $Credential) {
        Write-Log "Credentials required. Exiting." -Level "ERROR"
        exit 1
    }
}

# Show credential info (username only, not password)
Write-Log "Using credentials: $($Credential.UserName)" -Level "INFO"

# Determine list of computers to process
$computers = @()

if ($All) {
    Write-Log "Querying ShopDB for all shopfloor PCs..." -Level "INFO"
    $shopfloorPCs = Get-ShopfloorPCsFromApi -ApiUrl $ApiUrl
    if ($shopfloorPCs.Count -eq 0) {
        Write-Log "No shopfloor PCs found in ShopDB. Exiting." -Level "WARNING"
        exit 0
    }

    # Display summary table
    Write-Host ""
    Write-Host "  Shopfloor PCs from ShopDB:" -ForegroundColor Cyan
    Write-Host "  $("-" * 90)" -ForegroundColor Gray
    Write-Host ("  {0,-20} {1,-15} {2,-15} {3,-20} {4,-15}" -f "Hostname", "Machine #", "IP Address", "Last Updated", "Logged In") -ForegroundColor Cyan
    Write-Host "  $("-" * 90)" -ForegroundColor Gray

    foreach ($pc in $shopfloorPCs) {
        $lastUpdated = if ($pc.lastupdated) { $pc.lastupdated } else { "Never" }
        $loggedIn = if ($pc.loggedinuser) { $pc.loggedinuser } else { "-" }
        $machineNo = if ($pc.machinenumber) { $pc.machinenumber } else { "-" }
        $ipAddr = if ($pc.ipaddress) { $pc.ipaddress } else { "-" }

        Write-Host ("  {0,-20} {1,-15} {2,-15} {3,-20} {4,-15}" -f $pc.hostname, $machineNo, $ipAddr, $lastUpdated, $loggedIn)
    }
    Write-Host "  $("-" * 90)" -ForegroundColor Gray
    Write-Host ""

    # Extract hostnames for processing
    $computers = $shopfloorPCs | ForEach-Object { $_.hostname } | Where-Object { $_ -ne "" -and $_ -ne $null }
    Write-Log "Found $($computers.Count) shopfloor PCs with hostnames" -Level "INFO"

} elseif ($ComputerName) {
    $computers = $ComputerName
} else {
    Write-Log "No computers specified. Use -ComputerName or -All parameter." -Level "ERROR"
    Write-Host ""
    Write-Host "Usage Examples:" -ForegroundColor Yellow
    Write-Host "  # Update all shopfloor PCs from database:" -ForegroundColor Gray
    Write-Host "  .\Update-ShopfloorPCs-Remote.ps1 -All -Credential `$cred" -ForegroundColor Green
    Write-Host ""
    Write-Host "  # Update specific PC:" -ForegroundColor Gray
    Write-Host "  .\Update-ShopfloorPCs-Remote.ps1 -ComputerName 'PC01' -Credential `$cred" -ForegroundColor Green
    Write-Host ""
    Write-Host "  # Setup TrustedHosts first (run as admin):" -ForegroundColor Gray
    Write-Host "  .\Update-ShopfloorPCs-Remote.ps1 -SetupTrustedHosts" -ForegroundColor Green
    Write-Host ""
    exit 1
}

Write-Log "Processing $($computers.Count) computer(s)..." -Level "INFO"

# Build FQDNs directly - skip slow DNS/connectivity checks
# Let WinRM handle connection failures (much faster)
Write-Log "Building FQDN list..." -Level "INFO"
$targetComputers = @()

foreach ($computer in $computers) {
    # Build FQDN - if hostname already contains dots, assume it's already an FQDN
    $fqdn = if ($computer -like "*.*") { $computer } else { "$computer.$DnsSuffix" }

    $targetComputers += @{
        Hostname = $computer
        FQDN = $fqdn
        IPAddress = $null
    }
}

if ($targetComputers.Count -eq 0) {
    Write-Log "No computers to process. Exiting." -Level "ERROR"
    exit 1
}

Write-Log "Will attempt connection to $($targetComputers.Count) PC(s)" -Level "INFO"
$skippedComputers = @()

if ($WhatIf) {
    Write-Log "WhatIf mode - no changes will be made" -Level "WARNING"
    Write-Log "Would process: $($targetComputers.FQDN -join ', ')" -Level "INFO"
    exit 0
}

# Build session options - use FQDNs for WinRM connection
$fqdnList = $targetComputers | ForEach-Object { $_.FQDN }

# Create session options with short timeout (15 seconds per PC)
$sessionOption = New-PSSessionOption -OpenTimeout 15000 -OperationTimeout 30000 -NoMachineProfile

$sessionParams = @{
    ComputerName = $fqdnList
    ScriptBlock = (Get-RemotePCInfo)
    SessionOption = $sessionOption
    Authentication = 'Negotiate'
    ErrorAction = 'SilentlyContinue'
    ErrorVariable = 'remoteErrors'
}

if ($Credential) {
    $sessionParams.Credential = $Credential
    Write-Log "Credential added to session params: $($Credential.UserName)" -Level "INFO"
} else {
    Write-Log "WARNING: No credential in session params!" -Level "WARNING"
}

if ($ThrottleLimit -and $PSVersionTable.PSVersion.Major -ge 7) {
    $sessionParams.ThrottleLimit = $ThrottleLimit
}

# Execute remote commands (runs in parallel by default)
Write-Log "Connecting to remote PCs via WinRM (timeout: 15s per PC)..." -Level "INFO"
$results = Invoke-Command @sessionParams

# Process results
$successCount = 0
$failCount = 0
$successPCs = @()
$failedPCs = @()

Write-Host ""
Write-Log "Processing WinRM results..." -Level "INFO"

foreach ($result in $results) {
    if ($result.Success) {
        Write-Log "[OK] $($result.Hostname)" -Level "SUCCESS"
        Write-Log "     Serial: $($result.SerialNumber) | Model: $($result.Model) | OS: $($result.OSVersion)" -Level "INFO"

        if ($result.NetworkInterfaces -and $result.NetworkInterfaces.Count -gt 0) {
            $ips = ($result.NetworkInterfaces | ForEach-Object { $_.IPAddress }) -join ", "
            Write-Log "     IPs: $ips" -Level "INFO"
        }

        if ($result.MachineNo) {
            Write-Log "     Machine #: $($result.MachineNo)" -Level "INFO"
        } elseif ($result.IsGenericMachineNo) {
            Write-Log "     Machine #: (generic - requires manual assignment)" -Level "WARNING"
        }

        # Show detected PC type and software
        if ($result.DetectedPcType) {
            $detectedSoftware = @()
            if ($result.HasPcDmis) { $detectedSoftware += "PC-DMIS" }
            if ($result.HasGoCMM) { $detectedSoftware += "goCMM" }
            if ($result.HasDODA) { $detectedSoftware += "DODA" }
            if ($result.HasFormTracePak) { $detectedSoftware += "FormTracePak" }
            if ($result.HasFormStatusMonitor) { $detectedSoftware += "FormStatusMonitor" }
            if ($result.HasKeyence) { $detectedSoftware += "Keyence VR" }
            if ($result.HasGageCal) { $detectedSoftware += "GageCal" }
            if ($result.HasNISoftware) { $detectedSoftware += "NI Software" }
            if ($result.HasGenspect) { $detectedSoftware += "Genspect" }
            if ($result.HasHeatTreat) { $detectedSoftware += "HeatTreat" }

            $softwareStr = if ($detectedSoftware.Count -gt 0) { " (" + ($detectedSoftware -join ", ") + ")" } else { "" }
            Write-Log "     PC Type: $($result.DetectedPcType)$softwareStr" -Level "INFO"
        }

        # Log tracked apps count
        if ($result.MatchedAppsCount -and $result.MatchedAppsCount -gt 0) {
            Write-Log "     Tracked Apps: $($result.MatchedAppsCount) matched ($($result.MatchedAppNames))" -Level "INFO"
        }

        # Pass the result hashtable directly to the API function
        # Note: $result from Invoke-Command is already a hashtable,
        # PSObject.Properties gives metadata (Keys, Values, Count) not the actual data

        # Send to API
        Write-Log "     Sending to API..." -Level "INFO"
        $apiResult = Send-PCDataToApi -PCData $result -ApiUrl $ApiUrl

        if ($apiResult.Success) {
            Write-Log "     -> Updated in ShopDB (MachineID: $($apiResult.MachineId))" -Level "SUCCESS"

            # Update WinRM status - since we successfully connected via WinRM, mark it as enabled
            try {
                $winrmBody = @{
                    action = "updateWinRMStatus"
                    hostname = $result.Hostname
                    hasWinRM = "1"
                }
                $winrmResponse = Invoke-RestMethod -Uri $ApiUrl -Method Post -Body $winrmBody -ErrorAction Stop
                if ($winrmResponse.success) {
                    Write-Log "     -> WinRM status updated" -Level "SUCCESS"
                }
            } catch {
                Write-Log "     -> WinRM status update failed (non-critical): $_" -Level "WARNING"
            }

            $successCount++
            $successPCs += @{
                Hostname = $result.Hostname
                MachineId = $apiResult.MachineId
                Serial = $result.SerialNumber
            }
        } else {
            Write-Log "     -> API Error: $($apiResult.Message)" -Level "ERROR"
            $failCount++
            $failedPCs += @{
                Hostname = $result.Hostname
                Error = "API: $($apiResult.Message)"
            }
        }
    } else {
        Write-Log "[FAIL] $($result.Hostname): $($result.Error)" -Level "ERROR"
        $failCount++
        $failedPCs += @{
            Hostname = $result.Hostname
            Error = $result.Error
        }
    }
    Write-Host ""
}

# Collect connection errors for IP fallback retry
$connectionFailures = @()
foreach ($err in $remoteErrors) {
    $targetPC = if ($err.TargetObject) { $err.TargetObject } else { "Unknown" }
    Write-Log "[FAIL] ${targetPC}: $($err.Exception.Message)" -Level "ERROR"
    $connectionFailures += @{
        Hostname = $targetPC
        FQDN = $targetPC
        Error = $err.Exception.Message
    }
}

# IP Fallback: Retry failed connections using recorded IP addresses (10.134.*.*)
if ($connectionFailures.Count -gt 0) {
    Write-Host ""
    Write-Log "Attempting IP fallback for $($connectionFailures.Count) failed connection(s)..." -Level "INFO"

    foreach ($failure in $connectionFailures) {
        # Extract hostname from FQDN
        $hostname = $failure.FQDN -replace "\..*$", ""

        # Query API for recorded IP address
        try {
            $ipLookupBody = @{
                action = "getRecordedIP"
                hostname = $hostname
            }
            $ipResponse = Invoke-RestMethod -Uri $ApiUrl -Method Post -Body $ipLookupBody -ErrorAction Stop

            if ($ipResponse.success -and $ipResponse.ipaddress -and $ipResponse.ipaddress -match "^10\.134\.") {
                $fallbackIP = $ipResponse.ipaddress
                Write-Log "  Found recorded IP for ${hostname}: $fallbackIP - retrying..." -Level "INFO"

                # Retry connection using IP address
                $ipSessionParams = @{
                    ComputerName = $fallbackIP
                    ScriptBlock = (Get-RemotePCInfo)
                    SessionOption = $sessionOption
                    Authentication = 'Negotiate'
                    ErrorAction = 'Stop'
                }
                if ($Credential) {
                    $ipSessionParams.Credential = $Credential
                }

                try {
                    $ipResult = Invoke-Command @ipSessionParams

                    if ($ipResult.Success) {
                        Write-Log "[OK] $($ipResult.Hostname) (via IP: $fallbackIP)" -Level "SUCCESS"
                        Write-Log "     Serial: $($ipResult.SerialNumber) | Model: $($ipResult.Model) | OS: $($ipResult.OSVersion)" -Level "INFO"

                        if ($ipResult.NetworkInterfaces -and $ipResult.NetworkInterfaces.Count -gt 0) {
                            $ips = ($ipResult.NetworkInterfaces | ForEach-Object { $_.IPAddress }) -join ", "
                            Write-Log "     IPs: $ips" -Level "INFO"
                        }

                        if ($ipResult.DetectedPcType) {
                            Write-Log "     PC Type: $($ipResult.DetectedPcType)" -Level "INFO"
                        }

                        if ($ipResult.MatchedAppsCount -and $ipResult.MatchedAppsCount -gt 0) {
                            Write-Log "     Tracked Apps: $($ipResult.MatchedAppsCount) matched ($($ipResult.MatchedAppNames))" -Level "INFO"
                        }

                        # Send to API
                        Write-Log "     Sending to API..." -Level "INFO"
                        $apiResult = Send-PCDataToApi -PCData $ipResult -ApiUrl $ApiUrl

                        if ($apiResult.Success) {
                            Write-Log "     -> Updated in ShopDB (MachineID: $($apiResult.MachineId))" -Level "SUCCESS"
                            $successCount++
                            $successPCs += @{
                                Hostname = $ipResult.Hostname
                                MachineId = $apiResult.MachineId
                                Serial = $ipResult.SerialNumber
                                ViaIP = $fallbackIP
                            }
                        } else {
                            Write-Log "     -> API Error: $($apiResult.Message)" -Level "ERROR"
                            $failCount++
                            $failedPCs += @{ Hostname = $hostname; Error = "API: $($apiResult.Message)" }
                        }
                    } else {
                        Write-Log "[FAIL] $hostname (via IP: $fallbackIP): $($ipResult.Error)" -Level "ERROR"
                        $failCount++
                        $failedPCs += @{ Hostname = $hostname; Error = $ipResult.Error }
                    }
                } catch {
                    Write-Log "[FAIL] $hostname (via IP: $fallbackIP): $($_.Exception.Message)" -Level "ERROR"
                    $failCount++
                    $failedPCs += @{ Hostname = $hostname; Error = $_.Exception.Message }
                }
            } else {
                # No valid IP found, add to failed list
                Write-Log "  No 10.134.*.* IP recorded for $hostname - skipping fallback" -Level "WARNING"
                $failCount++
                $failedPCs += @{ Hostname = $hostname; Error = $failure.Error }
            }
        } catch {
            Write-Log "  Failed to lookup IP for ${hostname}: $($_.Exception.Message)" -Level "WARNING"
            $failCount++
            $failedPCs += @{ Hostname = $hostname; Error = $failure.Error }
        }
        Write-Host ""
    }
}

# Final Summary
Write-Host ""
Write-Host "=" * 70 -ForegroundColor Cyan
Write-Host "  SUMMARY" -ForegroundColor Cyan
Write-Host "=" * 70 -ForegroundColor Cyan
Write-Host ""

Write-Host "  Total Processed: $($successCount + $failCount)" -ForegroundColor White
Write-Host "  Successful:      $successCount" -ForegroundColor Green
Write-Host "  Failed:          $failCount" -ForegroundColor $(if ($failCount -gt 0) { "Red" } else { "White" })
Write-Host "  Skipped (DNS):   $($skippedComputers.Count)" -ForegroundColor $(if ($skippedComputers.Count -gt 0) { "Yellow" } else { "White" })
Write-Host ""

if ($successPCs.Count -gt 0) {
    Write-Host "  Successfully Updated:" -ForegroundColor Green
    foreach ($pc in $successPCs) {
        Write-Host "    - $($pc.Hostname) (ID: $($pc.MachineId))" -ForegroundColor Gray
    }
    Write-Host ""
}

if ($failedPCs.Count -gt 0) {
    Write-Host "  Failed:" -ForegroundColor Red
    foreach ($pc in $failedPCs) {
        Write-Host "    - $($pc.Hostname): $($pc.Error)" -ForegroundColor Gray
    }
    Write-Host ""
}

if ($skippedComputers.Count -gt 0) {
    Write-Host "  Skipped (DNS/Connectivity):" -ForegroundColor Yellow
    foreach ($pc in $skippedComputers) {
        Write-Host "    - $pc" -ForegroundColor Gray
    }
    Write-Host ""
}

Write-Host "=" * 70 -ForegroundColor Cyan

#endregion Main
