###############################################################################
# gitlab-smoke-tests.ps1 - Verify GitLab instance health after upgrades
#
# PowerShell port of gitlab-smoke-tests.sh. Zero external dependencies
# beyond PowerShell 5.1+ and git. Runs on Windows, Linux, and macOS.
#
# Author: Phil Connor
# Contact: contact@mylinux.work
# License: MIT
# Version 1.00
#
# Usage:
#   $env:GITLAB_URL = "https://gitlab.example.com"
#   $env:GITLAB_TOKEN = "glpat-xxxxxxxxxxxx"
#   .\gitlab-smoke-tests.ps1
#   .\gitlab-smoke-tests.ps1 -SkipGit -SkipRegistry
#   .\gitlab-smoke-tests.ps1 -Insecure -Format junit
#   .\gitlab-smoke-tests.ps1 -Format tap
###############################################################################

[CmdletBinding()]
param(
    [string]$GitLabUrl = $env:GITLAB_URL,
    [string]$GitLabToken = $env:GITLAB_TOKEN,
    [string]$GitLabUser = $(if ($env:GITLAB_USER) { $env:GITLAB_USER } else { "root" }),
    [string]$HealthToken = $env:GITLAB_HEALTH_TOKEN,
    [string]$ProjectPrefix = $(if ($env:SMOKE_PROJECT_PREFIX) { $env:SMOKE_PROJECT_PREFIX } else { "smoke-test" }),
    [int]$Timeout = $(if ($env:CURL_TIMEOUT) { [int]$env:CURL_TIMEOUT } else { 10 }),
    [switch]$Insecure,
    [switch]$SkipGit,
    [switch]$SkipRegistry,
    [switch]$SkipCleanup,
    [ValidateSet("text","tap","junit")]
    [string]$Format = "text",
    [string]$JunitFile = "smoke-results.xml",
    [switch]$NoColor
)

$ErrorActionPreference = "Continue"

# ============================================================================
# STATE
# ============================================================================

$script:Pass = 0
$script:Fail = 0
$script:Skip = 0
$script:Total = 0
$script:Results = @()
$script:CleanupProjectId = ""
$script:TmpDir = ""
$script:StartTime = $null
$script:GitCloneOk = $false

# ============================================================================
# COLORS
# ============================================================================

function Write-Color {
    param([string]$Text, [string]$Color = "White")
    if ($NoColor) {
        Write-Host $Text
    } else {
        Write-Host $Text -ForegroundColor $Color
    }
}

function Write-Log   { param([string]$Msg) Write-Color "[INFO] $Msg" "Cyan" }
function Write-Warn  { param([string]$Msg) Write-Color "[WARN] $Msg" "Yellow" }
function Write-Err   { param([string]$Msg) Write-Color "[ERROR] $Msg" "Red" }

# ============================================================================
# TEST RESULT RECORDING
# ============================================================================

function Record-Pass {
    param([string]$Name, [string]$Detail = "")
    $script:Pass++
    $script:Total++
    $script:Results += [PSCustomObject]@{ Status="PASS"; Name=$Name; Detail=$Detail }
    if ($Format -eq "tap") {
        Write-Host "ok $($script:Total) - $Name"
    } else {
        $msg = "  $(if($NoColor){'[PASS]'}else{[char]0x2713}) $Name"
        if ($Detail) { $msg += " - $Detail" }
        Write-Color $msg "Green"
    }
}

function Record-Fail {
    param([string]$Name, [string]$Detail = "")
    $script:Fail++
    $script:Total++
    $script:Results += [PSCustomObject]@{ Status="FAIL"; Name=$Name; Detail=$Detail }
    if ($Format -eq "tap") {
        Write-Host "not ok $($script:Total) - $Name"
        if ($Detail) { Write-Host "  # $Detail" }
    } else {
        $msg = "  $(if($NoColor){'[FAIL]'}else{[char]0x2717}) $Name"
        if ($Detail) { $msg += " - $Detail" }
        Write-Color $msg "Red"
    }
}

