<#
.SYNOPSIS
    Windows Print Server Prometheus Metrics Exporter
.DESCRIPTION
    Prometheus exporter for Windows Print Server. Monitors print queues, job
    counts, job status, printer errors, spooler health, and driver information.
    Exports metrics as Prometheus-compatible text format for windows_exporter
    textfile collector or standalone HTTP listener.
.PARAMETER Mode
    Output mode: 'stdout' (default), 'textfile', or 'http'
.PARAMETER Port
    HTTP port for http mode (default: 9197)
.PARAMETER TextfileDir
    Directory for textfile collector output (default: C:\ProgramData\node_exporter)
.PARAMETER OutputFile
    Custom output file path
.PARAMETER InstallScheduledTask
    Switch to create a scheduled task for automatic collection
.PARAMETER TaskIntervalMinutes
    Interval in minutes for the scheduled task (default: 2)
.NOTES
    Author:  Phil Connor
    Contact: contact@mylinux.work
    Website: https://mylinux.work
    License: MIT
    Version: 1.0
#>

param(
    [switch]$TextFile,
    [string]$OutFile = "",
    [switch]$Install,
    [string]$Listen = "",
    [int]$Interval = 120
)

$ErrorActionPreference = "SilentlyContinue"
$Version = "1.0"
$TextfileDir = "C:\ProgramData\node_exporter"
$MetricPrefix = "windows_print"

# ============================================================================
# HELPER FUNCTIONS
# ============================================================================

function Get-PrometheusEscape {
    param([string]$Value)
    $Value -replace '\\', '\\' -replace '"', '\"' -replace "`n", ''
}

function Write-MetricHeader {
    param([string]$Name, [string]$Help, [string]$Type)
    "# HELP $Name $Help"
    "# TYPE $Name $Type"
}

# ============================================================================
# METRIC GENERATION
# ============================================================================

