param(
  [Parameter(Mandatory = $true)]
  [string]$Root,
  [Parameter(Mandatory = $true)]
  [int]$Port,
  [Parameter(Mandatory = $true)]
  [string]$InstallRoot
)

$ErrorActionPreference = "Stop"
$rootPath = [System.IO.Path]::GetFullPath($Root)
$pidFile = Join-Path $InstallRoot "server.pid"
Set-Content -LiteralPath $pidFile -Value $PID -Encoding ASCII

function Get-ContentType {
  param([string]$Path)
  switch ([System.IO.Path]::GetExtension($Path).ToLowerInvariant()) {
    ".html" { "text/html; charset=utf-8"; break }
    ".js" { "application/javascript; charset=utf-8"; break }
    ".css" { "text/css; charset=utf-8"; break }
    ".json" { "application/json; charset=utf-8"; break }
    ".webmanifest" { "application/manifest+json; charset=utf-8"; break }
    ".png" { "image/png"; break }
    ".jpg" { "image/jpeg"; break }
    ".jpeg" { "image/jpeg"; break }
    ".ico" { "image/x-icon"; break }
    default { "application/octet-stream"; break }
  }
}

function Send-Response {
  param(
    [System.Net.Sockets.NetworkStream]$Stream,
    [int]$Status,
    [string]$StatusText,
    [byte[]]$Body,
    [string]$ContentType = "text/plain; charset=utf-8",
    [bool]$HeadOnly = $false
  )
  $headers = "HTTP/1.1 $Status $StatusText`r`nContent-Length: $($Body.Length)`r`nContent-Type: $ContentType`r`nCache-Control: no-store`r`nConnection: close`r`n`r`n"
  $headerBytes = [System.Text.Encoding]::ASCII.GetBytes($headers)
  $Stream.Write($headerBytes, 0, $headerBytes.Length)
  if (!$HeadOnly -and $Body.Length -gt 0) {
    $Stream.Write($Body, 0, $Body.Length)
  }
  $Stream.Flush()
}

function Send-JsonResponse {
  param(
    [System.Net.Sockets.NetworkStream]$Stream,
    [object]$Payload,
    [int]$Status = 200,
    [string]$StatusText = "OK"
  )
  $json = $Payload | ConvertTo-Json -Depth 6 -Compress
  Send-Response -Stream $Stream -Status $Status -StatusText $StatusText -Body ([System.Text.Encoding]::UTF8.GetBytes($json)) -ContentType "application/json; charset=utf-8"
}

function Get-SafeFileName {
  param([string]$Name)
  $safe = if ([string]::IsNullOrWhiteSpace($Name)) { "know-god-more-certificate.jpg" } else { $Name }
  $safe = [System.Text.RegularExpressions.Regex]::Replace($safe, "[^A-Za-z0-9._-]", "_")
  if (!$safe.ToLowerInvariant().EndsWith(".jpg") -and !$safe.ToLowerInvariant().EndsWith(".jpeg")) {
    $safe = "$safe.jpg"
  }
  return $safe
}

function Save-CertificateJpeg {
  param(
    [string]$FileName,
    [string]$DataUrl,
    [string]$Destination = "pictures"
  )
  if ([string]::IsNullOrWhiteSpace($DataUrl)) {
    throw "Certificate image data is empty."
  }
  $base64 = $DataUrl
  $comma = $base64.IndexOf(",")
  if ($comma -ge 0) {
    $base64 = $base64.Substring($comma + 1)
  }
  $bytes = [System.Convert]::FromBase64String($base64)
  $destinationName = if ($Destination -and $Destination.ToLowerInvariant() -eq "downloads") { "downloads" } else { "pictures" }
  if ($destinationName -eq "downloads") {
    $baseFolder = Join-Path ([Environment]::GetFolderPath("UserProfile")) "Downloads"
  } else {
    $baseFolder = [Environment]::GetFolderPath("MyPictures")
  }
  if ([string]::IsNullOrWhiteSpace($baseFolder)) {
    $baseFolder = [Environment]::GetFolderPath("MyDocuments")
  }
  $safeFileName = Get-SafeFileName -Name $FileName
  $folderLabel = if ($destinationName -eq "downloads") { "Downloads\Know God More" } else { "Pictures\Know God More" }
  $dir = Join-Path $baseFolder "Know God More"
  try {
    New-Item -ItemType Directory -Path $dir -Force | Out-Null
    $path = Join-Path $dir $safeFileName
    [System.IO.File]::WriteAllBytes($path, $bytes)
    return @{ path = $path; folder = $folderLabel; fallback = $false }
  } catch {
    $fallbackDir = Join-Path $InstallRoot "SAVED CERTIFICATES"
    New-Item -ItemType Directory -Path $fallbackDir -Force | Out-Null
    $fallbackPath = Join-Path $fallbackDir $safeFileName
    [System.IO.File]::WriteAllBytes($fallbackPath, $bytes)
    return @{
      path = $fallbackPath
      folder = "the app SAVED CERTIFICATES folder"
      fallback = $true
      originalFolder = $folderLabel
      originalError = $_.Exception.Message
    }
  }
}