function Record-Skip {
    param([string]$Name, [string]$Reason = "")
    $script:Skip++
    $script:Total++
    $script:Results += [PSCustomObject]@{ Status="SKIP"; Name=$Name; Detail=$Reason }
    if ($Format -eq "tap") {
        Write-Host "ok $($script:Total) - $Name # SKIP $Reason"
    } else {
        $msg = "  $(if($NoColor){'[SKIP]'}else{[char]0x2298}) $Name"
        if ($Reason) { $msg += " - $Reason" }
        Write-Color $msg "Yellow"
    }
}

# ============================================================================
# HTTP HELPERS
# ============================================================================

function Invoke-GitLabApi {
    param(
        [string]$Method,
        [string]$Endpoint,
        [string]$Body = $null,
        [switch]$StatusOnly
    )

    $uri = "$GitLabUrl/api/v4$Endpoint"
    $headers = @{ "Content-Type" = "application/json" }
    if ($GitLabToken) { $headers["PRIVATE-TOKEN"] = $GitLabToken }

    $params = @{
        Uri             = $uri
        Method          = $Method
        Headers         = $headers
        TimeoutSec      = $Timeout
        UseBasicParsing = $true
        ErrorAction     = "Stop"
    }

    if ($Insecure -and $PSVersionTable.PSVersion.Major -ge 7) {
        $params["SkipCertificateCheck"] = $true
    }

    if ($Body) {
        $params["Body"] = $Body
    }

    try {
        if ($StatusOnly) {
            $response = Invoke-WebRequest @params
            return [int]$response.StatusCode
        } else {
            return Invoke-RestMethod @params
        }
    } catch {
        if ($StatusOnly) {
            if ($_.Exception.Response) {
                return [int]$_.Exception.Response.StatusCode
            }
            return 0
        }
        return $null
    }
}

function Invoke-HealthCheck {
    param([string]$Path)

    $uri = "$GitLabUrl$Path"
    if ($HealthToken) { $uri += "?token=$HealthToken" }

    $params = @{
        Uri             = $uri
        Method          = "GET"
        TimeoutSec      = $Timeout
        UseBasicParsing = $true
        ErrorAction     = "Stop"
    }

    if ($Insecure -and $PSVersionTable.PSVersion.Major -ge 7) {
        $params["SkipCertificateCheck"] = $true
    }

    try {
        $response = Invoke-WebRequest @params
        return [int]$response.StatusCode
    } catch {
        if ($_.Exception.Response) {
            return [int]$_.Exception.Response.StatusCode
        }
        return 0
    }
}

# ============================================================================
# TLS HELPER
# ============================================================================

function Get-TlsCertExpiry {
    param([string]$HostName, [int]$Port = 443)

    try {
        $tcpClient = New-Object System.Net.Sockets.TcpClient
        $tcpClient.ReceiveTimeout = $Timeout * 1000
        $tcpClient.SendTimeout = $Timeout * 1000
        $tcpClient.Connect($HostName, $Port)

        $sslStream = New-Object System.Net.Security.SslStream(
            $tcpClient.GetStream(), $false,
            { param($s,$c,$ch,$e) return $true }
        )
        $sslStream.AuthenticateAsClient($HostName)

        $cert = $sslStream.RemoteCertificate
        $expiry = [DateTime]$cert.GetExpirationDateString()

        $sslStream.Dispose()
        $tcpClient.Dispose()

        return $expiry
    } catch {
        return $null
    }
}

# ============================================================================
# TEST SUITES
# ============================================================================

# -- 1. Connectivity --------------------------------------------------------