function Get-PrintMetrics {
    $startTime = Get-Date
    $metrics = [System.Collections.ArrayList]::new()

    # Check if Print Spooler is running
    $spooler = Get-Service -Name "Spooler" -ErrorAction SilentlyContinue
    $spoolerUp = if ($spooler -and $spooler.Status -eq "Running") { 1 } else { 0 }

    [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_up" "Print server exporter status (1=up, 0=down)" "gauge"))
    [void]$metrics.Add("${MetricPrefix}_up $spoolerUp")
    [void]$metrics.Add("")

    [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_exporter_info" "Exporter version information" "gauge"))
    [void]$metrics.Add("${MetricPrefix}_exporter_info{version=`"$Version`"} 1")
    [void]$metrics.Add("")

    [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_spooler_up" "Print Spooler service status" "gauge"))
    [void]$metrics.Add("${MetricPrefix}_spooler_up $spoolerUp")
    [void]$metrics.Add("")

    if ($spoolerUp -eq 0) {
        return ($metrics -join "`n")
    }

    # ========================================================================
    # PRINTER METRICS
    # ========================================================================

    $printers = Get-Printer -ErrorAction SilentlyContinue

    $totalPrinters = 0
    $onlinePrinters = 0
    $offlinePrinters = 0
    $errorPrinters = 0

    if ($printers) {
        $totalPrinters = @($printers).Count

        [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_printers_total" "Total configured printers" "gauge"))
        [void]$metrics.Add("${MetricPrefix}_printers_total $totalPrinters")
        [void]$metrics.Add("")

        [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_printer_status" "Printer status (1=current state)" "gauge"))

        foreach ($printer in $printers) {
            $name = Get-PrometheusEscape $printer.Name
            $status = $printer.PrinterStatus.ToString()
            $shared = if ($printer.Shared) { "true" } else { "false" }

            [void]$metrics.Add("${MetricPrefix}_printer_status{printer=`"$name`",status=`"$status`",shared=`"$shared`"} 1")

            switch ($status) {
                "Normal" { $onlinePrinters++ }
                "Offline" { $offlinePrinters++ }
                default { $errorPrinters++ }
            }
        }
        [void]$metrics.Add("")

        [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_printers_online" "Online printers" "gauge"))
        [void]$metrics.Add("${MetricPrefix}_printers_online $onlinePrinters")
        [void]$metrics.Add("")

        [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_printers_offline" "Offline printers" "gauge"))
        [void]$metrics.Add("${MetricPrefix}_printers_offline $offlinePrinters")
        [void]$metrics.Add("")

        [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_printers_error" "Printers in error state" "gauge"))
        [void]$metrics.Add("${MetricPrefix}_printers_error $errorPrinters")
        [void]$metrics.Add("")
    }

    # ========================================================================
    # PRINT JOB METRICS
    # ========================================================================

    $jobs = Get-PrintJob -PrinterName * -ErrorAction SilentlyContinue

    $totalJobs = 0
    $printingJobs = 0
    $queuedJobs = 0
    $errorJobs = 0
    $totalPages = 0
    $totalBytes = 0

    if ($jobs) {
        $totalJobs = @($jobs).Count

        foreach ($job in $jobs) {
            $totalPages += $job.TotalPages
            $totalBytes += $job.Size

            switch ($job.JobStatus) {
                { $_ -match "Printing" } { $printingJobs++ }
                { $_ -match "Error" } { $errorJobs++ }
                default { $queuedJobs++ }
            }
        }
    }

    [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_jobs_total" "Total print jobs in queue" "gauge"))
    [void]$metrics.Add("${MetricPrefix}_jobs_total $totalJobs")
    [void]$metrics.Add("")

    [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_jobs_printing" "Jobs currently printing" "gauge"))
    [void]$metrics.Add("${MetricPrefix}_jobs_printing $printingJobs")
    [void]$metrics.Add("")

    [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_jobs_queued" "Jobs waiting in queue" "gauge"))
    [void]$metrics.Add("${MetricPrefix}_jobs_queued $queuedJobs")
    [void]$metrics.Add("")

    [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_jobs_error" "Jobs in error state" "gauge"))
    [void]$metrics.Add("${MetricPrefix}_jobs_error $errorJobs")
    [void]$metrics.Add("")

    [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_jobs_pages_total" "Total pages across all queued jobs" "gauge"))
    [void]$metrics.Add("${MetricPrefix}_jobs_pages_total $totalPages")
    [void]$metrics.Add("")

    [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_jobs_bytes_total" "Total bytes across all queued jobs" "gauge"))
    [void]$metrics.Add("${MetricPrefix}_jobs_bytes_total $totalBytes")
    [void]$metrics.Add("")

    # Per-printer job counts
    if ($printers -and $jobs) {
        [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_queue_jobs" "Jobs per printer queue" "gauge"))
        foreach ($printer in $printers) {
            $name = Get-PrometheusEscape $printer.Name
            $pJobs = @($jobs | Where-Object { $_.PrinterName -eq $printer.Name }).Count
            [void]$metrics.Add("${MetricPrefix}_queue_jobs{printer=`"$name`"} $pJobs")
        }
        [void]$metrics.Add("")
    }

    # ========================================================================
    # PRINT PORT METRICS
    # ========================================================================

    $ports = Get-PrinterPort -ErrorAction SilentlyContinue
    if ($ports) {
        [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_ports_total" "Total configured printer ports" "gauge"))
        [void]$metrics.Add("${MetricPrefix}_ports_total $(@($ports).Count)")
        [void]$metrics.Add("")
    }

    # ========================================================================
    # DRIVER METRICS
    # ========================================================================

    $drivers = Get-PrinterDriver -ErrorAction SilentlyContinue
    if ($drivers) {
        [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_drivers_total" "Total installed printer drivers" "gauge"))
        [void]$metrics.Add("${MetricPrefix}_drivers_total $(@($drivers).Count)")
        [void]$metrics.Add("")
    }

    # ========================================================================
    # PERFORMANCE COUNTERS
    # ========================================================================

    $perfCounter = Get-Counter -Counter "\Print Queue(_Total)\Total Jobs Printed" -ErrorAction SilentlyContinue
    if ($perfCounter) {
        $totalPrinted = [math]::Round($perfCounter.CounterSamples[0].CookedValue)
        [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_jobs_printed_total" "Total jobs printed (lifetime)" "counter"))
        [void]$metrics.Add("${MetricPrefix}_jobs_printed_total $totalPrinted")
        [void]$metrics.Add("")
    }

    # ========================================================================
    # EXPORTER RUNTIME
    # ========================================================================

    $endTime = Get-Date
    $duration = [math]::Round(($endTime - $startTime).TotalSeconds, 2)
    $timestamp = [math]::Round((Get-Date -UFormat %s), 0)

    [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_exporter_duration_seconds" "Script execution time" "gauge"))
    [void]$metrics.Add("${MetricPrefix}_exporter_duration_seconds $duration")
    [void]$metrics.Add("")

    [void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_exporter_last_run_timestamp" "Unix timestamp of last run" "gauge"))
    [void]$metrics.Add("${MetricPrefix}_exporter_last_run_timestamp $timestamp")

    return ($metrics -join "`n")
}

# ============================================================================
# OUTPUT MODES
# ============================================================================

if ($Install) {
    $scriptPath = $MyInvocation.MyCommand.Path
    $action = New-ScheduledTaskAction -Execute "powershell.exe" `
        -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`" -TextFile"
    $trigger = New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Seconds $Interval) -Once -At (Get-Date)
    $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
    $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
    Register-ScheduledTask -TaskName "Print Server Metrics Exporter" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force
    Write-Host "Scheduled task 'Print Server Metrics Exporter' created (interval: ${Interval}s)"
    exit 0
}

if ($Listen -ne "") {
    $port = $Listen -replace '.*:', ''
    if (-not $port) { $port = "9197" }
    $listener = [System.Net.HttpListener]::new()
    $listener.Prefixes.Add("http://+:$port/")
    $listener.Start()
    Write-Host "Print Server exporter listening on port $port..."

    while ($listener.IsListening) {
        $context = $listener.GetContext()
        $response = $context.Response
        $output = Get-PrintMetrics
        $buffer = [System.Text.Encoding]::UTF8.GetBytes($output)
        $response.ContentType = "text/plain; version=0.0.4"
        $response.ContentLength64 = $buffer.Length
        $response.OutputStream.Write($buffer, 0, $buffer.Length)
        $response.Close()
    }
} elseif ($TextFile -or $OutFile -ne "") {
    $outputPath = if ($OutFile -ne "") { $OutFile } else { Join-Path $TextfileDir "print-server.prom" }
    $outputDir = Split-Path $outputPath -Parent
    if (-not (Test-Path $outputDir)) { New-Item -ItemType Directory -Path $outputDir -Force | Out-Null }
    $tempFile = Join-Path $outputDir ".print-server-metrics.tmp"
    $metricsOutput = Get-PrintMetrics
    $metricsOutput | Out-File -FilePath $tempFile -Encoding utf8 -NoNewline
    Move-Item -Path $tempFile -Destination $outputPath -Force
    Write-Host "Metrics written to $outputPath"
} else {
    Get-PrintMetrics
}