function Save-PrintHtml {
  param(
    [string]$JobName,
    [string]$Html
  )
  if ([string]::IsNullOrWhiteSpace($Html)) {
    throw "Print document is empty."
  }
  $safe = Get-SafeFileName -Name "$JobName.html"
  $safe = [System.IO.Path]::ChangeExtension($safe, ".html")
  $dir = Join-Path ([Environment]::GetFolderPath("MyDocuments")) "Know God More\Print Jobs"
  New-Item -ItemType Directory -Path $dir -Force | Out-Null
  $path = Join-Path $dir $safe
  [System.IO.File]::WriteAllText($path, $Html, [System.Text.Encoding]::UTF8)
  return $path
}

function Invoke-PrintFile {
  param([string]$Path)
  try {
    Start-Process -FilePath $Path -Verb Print -WindowStyle Minimized
    return $true
  } catch {
    try {
      Start-Process -FilePath $Path
    } catch {}
    return $false
  }
}

$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Parse("127.0.0.1"), $Port)
$listener.Start()

while ($true) {
  $client = $listener.AcceptTcpClient()
  try {
    $stream = $client.GetStream()
    $reader = New-Object System.IO.StreamReader($stream, [System.Text.Encoding]::ASCII, $false, 4096, $true)
    $requestLine = $reader.ReadLine()
    if ([string]::IsNullOrWhiteSpace($requestLine)) {
      $client.Close()
      continue
    }

    $parts = $requestLine.Split(" ")
    $method = $parts[0].ToUpperInvariant()
    $url = $parts[1]
    $headers = @{}
    while (($line = $reader.ReadLine()) -ne $null -and $line -ne "") {
      $separator = $line.IndexOf(":")
      if ($separator -gt 0) {
        $headers[$line.Substring(0, $separator).Trim().ToLowerInvariant()] = $line.Substring($separator + 1).Trim()
      }
    }

    $headOnly = $method -eq "HEAD"
    $pathOnly = $url.Split("?")[0]

    if ($method -eq "POST" -and $pathOnly.StartsWith("/kgm-")) {
      $contentLength = 0
      if ($headers.ContainsKey("content-length")) {
        [int]::TryParse($headers["content-length"], [ref]$contentLength) | Out-Null
      }
      $body = ""
      if ($contentLength -gt 0) {
        $bodyBuilder = New-Object System.Text.StringBuilder
        $remaining = $contentLength
        while ($remaining -gt 0) {
          $chunkSize = [Math]::Min(8192, $remaining)
          $buffer = New-Object char[] $chunkSize
          $read = $reader.Read($buffer, 0, $chunkSize)
          if ($read -le 0) {
            break
          }
          [void]$bodyBuilder.Append($buffer, 0, $read)
          $remaining -= $read
        }
        $body = $bodyBuilder.ToString()
      }
      $payload = if ([string]::IsNullOrWhiteSpace($body)) { @{} } else { $body | ConvertFrom-Json }

      if ($pathOnly -eq "/kgm-save-jpeg") {
        $destination = if ($payload.destination) { [string]$payload.destination } else { "pictures" }
        $saved = Save-CertificateJpeg -FileName $payload.fileName -DataUrl $payload.dataUrl -Destination $destination
        $message = if ($saved.fallback) { "Certificate saved in $($saved.folder) because Windows blocked $($saved.originalFolder)." } else { "Certificate saved in $($saved.folder)." }
        Send-JsonResponse -Stream $stream -Payload @{ ok = $true; path = $saved.path; fallback = $saved.fallback; message = $message }
        $client.Close()
        continue
      }

      if ($pathOnly -eq "/kgm-share-jpeg") {
        $saved = Save-CertificateJpeg -FileName $payload.fileName -DataUrl $payload.dataUrl -Destination "pictures"
        $savedPath = $saved.path
        try { Start-Process -FilePath "explorer.exe" -ArgumentList "/select,`"$savedPath`"" } catch {}
        $message = if ($saved.fallback) { "Certificate saved in the app SAVED CERTIFICATES folder and opened for sharing." } else { "Certificate saved and opened for sharing." }
        Send-JsonResponse -Stream $stream -Payload @{ ok = $true; path = $savedPath; fallback = $saved.fallback; message = $message }
        $client.Close()
        continue
      }

      if ($pathOnly -eq "/kgm-print-jpeg") {
        $saved = Save-CertificateJpeg -FileName $payload.fileName -DataUrl $payload.dataUrl -Destination "pictures"
        $savedPath = $saved.path
        $printed = Invoke-PrintFile -Path $savedPath
        $message = if ($printed) { "Certificate sent to the Windows print service." } else { "Certificate saved and opened. Choose Print from the viewer." }
        Send-JsonResponse -Stream $stream -Payload @{ ok = $true; path = $savedPath; fallback = $saved.fallback; message = $message }
        $client.Close()
        continue
      }

      if ($pathOnly -eq "/kgm-print-html") {
        $htmlPath = Save-PrintHtml -JobName $payload.jobName -Html $payload.html
        $printed = Invoke-PrintFile -Path $htmlPath
        $message = if ($printed) { "Competition certificates sent to the Windows print service." } else { "Competition certificates opened. Choose Print from the viewer." }
        Send-JsonResponse -Stream $stream -Payload @{ ok = $true; path = $htmlPath; message = $message }
        $client.Close()
        continue
      }

      Send-JsonResponse -Stream $stream -Status 404 -StatusText "Not Found" -Payload @{ ok = $false; message = "Unknown Know God More certificate action." }
      $client.Close()
      continue
    }

    if ($method -ne "GET" -and $method -ne "HEAD") {
      Send-Response -Stream $stream -Status 405 -StatusText "Method Not Allowed" -Body ([System.Text.Encoding]::UTF8.GetBytes("Method not allowed")) -HeadOnly $headOnly
      $client.Close()
      continue
    }

    if ($url -eq "/kgm-health") {
      Send-Response -Stream $stream -Status 200 -StatusText "OK" -Body ([System.Text.Encoding]::UTF8.GetBytes("Know God More")) -HeadOnly $headOnly
      $client.Close()
      continue
    }

    $decodedPath = [System.Uri]::UnescapeDataString($pathOnly).TrimStart("/")
    if ([string]::IsNullOrWhiteSpace($decodedPath)) {
      $decodedPath = "index.html"
    }

    $localPath = [System.IO.Path]::GetFullPath((Join-Path $rootPath ($decodedPath -replace "/", "\")))
    if (!$localPath.StartsWith($rootPath, [System.StringComparison]::OrdinalIgnoreCase) -or !(Test-Path -LiteralPath $localPath -PathType Leaf)) {
      Send-Response -Stream $stream -Status 404 -StatusText "Not Found" -Body ([System.Text.Encoding]::UTF8.GetBytes("Not found")) -HeadOnly $headOnly
      $client.Close()
      continue
    }

    $bytes = [System.IO.File]::ReadAllBytes($localPath)
    Send-Response -Stream $stream -Status 200 -StatusText "OK" -Body $bytes -ContentType (Get-ContentType -Path $localPath) -HeadOnly $headOnly
  } catch {
    try {
      Add-Content -LiteralPath (Join-Path $InstallRoot "server-error.log") -Value "$(Get-Date -Format o) $($_.Exception.Message)" -Encoding UTF8
    } catch {}
    try {
      $message = [System.Text.Encoding]::UTF8.GetBytes("Server error")
      Send-Response -Stream $stream -Status 500 -StatusText "Server Error" -Body $message
    } catch {}
  } finally {
    $client.Close()
  }
}