function Test-Connectivity {
    Write-Host ""
    Write-Color "Connectivity" "White"

    # 1a. Health endpoint
    $code = Invoke-HealthCheck "/-/health"
    if ($code -eq 200) {
        Record-Pass "GitLab health endpoint reachable" "HTTP $code"
    } else {
        Record-Fail "GitLab health endpoint reachable" "HTTP $code"
    }

    # 1b. Readiness
    $code = Invoke-HealthCheck "/-/readiness"
    if ($code -eq 200) {
        Record-Pass "GitLab readiness check" "HTTP $code"
    } else {
        Record-Fail "GitLab readiness check" "HTTP $code"
    }

    # 1c. Liveness
    $code = Invoke-HealthCheck "/-/liveness"
    if ($code -eq 200) {
        Record-Pass "GitLab liveness check" "HTTP $code"
    } else {
        Record-Fail "GitLab liveness check" "HTTP $code"
    }

    # 1d. TLS certificate
    if ($GitLabUrl -match "^https://") {
        $hostPart = $GitLabUrl -replace "^https://", "" -replace "/.*", "" -replace ":.*", ""
        $portPart = 443
        if ($GitLabUrl -match ":(\d+)") { $portPart = [int]$Matches[1] }

        $expiry = Get-TlsCertExpiry -HostName $hostPart -Port $portPart
        if ($expiry) {
            $daysLeft = [math]::Floor(($expiry - (Get-Date)).TotalDays)
            if ($daysLeft -gt 30) {
                Record-Pass "TLS certificate valid" "$daysLeft days remaining"
            } elseif ($daysLeft -gt 0) {
                Record-Pass "TLS certificate valid" "$daysLeft days remaining (renew soon)"
            } else {
                Record-Fail "TLS certificate valid" "expired or expiring in $daysLeft days"
            }
        } else {
            Record-Skip "TLS certificate check" "could not retrieve certificate"
        }
    } else {
        Record-Skip "TLS certificate check" "not using HTTPS"
    }
}

# -- 2. API ----------------------------------------------------------------

function Test-Api {
    Write-Host ""
    Write-Color "API" "White"

    # 2a. Version
    $versionData = Invoke-GitLabApi -Method GET -Endpoint "/version"
    if ($versionData -and $versionData.version) {
        Record-Pass "API version endpoint" "GitLab $($versionData.version) ($($versionData.revision))"
    } else {
        Record-Fail "API version endpoint" "no version returned"
    }

    # 2b. Authentication
    $authStatus = Invoke-GitLabApi -Method GET -Endpoint "/user" -StatusOnly
    if ($authStatus -eq 200) {
        $userData = Invoke-GitLabApi -Method GET -Endpoint "/user"
        Record-Pass "API authentication" "authenticated as $($userData.username)"
    } elseif ($authStatus -eq 401) {
        Record-Fail "API authentication" "token rejected (HTTP 401)"
    } else {
        Record-Fail "API authentication" "HTTP $authStatus"
    }

    # 2c. List projects
    $projStatus = Invoke-GitLabApi -Method GET -Endpoint "/projects?per_page=1" -StatusOnly
    if ($projStatus -eq 200) {
        Record-Pass "API list projects" "database responding"
    } else {
        Record-Fail "API list projects" "HTTP $projStatus"
    }

    # 2d. List users
    $userStatus = Invoke-GitLabApi -Method GET -Endpoint "/users?per_page=1" -StatusOnly
    if ($userStatus -eq 200) {
        Record-Pass "API list users" "user directory accessible"
    } else {
        Record-Fail "API list users" "HTTP $userStatus"
    }

    # 2e. Sidekiq
    $sidekiq = Invoke-GitLabApi -Method GET -Endpoint "/sidekiq/compound_metrics"
    if ($sidekiq -and -not $sidekiq.error) {
        $procCount = 0
        if ($sidekiq.processes) { $procCount = @($sidekiq.processes).Count }
        Record-Pass "Sidekiq running" "$procCount process(es) responding"
    } else {
        Record-Fail "Sidekiq running" "could not query Sidekiq metrics"
    }

    # 2f. Runners
    $runnerStatus = Invoke-GitLabApi -Method GET -Endpoint "/runners/all?per_page=1" -StatusOnly
    if ($runnerStatus -eq 200) {
        Record-Pass "API runners endpoint" "runner management accessible"
    } elseif ($runnerStatus -eq 403) {
        Record-Skip "API runners endpoint" "token lacks admin scope"
    } else {
        Record-Fail "API runners endpoint" "HTTP $runnerStatus"
    }

    # 2g. Search
    $searchStatus = Invoke-GitLabApi -Method GET -Endpoint "/search?scope=projects&search=test" -StatusOnly
    if ($searchStatus -eq 200) {
        Record-Pass "API search" "search index responding"
    } elseif ($searchStatus -eq 403) {
        Record-Skip "API search" "search disabled or token lacks scope"
    } else {
        Record-Fail "API search" "HTTP $searchStatus"
    }
}

