# Samy.Http.ps1
# HTTP helpers, listener, and dispatcher

function Send-Text {
    param($Context, $Text)
    if (-not $Context -or -not $Context.Response) {
        return
    }
    $bytes = [Text.Encoding]::UTF8.GetBytes([string]$Text)
    $Context.Response.ContentType     = 'text/plain'
    $Context.Response.ContentLength64 = $bytes.Length
    $Context.Response.OutputStream.Write($bytes,0,$bytes.Length)
    $Context.Response.OutputStream.Close()
}

function Send-HTML {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)][object] $Context,
        [Parameter(Mandatory = $true)][string] $Html
    )
    if (-not $Context -or -not $Context.Response) {
        return
    }
    $bytes = [Text.Encoding]::UTF8.GetBytes($Html)
    $Context.Response.ContentType     = 'text/html'
    $Context.Response.ContentLength64 = $bytes.Length
    $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length)
    $Context.Response.OutputStream.Close()
}

function Send-JSON {
    [CmdletBinding()]
    param(
        $Context,
        $Object
    )

    if (-not $Context -or -not $Context.Response) {
        return
    }

    try {
        if ($null -eq $Object) {
            Write-LogHybrid "Send-JSON called with `$null object; returning empty JSON array." Warning Printers -LogToEvent
            $json = '[]'
        }
        else {
            try {
                $json = $Object | ConvertTo-Json -Depth 5 -ErrorAction Stop
            }
            catch {
                Write-LogHybrid "Send-JSON serialization failed: $($_.Exception.Message); returning empty JSON array." Error Printers -LogToEvent
                $json = '[]'
            }
        }

        $json  = [string]$json
        $bytes = [Text.Encoding]::UTF8.GetBytes($json)
        $Context.Response.ContentType     = 'application/json'
        $Context.Response.ContentLength64 = $bytes.Length
        $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length)
        $Context.Response.OutputStream.Close()
    }
    catch {
        Write-LogHybrid "Send-JSON fatal error: $($_.Exception.Message)" Error Printers -LogToEvent
        try {
            $fallback = '[]'
            $bytes = [Text.Encoding]::UTF8.GetBytes($fallback)
            $Context.Response.ContentType     = 'application/json'
            $Context.Response.ContentLength64 = $bytes.Length
            $Context.Response.OutputStream.Write($bytes, 0, $bytes.Length)
            $Context.Response.OutputStream.Close()
        }
        catch {
        }
    }
}

function Invoke-TasksCompleted {
    param($Context)

    Write-LogHybrid "All UI-selected tasks processed" Info UI -LogToEvent
    Send-Text $Context "Tasks completion acknowledged."
}

function Get-NextFreePort {
    param([int]$Start)

    for ($p = [Math]::Max(1024,$Start); $p -lt 65535; $p++) {
        $l = [System.Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, $p)
        try {
            $l.Start()
            $l.Stop()
            return $p
        }
        catch {
        }
    }
    throw "No free TCP port available."
}

function Dispatch-Request {
    param($Context)

    $path = $Context.Request.Url.AbsolutePath.TrimStart('/')

    if ($path -eq 'quit') {
        Write-LogHybrid "Shutdown requested" Info Server -LogToEvent
        Send-Text $Context "Server shutting down."
        $Global:Listener.Stop()
        return
    }

    if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'tasksCompleted') {
        Invoke-TasksCompleted $Context
        return
    }

    if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'getpw') {
        Invoke-FetchSites $Context
        return
    }

    if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'renameComputer') {
        Invoke-RenameComputer $Context
        return
    }

    if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'getprinters') {
        Invoke-GetPrinters $Context
        return
    }

    if ($Context.Request.HttpMethod -eq 'POST' -and $path -eq 'installprinters') {
        Invoke-InstallPrinters $Context
        return
    }

    if ($path -in @('', 'onboard', 'offboard', 'tweaks', 'SVSApps')) {
        $page = if ($path -eq '') { 'onboard' } else { $path }
        $html = Get-UIHtml -Page $page
        Send-HTML $Context $html
        return
    }

    $task = $Global:SamyTasks | Where-Object Name -EQ $path
    if ($task) {
        & $task.HandlerFn $Context
        return
    }

    $Context.Response.StatusCode = 404
    Send-Text $Context '404 - Not Found'
}

function Start-SamyHttpServer {
    param(
        [Parameter(Mandatory)][int]$Port
    )

    $Global:Listener = [System.Net.HttpListener]::new()
    $primaryPrefix  = "http://localhost:$Port/"
    $wildcardPrefix = "http://+:$Port/"

    try {
        $Global:Listener.Prefixes.Add($primaryPrefix)
        $Global:Listener.Start()
        Write-LogHybrid "Listening on $primaryPrefix" Info Server -LogToEvent
    }
    catch [System.Net.HttpListenerException] {
        if ($_.Exception.ErrorCode -eq 5) {
            Write-LogHybrid "Access denied on $primaryPrefix. Attempting URL ACL..." Warning Server -LogToEvent
            try {
                $user = "$env:USERDOMAIN\$env:USERNAME"
                if (-not $user.Trim()) {
                    $user = $env:USERNAME
                }
                Start-Process -FilePath "netsh" -ArgumentList "http add urlacl url=$wildcardPrefix user=`"$user`" listen=yes" -Verb RunAs -WindowStyle Hidden -Wait
                $Global:Listener = [System.Net.HttpListener]::new()
                $Global:Listener.Prefixes.Add($wildcardPrefix)
                $Global:Listener.Start()
                Write-LogHybrid "Listening on $wildcardPrefix (URL ACL added for $user)" Success Server -LogToEvent
            }
            catch {
                Write-LogHybrid "URL ACL registration failed: $($_.Exception.Message)" Error Server -LogToEvent
                return
            }
        }
        elseif ($_.Exception.NativeErrorCode -in 32,183) {
            $old = $Port
            $Port = Get-NextFreePort -Start ($Port + 1)
            $Global:Listener = [System.Net.HttpListener]::new()
            $primaryPrefix = "http://localhost:$Port/"
            $Global:Listener.Prefixes.Add($primaryPrefix)
            $Global:Listener.Start()
            Write-LogHybrid "Port $old busy. Listening on $primaryPrefix" Warning Server -LogToEvent
        }
        else {
            Write-LogHybrid "HttpListener start failed: $($_.Exception.Message)" Error Server -LogToEvent
            return
        }
    }

    try {
        while ($Global:Listener.IsListening) {
            $ctx = $Global:Listener.GetContext()
            try {
                Dispatch-Request $ctx
            }
            catch {
                Write-LogHybrid "Dispatch error: $($_.Exception.Message)" Error Server -LogToEvent
            }
        }
    }
    finally {
        $Global:Listener.Close()
        Write-LogHybrid "Listener closed." Info Server -LogToEvent
    }
}