# -- 3. Git Operations -----------------------------------------------------

function Test-Git {
    if ($SkipGit) {
        Write-Host ""
        Write-Color "Git Operations" "White"
        Record-Skip "Git clone" "SkipGit specified"
        Record-Skip "Git push" "SkipGit specified"
        return
    }

    Write-Host ""
    Write-Color "Git Operations" "White"

    # Create test project
    $projectName = "$ProjectPrefix-$([DateTimeOffset]::UtcNow.ToUnixTimeSeconds())"
    $body = @{ name = $projectName; visibility = "private"; initialize_with_readme = $true } | ConvertTo-Json
    $project = Invoke-GitLabApi -Method POST -Endpoint "/projects" -Body $body

    if (-not $project -or -not $project.id) {
        Record-Fail "Create test project" "API returned no project ID"
        Record-Skip "Git clone" "no test project"
        Record-Skip "Git push" "no test project"
        return
    }

    $script:CleanupProjectId = $project.id
    Record-Pass "Create test project" "$projectName (ID: $($project.id))"

    # Build clone URL
    $httpUrl = $project.http_url_to_repo
    if (-not $httpUrl) {
        $httpUrl = "$GitLabUrl/$GitLabUser/$projectName.git"
    }

    # Rewrite origin if API returns an internal hostname
    $apiOrigin = if ($httpUrl -match "^(https?://[^/]+)") { $Matches[1] } else { "" }
    if ($apiOrigin -and $apiOrigin -ne $GitLabUrl) {
        $httpUrl = $httpUrl -replace [regex]::Escape($apiOrigin), $GitLabUrl
    }

    # Inject token
    if ($httpUrl -match "^https://") {
        $cloneUrl = $httpUrl -replace "^https://", "https://oauth2:${GitLabToken}@"
    } elseif ($httpUrl -match "^http://") {
        $cloneUrl = $httpUrl -replace "^http://", "http://oauth2:${GitLabToken}@"
    } else {
        $cloneUrl = $httpUrl
    }

    # Temp directory
    $script:TmpDir = Join-Path ([System.IO.Path]::GetTempPath()) "gitlab-smoke-$([guid]::NewGuid().ToString('N').Substring(0,8))"
    New-Item -ItemType Directory -Path $script:TmpDir -Force | Out-Null

    # Wait for repo init
    Start-Sleep -Seconds 2

    # Clone
    $gitArgs = @("clone")
    if ($Insecure) { $env:GIT_SSL_NO_VERIFY = "true" }

    $repoDir = Join-Path $script:TmpDir "repo"
    $cloneOutput = & git clone $cloneUrl $repoDir 2>&1
    $cloneRc = $LASTEXITCODE

    if ($cloneRc -eq 0) {
        $script:GitCloneOk = $true
        Record-Pass "Git clone (HTTPS)" "Gitaly responding"
    } else {
        $shortErr = ($cloneOutput | Select-String -Pattern "fatal|error" | Select-Object -First 1) -replace [regex]::Escape($GitLabToken), "[REDACTED]"
        Record-Fail "Git clone (HTTPS)" "$shortErr"
        return
    }

    # Push
    Push-Location $repoDir
    try {
        & git config user.email "smoke-test@example.com"
        & git config user.name "Smoke Test"
        "smoke test $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')" | Out-File -FilePath "smoke-test.txt" -Encoding utf8

        & git add smoke-test.txt
        & git commit -m "smoke test commit" 2>&1 | Out-Null

        $pushOutput = & git push origin main 2>&1
        $pushRc = $LASTEXITCODE
        if ($pushRc -ne 0) {
            $pushOutput = & git push origin master 2>&1
            $pushRc = $LASTEXITCODE
        }

        if ($pushRc -eq 0) {
            Record-Pass "Git push (HTTPS)" "write to Gitaly succeeded"
        } else {
            Record-Fail "Git push (HTTPS)" "push failed"
        }
    } finally {
        Pop-Location
    }
}

# -- 4. Container Registry -------------------------------------------------

function Test-Registry {
    if ($SkipRegistry) {
        Write-Host ""
        Write-Color "Container Registry" "White"
        Record-Skip "Registry API" "SkipRegistry specified"
        return
    }

    Write-Host ""
    Write-Color "Container Registry" "White"

    # Check if registry is enabled
    $registryEnabled = ""
    $settings = Invoke-GitLabApi -Method GET -Endpoint "/application/settings"
    if ($settings) {
        $registryEnabled = $settings.container_registry_enabled
    }

    if ($registryEnabled -eq $false) {
        Record-Skip "Registry API reachable" "container registry disabled in application settings"
        Record-Skip "Registry project endpoint" "container registry disabled in application settings"
        return
    }

    # Try registry v2 API
    $hostPart = $GitLabUrl -replace "^https?://", "" -replace "/.*", ""
    $registryStatus = 0

    $registryUrls = @(
        "$GitLabUrl`:5050/v2/",
        "https://${hostPart}:5050/v2/",
        "https://registry.${hostPart}/v2/"
    )

    foreach ($regUrl in $registryUrls) {
        try {
            $params = @{
                Uri             = $regUrl
                Method          = "GET"
                TimeoutSec      = $Timeout
                UseBasicParsing = $true
                ErrorAction     = "Stop"
            }
            if ($Insecure -and $PSVersionTable.PSVersion.Major -ge 7) {
                $params["SkipCertificateCheck"] = $true
            }
            $response = Invoke-WebRequest @params
            $registryStatus = [int]$response.StatusCode
            break
        } catch {
            if ($_.Exception.Response) {
                $registryStatus = [int]$_.Exception.Response.StatusCode
                if ($registryStatus -eq 401) { break }
            }
        }
    }

    if ($registryStatus -eq 200 -or $registryStatus -eq 401) {
        Record-Pass "Registry API reachable" "HTTP $registryStatus"
    } elseif ($registryStatus -eq 0) {
        if ($registryEnabled -eq $true) {
            Record-Fail "Registry API reachable" "enabled in settings but not reachable at standard ports/hosts"
        } else {
            Record-Skip "Registry API reachable" "not found at standard ports/hosts (settings unreadable - may need admin token)"
        }
    } else {
        Record-Fail "Registry API reachable" "HTTP $registryStatus"
    }

    # Project-level registry
    if ($script:CleanupProjectId) {
        $regStatus = Invoke-GitLabApi -Method GET -Endpoint "/projects/$($script:CleanupProjectId)/registry/repositories" -StatusOnly
        if ($regStatus -eq 200) {
            Record-Pass "Registry project endpoint" "project registry accessible"
        } elseif ($regStatus -eq 404) {
            Record-Skip "Registry project endpoint" "container registry not enabled for project"
        } else {
            Record-Fail "Registry project endpoint" "HTTP $regStatus"
        }
    }
}

# -- 5. CI/CD --------------------------------------------------------------

function Test-CICD {
    Write-Host ""
    Write-Color "CI/CD" "White"

    # Runners
    $runners = Invoke-GitLabApi -Method GET -Endpoint "/runners/all?per_page=100"
    if ($runners -is [array]) {
        $runnerCount = $runners.Count
        $onlineCount = @($runners | Where-Object { $_.status -eq "online" }).Count

        if ($onlineCount -gt 0) {
            Record-Pass "CI/CD runners online" "$onlineCount/$runnerCount runners online"
        } elseif ($runnerCount -gt 0) {
            Record-Fail "CI/CD runners online" "0/$runnerCount runners online"
        } else {
            Record-Skip "CI/CD runners online" "no runners registered"
        }
    } else {
        Record-Skip "CI/CD runners" "could not query runners (admin token required)"
    }

    # CI/CD settings
    $cicdStatus = Invoke-GitLabApi -Method GET -Endpoint "/application/settings" -StatusOnly
    if ($cicdStatus -eq 200) {
        Record-Pass "CI/CD settings accessible" "application settings readable"
    } elseif ($cicdStatus -eq 403) {
        Record-Skip "CI/CD settings accessible" "admin token required"
    } else {
        Record-Fail "CI/CD settings accessible" "HTTP $cicdStatus"
    }
}

# -- 6. Background Migrations ----------------------------------------------

function Test-Migrations {
    Write-Host ""
    Write-Color "Background Migrations" "White"

    $migrations = Invoke-GitLabApi -Method GET -Endpoint "/admin/batched_background_migrations?database=main"

    if ($migrations -is [array]) {
        $totalMig = $migrations.Count
        $failedMig = @($migrations | Where-Object { $_.status -eq "failed" }).Count
        $activeMig = @($migrations | Where-Object { $_.status -eq "active" }).Count
        $pausedMig = @($migrations | Where-Object { $_.status -eq "paused" }).Count
        $finishedMig = @($migrations | Where-Object { $_.status -eq "finished" }).Count

        if ($failedMig -gt 0) {
            Record-Fail "Background migrations" "$failedMig failed, $activeMig active, $pausedMig paused, $finishedMig finished of $totalMig"
        } elseif ($pausedMig -gt 0) {
            Record-Fail "Background migrations" "$pausedMig paused, $activeMig active, $finishedMig finished of $totalMig"
        } elseif ($activeMig -gt 0) {
            Record-Pass "Background migrations" "$activeMig active, $finishedMig finished of $totalMig (in progress)"
        } else {
            Record-Pass "Background migrations" "all $totalMig finished"
        }
    } else {
        $migStatus = Invoke-GitLabApi -Method GET -Endpoint "/admin/batched_background_migrations?database=main" -StatusOnly
        if ($migStatus -eq 403) {
            Record-Skip "Background migrations" "admin token required"
        } else {
            Record-Skip "Background migrations" "could not query (HTTP $migStatus)"
        }
    }
}

# -- 7. Components ---------------------------------------------------------

function Test-Components {
    Write-Host ""
    Write-Color "Components" "White"

    # Metadata
    $metadata = Invoke-GitLabApi -Method GET -Endpoint "/metadata"
    if ($metadata -and $metadata.version) {
        $edition = if ($metadata.enterprise -eq $true) { "EE" } else { "CE" }
        Record-Pass "GitLab metadata" "$($metadata.version) $edition"
    } elseif ($metadata) {
        Record-Pass "GitLab metadata" "endpoint reachable"
    } else {
        Record-Skip "GitLab metadata" "metadata endpoint not available"
    }

    # Statistics
    $stats = Invoke-GitLabApi -Method GET -Endpoint "/application/statistics"
    if ($stats -and $stats.active_users) {
        Record-Pass "Instance statistics" "$($stats.active_users) users, $($stats.projects) projects, $($stats.groups) groups"
    } elseif ($stats) {
        Record-Pass "Instance statistics" "endpoint reachable"
    } else {
        Record-Skip "Instance statistics" "admin token required"
    }

    # Gitaly (inferred)
    if ($script:GitCloneOk) {
        Record-Pass "Gitaly storage" "project created and cloned successfully"
    } elseif ($script:CleanupProjectId) {
        Record-Skip "Gitaly storage" "project created but clone was not tested or failed"
    }

    # PostgreSQL (inferred)
    $pgStatus = Invoke-GitLabApi -Method GET -Endpoint "/projects?per_page=1&order_by=updated_at" -StatusOnly
    if ($pgStatus -eq 200) {
        Record-Pass "PostgreSQL" "database queries succeeding"
    } else {
        Record-Fail "PostgreSQL" "sorted query failed (HTTP $pgStatus)"
    }

    # Redis (inferred)
    $redisStatus = Invoke-GitLabApi -Method GET -Endpoint "/user" -StatusOnly
    if ($redisStatus -eq 200) {
        Record-Pass "Redis" "session/cache operational (auth succeeded)"
    } else {
        Record-Skip "Redis" "cannot verify independently"
    }
}

# ============================================================================
# OUTPUT
# ============================================================================

function Write-Summary {
    $duration = [math]::Floor(((Get-Date) - $script:StartTime).TotalSeconds)

    Write-Host ""
    $separator = [string]::new([char]0x2500, 40)
    Write-Color $separator "White"
    Write-Color "Summary  $GitLabUrl" "White"

    $summaryLine = "  $($script:Pass) passed  $($script:Fail) failed  $($script:Skip) skipped  (${duration}s)"
    Write-Host $summaryLine
    Write-Color $separator "White"

    if ($script:Fail -eq 0) {
        Write-Color "All tests passed." "Green"
    } else {
        Write-Color "$($script:Fail) test(s) failed." "Red"
    }
}

function Write-TapHeader {
    Write-Host "TAP version 13"
}

function Write-TapFooter {
    Write-Host "1..$($script:Total)"
    Write-Host "# pass $($script:Pass)"
    Write-Host "# fail $($script:Fail)"
    Write-Host "# skip $($script:Skip)"
}

function Write-JunitReport {
    $duration = [math]::Floor(((Get-Date) - $script:StartTime).TotalSeconds)

    $xml = @"
<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="$($script:Total)" failures="$($script:Fail)" skipped="$($script:Skip)" time="$duration">
  <testsuite name="gitlab-smoke-tests" tests="$($script:Total)" failures="$($script:Fail)" skipped="$($script:Skip)" time="$duration">
"@

    foreach ($r in $script:Results) {
        $safeName = $r.Name -replace '&','&amp;' -replace '<','&lt;' -replace '>','&gt;' -replace '"','&quot;'
        $safeDetail = $r.Detail -replace '&','&amp;' -replace '<','&lt;' -replace '>','&gt;' -replace '"','&quot;'

        switch ($r.Status) {
            "PASS" {
                $xml += "`n    <testcase name=`"$safeName`" classname=`"smoke`">"
                if ($r.Detail) { $xml += "`n      <system-out>$safeDetail</system-out>" }
                $xml += "`n    </testcase>"
            }
            "FAIL" {
                $xml += "`n    <testcase name=`"$safeName`" classname=`"smoke`">"
                $xml += "`n      <failure message=`"$safeDetail`">FAILED: $safeName - $safeDetail</failure>"
                $xml += "`n    </testcase>"
            }
            "SKIP" {
                $xml += "`n    <testcase name=`"$safeName`" classname=`"smoke`">"
                $xml += "`n      <skipped message=`"$safeDetail`"/>"
                $xml += "`n    </testcase>"
            }
        }
    }

    $xml += "`n  </testsuite>"
    $xml += "`n</testsuites>"

    $xml | Out-File -FilePath $JunitFile -Encoding utf8
    Write-Log "JUnit report written to $JunitFile"
}

# ============================================================================
# CLEANUP
# ============================================================================

function Invoke-Cleanup {
    if ($script:CleanupProjectId -and -not $SkipCleanup) {
        try {
            Invoke-GitLabApi -Method DELETE -Endpoint "/projects/$($script:CleanupProjectId)" | Out-Null
        } catch { }
    }

    if ($script:TmpDir -and (Test-Path $script:TmpDir)) {
        Remove-Item -Recurse -Force $script:TmpDir -ErrorAction SilentlyContinue
    }

    if ($env:GIT_SSL_NO_VERIFY) {
        Remove-Item Env:\GIT_SSL_NO_VERIFY -ErrorAction SilentlyContinue
    }
}

# ============================================================================
# MAIN
# ============================================================================

function Show-Usage {
    @"
Usage: .\gitlab-smoke-tests.ps1 [OPTIONS]

Smoke-test a GitLab instance. PowerShell 5.1+, git only.
Designed for air-gapped environments.

Required environment variables:
  GITLAB_URL          GitLab base URL (https://gitlab.example.com)
  GITLAB_TOKEN        Personal access token (api scope; admin for full coverage)

Optional environment variables:
  GITLAB_HEALTH_TOKEN Health check access token
  GITLAB_USER         Username for git operations (default: root)

Parameters:
  -SkipGit            Skip git clone/push tests
  -SkipRegistry       Skip container registry tests
  -SkipCleanup        Don't delete the test project after run
  -Insecure           Allow self-signed TLS certificates
  -Timeout N          HTTP timeout in seconds (default: 10)
  -Format FORMAT      Output: text (default), tap, junit
  -JunitFile FILE     JUnit output path (default: smoke-results.xml)
  -NoColor            Disable colored output
  -Verbose            Show debug output

Examples:
  `$env:GITLAB_URL = "https://gitlab.example.com"
  `$env:GITLAB_TOKEN = "glpat-xxxxxxxxxxxx"
  .\gitlab-smoke-tests.ps1

  .\gitlab-smoke-tests.ps1 -Insecure -Format junit
  .\gitlab-smoke-tests.ps1 -SkipGit -SkipRegistry
  .\gitlab-smoke-tests.ps1 -Format tap
"@
}

# Handle PS 5.1 TLS and self-signed certs
if ($PSVersionTable.PSVersion.Major -lt 7) {
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    if ($Insecure) {
        Add-Type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAll : ICertificatePolicy {
    public bool CheckValidationResult(ServicePoint sp, X509Certificate cert,
        WebRequest req, int problem) { return true; }
}
"@ -ErrorAction SilentlyContinue
        [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAll
    }
}

# Validate
if (-not $GitLabUrl) {
    Write-Err "GITLAB_URL is required"
    Write-Host ""
    Show-Usage
    exit 1
}

if (-not $GitLabToken) {
    Write-Err "GITLAB_TOKEN is required"
    Write-Host ""
    Show-Usage
    exit 1
}

$GitLabUrl = $GitLabUrl.TrimEnd("/")
$script:StartTime = Get-Date

if ($Format -eq "tap") {
    Write-TapHeader
} else {
    Write-Host ""
    Write-Color "GitLab Smoke Tests" "White"
    Write-Host "Target: $GitLabUrl"
    Write-Host "Time:   $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')"
    Write-Host ""
}

try {
    Test-Connectivity
    Test-Api
    Test-Git
    Test-Registry
    Test-CICD
    Test-Migrations
    Test-Components
} finally {
    Invoke-Cleanup
}

if ($Format -eq "tap") {
    Write-TapFooter
} elseif ($Format -eq "junit") {
    Write-Summary
    Write-JunitReport
} else {
    Write-Summary
}

if ($script:Fail -eq 0) { exit 0 } else { exit 1 }
