#Requires -Version 7

<#
.SYNOPSIS
    Packs all MxAccessGateway clients into a single dist/ directory.

.DESCRIPTION
    Runs each language client's native packaging command:
      .NET    -> dotnet pack (NuGet)
      Python  -> python -m build (sdist + wheel)
      Rust    -> cargo package (.crate)
      Java    -> gradle assemble + jars (jar + sources + javadoc + pom)
      Go      -> skipped; use scripts/tag-go-module.ps1

    All artifacts land in -OutputDir (default: dist/).

    With -Publish, each language pushes its package to the internal Gitea
    feed. Requires GITEA_USERNAME and GITEA_TOKEN env vars.

.PARAMETER OutputDir
    Where to drop the packed artifacts. Default: ./dist

.PARAMETER Languages
    Subset of languages to pack. Default: all five.
    Values: dotnet, python, rust, java, go

.PARAMETER Publish
    After packing, upload to Gitea feeds. Requires:
      GITEA_USERNAME
      GITEA_TOKEN
    Will refuse to publish if either is missing.

.PARAMETER SkipTests
    Skip per-language regression tests before packing. Default: false.

.EXAMPLE
    pwsh scripts/pack-clients.ps1
    pwsh scripts/pack-clients.ps1 -Languages dotnet,python
    pwsh scripts/pack-clients.ps1 -Publish
#>

[CmdletBinding()]
param(
    [string]$OutputDir = (Join-Path $PSScriptRoot '..' 'dist'),
    [string[]]$Languages = @('dotnet', 'python', 'rust', 'java', 'go'),
    [switch]$Publish,
    [switch]$SkipTests
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

# Normalize comma-separated strings that shells may pass as a single element.
$validLanguages = @('dotnet', 'python', 'rust', 'java', 'go')
$Languages = @($Languages | ForEach-Object { $_ -split ',' } | ForEach-Object {
    $_.Trim().ToLowerInvariant()
} | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })

foreach ($lang in $Languages) {
    if ($validLanguages -notcontains $lang) {
        throw "Unsupported language '$lang'. Supported values: $($validLanguages -join ', ')."
    }
}

if ($Languages.Count -eq 0) {
    throw "At least one language is required. Supported values: $($validLanguages -join ', ')."
}

# Resolve absolute output dir
$OutputDir = [System.IO.Path]::GetFullPath($OutputDir)
$RepoRoot  = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))

if (-not (Test-Path $OutputDir)) {
    New-Item -ItemType Directory -Path $OutputDir | Out-Null
}

if ($Publish) {
    if ([string]::IsNullOrEmpty($env:GITEA_USERNAME)) {
        throw 'Publish requires GITEA_USERNAME env var.'
    }
    if ([string]::IsNullOrEmpty($env:GITEA_TOKEN)) {
        throw 'Publish requires GITEA_TOKEN env var.'
    }
}

$GiteaNugetFeed = 'https://gitea.dohertylan.com/api/packages/dohertj2/nuget/index.json'
$GiteaPypiFeed  = 'https://gitea.dohertylan.com/api/packages/dohertj2/pypi'
$JavaHome       = '/Users/dohertj2/.local/jdks/jdk-21.0.11+10/Contents/Home'

# Generic Gitea package registry API (https://gitea.dohertylan.com/api/v1/packages/{owner}/{type}/{name}/{version}):
# returns 200 when that exact name+version already exists in the given feed
# type, 404 when it does not. Used as a pre-publish collision guard (CLI-39)
# so a re-run of this script can never silently overwrite a published artifact.
$GiteaPackageApiBase = 'https://gitea.dohertylan.com/api/v1/packages/dohertj2'

function Write-Header {
    param([string]$Text)
    Write-Host ''
    Write-Host '=== ' -NoNewline -ForegroundColor Cyan
    Write-Host $Text -ForegroundColor Cyan
}

function Test-GiteaPackageExists {
    <#
    .SYNOPSIS
        Queries the Gitea package API for an existing name+version in a feed.
    .OUTPUTS
        $true if the package/version already exists, $false if it does not.
        Throws if the registry cannot be reached or returns anything other
        than 200/404 — callers must treat "cannot verify" as "do not publish".
    #>
    param(
        [Parameter(Mandatory)][string]$Type,
        [Parameter(Mandatory)][string]$Name,
        [Parameter(Mandatory)][string]$Version
    )

    $uri = "$GiteaPackageApiBase/$Type/$Name/$Version"
    $headers = @{}
    if (-not [string]::IsNullOrEmpty($env:GITEA_TOKEN)) {
        $user = if ([string]::IsNullOrEmpty($env:GITEA_USERNAME)) { 'dohertj2' } else { $env:GITEA_USERNAME }
        $pair = "$($user):$($env:GITEA_TOKEN)"
        $basic = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
        $headers['Authorization'] = "Basic $basic"
    }

    try {
        $response = Invoke-WebRequest -Uri $uri -Headers $headers -Method Get -UseBasicParsing -ErrorAction Stop
        return ($response.StatusCode -eq 200)
    } catch {
        $statusCode = $null
        if ($_.Exception.PSObject.Properties['Response'] -and $_.Exception.Response) {
            $statusCode = [int]$_.Exception.Response.StatusCode
        }
        if ($statusCode -eq 404) {
            return $false
        }
        throw "Unable to query the Gitea package API for '$Type/$Name/$Version' ($uri): $($_.Exception.Message). Refusing to publish without a collision check — verify manually (or check GITEA_USERNAME/GITEA_TOKEN/network) and retry."
    }
}

function Assert-GiteaPackageNotPublished {
    <#
    .SYNOPSIS
        Aborts the script if $Name/$Version already exists in the $Type feed.
        Never force-overwrites a published artifact (CLI-39).
    #>
    param(
        [Parameter(Mandatory)][string]$Type,
        [Parameter(Mandatory)][string]$Name,
        [Parameter(Mandatory)][string]$Version
    )

    Write-Host "Checking Gitea '$Type' feed for existing '$Name' $Version..."
    if (Test-GiteaPackageExists -Type $Type -Name $Name -Version $Version) {
        throw "Gitea package '$Name' version '$Version' already exists in the '$Type' feed. Bump the client version before publishing — this script never force-overwrites a published artifact."
    }
    Write-Host "  Not found in the '$Type' feed — safe to publish '$Name' $Version." -ForegroundColor Green
}

# -------- .NET --------

function Invoke-PackDotnet {
    Write-Header '.NET'

    if (-not $SkipTests) {
        Write-Host 'Running .NET client tests...'
        $testProject = Join-Path $RepoRoot 'clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/ZB.MOM.WW.MxGateway.Client.Tests.csproj'
        & dotnet test $testProject --no-restore
        if ($LASTEXITCODE -ne 0) { throw '.NET tests failed.' }
    }

    Write-Host 'Packing ZB.MOM.WW.MxGateway.Contracts...'
    & dotnet pack (Join-Path $RepoRoot 'src/ZB.MOM.WW.MxGateway.Contracts/ZB.MOM.WW.MxGateway.Contracts.csproj') `
        -c Release -o $OutputDir
    if ($LASTEXITCODE -ne 0) { throw '.NET Contracts pack failed.' }

    Write-Host 'Packing ZB.MOM.WW.MxGateway.Client...'
    & dotnet pack (Join-Path $RepoRoot 'clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj') `
        -c Release -o $OutputDir
    if ($LASTEXITCODE -ne 0) { throw '.NET Client pack failed.' }

    Write-Host "Packed .NET artifacts -> $OutputDir" -ForegroundColor Green

    if ($Publish) {
        Write-Host 'Publishing .NET packages to Gitea...' -ForegroundColor Yellow
        Get-ChildItem $OutputDir -Filter 'ZB.MOM.WW.MxGateway.*.nupkg' | ForEach-Object {
            # nupkg filenames are '<PackageId>.<Version>.nupkg'; the id itself contains
            # dots (e.g. 'ZB.MOM.WW.MxGateway.Client'), so the id capture is lazy and the
            # version capture anchors on the leading digit to split at the right dot.
            $fileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($_.Name)
            if ($fileBaseName -notmatch '^(?<id>.+?)\.(?<version>\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$') {
                throw "Could not parse a NuGet package id/version out of '$($_.Name)'."
            }
            Assert-GiteaPackageNotPublished -Type 'nuget' -Name $Matches.id -Version $Matches.version

            & dotnet nuget push $_.FullName --source $GiteaNugetFeed --api-key $env:GITEA_TOKEN
            if ($LASTEXITCODE -ne 0) { throw "dotnet nuget push failed for '$($_.Name)'." }
        }
    }
}

# -------- Python --------

function Invoke-PackPython {
    Write-Header 'Python'

    # Use a persistent venv in /tmp so repeated runs skip reinstall.
    $Venv = '/tmp/mxgw-py'
    if (-not (Test-Path "$Venv/bin/python")) {
        Write-Host "Creating Python venv at $Venv..."
        & python3 -m venv $Venv
        if ($LASTEXITCODE -ne 0) { throw 'python3 -m venv failed.' }
        & "$Venv/bin/pip" install --quiet --upgrade pip
        & "$Venv/bin/pip" install --quiet build twine
        & "$Venv/bin/pip" install --quiet -e (Join-Path $RepoRoot 'clients/python[dev]')
    }

    if (-not $SkipTests) {
        Write-Host 'Running Python tests...'
        Push-Location (Join-Path $RepoRoot 'clients/python')
        try {
            & "$Venv/bin/python" -m pytest -q
            if ($LASTEXITCODE -ne 0) { throw 'Python tests failed.' }
        } finally { Pop-Location }
    }

    Write-Host 'Building Python sdist + wheel...'
    & "$Venv/bin/python" -m build (Join-Path $RepoRoot 'clients/python') --outdir $OutputDir
    if ($LASTEXITCODE -ne 0) { throw 'Python build failed.' }

    Write-Host "Packed Python artifacts -> $OutputDir" -ForegroundColor Green

    if ($Publish) {
        $pyprojectPath = Join-Path $RepoRoot 'clients/python/pyproject.toml'
        $pyprojectContent = Get-Content $pyprojectPath -Raw
        # Scope to the [project] section (not just the first "version = ..." line
        # in the file) — [build-system]/[tool.*] sections can carry their own
        # version-shaped keys, and matching the file's first hit would be luck
        # of ordering, not correctness.
        if ($pyprojectContent -notmatch '(?ms)^\[project\](?<section>.*?)(?=^\[|\z)') {
            throw "Could not find a [project] section in '$pyprojectPath'."
        }
        if ($Matches.section -notmatch '(?m)^\s*version\s*=\s*"([^"]+)"') {
            throw "Could not find [project].version in '$pyprojectPath'."
        }
        Assert-GiteaPackageNotPublished -Type 'pypi' -Name 'zb-mom-ww-mxaccess-gateway-client' -Version $Matches[1]

        Write-Host 'Publishing Python distribution to Gitea...' -ForegroundColor Yellow
        $wheels = @(Get-ChildItem $OutputDir -Filter 'zb_mom_ww_mxaccess_gateway_client-*.whl')
        $sdists = @(Get-ChildItem $OutputDir -Filter 'zb_mom_ww_mxaccess_gateway_client-*.tar.gz')
        $files  = ($wheels + $sdists) | ForEach-Object { $_.FullName }
        & "$Venv/bin/python" -m twine upload `
            --repository-url $GiteaPypiFeed `
            -u $env:GITEA_USERNAME `
            -p $env:GITEA_TOKEN `
            @files
        if ($LASTEXITCODE -ne 0) { throw 'twine upload failed.' }
    }
}

# -------- Rust --------

function Invoke-PackRust {
    Write-Header 'Rust'

    $rustDir = Join-Path $RepoRoot 'clients/rust'
    Push-Location $rustDir
    try {
        if (-not $SkipTests) {
            Write-Host 'Running Rust tests...'
            & cargo test --workspace
            if ($LASTEXITCODE -ne 0) { throw 'Rust tests failed.' }
        }

        Write-Host 'Running cargo package...'
        # No --no-verify: the crate vendors its protos (clients/rust/protos) so
        # the verify build proves it compiles standalone from the tarball (CLI-02).
        & cargo package
        if ($LASTEXITCODE -ne 0) { throw 'cargo package failed.' }

        $packageDir = Join-Path $rustDir 'target/package'
        $crates = @(Get-ChildItem $packageDir -Filter '*.crate')
        if ($crates.Count -eq 0) {
            throw 'cargo package produced no .crate files.'
        }
        foreach ($crate in $crates) {
            Copy-Item $crate.FullName -Destination $OutputDir -Force
            Write-Host "  Copied $($crate.Name)"
        }
    } finally { Pop-Location }

    Write-Host "Packed Rust artifacts -> $OutputDir" -ForegroundColor Green

    if ($Publish) {
        $cargoTomlPath = Join-Path $rustDir 'Cargo.toml'
        $cargoTomlContent = Get-Content $cargoTomlPath -Raw
        # Scope to the [package] section specifically — Cargo.toml also carries a
        # [workspace.package] section with its own "version = ..." line (today
        # identical, by convention, not by anything this regex can rely on), and
        # matching whichever comes first in the file is luck of ordering.
        if ($cargoTomlContent -notmatch '(?ms)^\[package\](?<section>.*?)(?=^\[|\z)') {
            throw "Could not find a [package] section in '$cargoTomlPath'."
        }
        if ($Matches.section -notmatch '(?m)^\s*version\s*=\s*"([^"]+)"') {
            throw "Could not find [package] version in '$cargoTomlPath'."
        }
        Assert-GiteaPackageNotPublished -Type 'cargo' -Name 'zb-mom-ww-mxgateway-client' -Version $Matches[1]

        Write-Host 'Publishing Rust crate to Gitea...' -ForegroundColor Yellow
        Push-Location (Join-Path $RepoRoot 'clients/rust')
        try {
            & cargo publish --registry dohertj2-gitea
            if ($LASTEXITCODE -ne 0) { throw 'cargo publish failed.' }
        } finally { Pop-Location }
    }
}

# -------- Java --------

function Invoke-PackJava {
    Write-Header 'Java'

    $env:JAVA_HOME = $JavaHome
    $javaDir = Join-Path $RepoRoot 'clients/java'
    Push-Location $javaDir
    try {
        if (-not $SkipTests) {
            Write-Host 'Running Java tests...'
            & gradle ':zb-mom-ww-mxgateway-client:test' --no-daemon
            if ($LASTEXITCODE -ne 0) { throw 'Java tests failed.' }
        }

        Write-Host 'Assembling Java jars + pom...'
        & gradle `
            ':zb-mom-ww-mxgateway-client:assemble' `
            ':zb-mom-ww-mxgateway-client:sourcesJar' `
            ':zb-mom-ww-mxgateway-client:javadocJar' `
            ':zb-mom-ww-mxgateway-client:generatePomFileForMavenPublication' `
            --no-daemon
        if ($LASTEXITCODE -ne 0) { throw 'Java assemble failed.' }

        $libsDir = Join-Path $javaDir 'zb-mom-ww-mxgateway-client/build/libs'
        $jars    = @(Get-ChildItem $libsDir -Filter 'zb-mom-ww-mxgateway-client-*.jar')
        if ($jars.Count -eq 0) {
            throw "No jars found under '$libsDir'."
        }
        foreach ($jar in $jars) {
            Copy-Item $jar.FullName -Destination $OutputDir -Force
            Write-Host "  Copied $($jar.Name)"
        }

        $pomSrc = Join-Path $javaDir 'zb-mom-ww-mxgateway-client/build/publications/maven/pom-default.xml'
        if (Test-Path $pomSrc) {
            # Derive the version from the jar filename (e.g. zb-mom-ww-mxgateway-client-0.1.0.jar).
            $versionJar = $jars | Where-Object { $_.Name -notmatch '-(sources|javadoc)\.jar$' } | Select-Object -First 1
            $version    = if ($versionJar) {
                [System.IO.Path]::GetFileNameWithoutExtension($versionJar.Name) -replace '^zb-mom-ww-mxgateway-client-', ''
            } else {
                '0.1.0'
            }
            $pomDest = Join-Path $OutputDir "zb-mom-ww-mxgateway-client-$version.pom"
            Copy-Item $pomSrc -Destination $pomDest -Force
            Write-Host "  Copied pom -> $([System.IO.Path]::GetFileName($pomDest))"
        } else {
            Write-Warning "POM not found at '$pomSrc'; skipping."
        }
    } finally { Pop-Location }

    Write-Host "Packed Java artifacts -> $OutputDir" -ForegroundColor Green

    if ($Publish) {
        $buildGradlePath = Join-Path $javaDir 'build.gradle'
        $buildGradleContent = Get-Content $buildGradlePath -Raw
        if ($buildGradleContent -notmatch "(?m)^\s*group\s*=\s*'([^']+)'") {
            throw "Could not find subprojects { group = '...' } in '$buildGradlePath'."
        }
        $javaGroup = $Matches[1]
        if ($buildGradleContent -notmatch "(?m)^\s*version\s*=\s*'([^']+)'") {
            throw "Could not find subprojects { version = '...' } in '$buildGradlePath'."
        }
        $javaVersion = $Matches[1]
        # Gitea's Maven package API identifies the package as "groupId:artifactId",
        # not the bare artifact id — passing just the artifact id here would query
        # a name that never exists and silently defeat the guard.
        Assert-GiteaPackageNotPublished -Type 'maven' -Name "$javaGroup`:zb-mom-ww-mxgateway-client" -Version $javaVersion

        Write-Host 'Publishing Java artifacts to Gitea Maven feed...' -ForegroundColor Yellow
        Push-Location $javaDir
        try {
            & gradle ':zb-mom-ww-mxgateway-client:publish' --no-daemon
            if ($LASTEXITCODE -ne 0) { throw 'gradle publish failed.' }
        } finally { Pop-Location }
    }
}

# -------- Go --------

function Invoke-PackGo {
    Write-Header 'Go'
    Write-Host 'Go modules are released by git-tagging — no artifact to pack.' -ForegroundColor Yellow
    Write-Host 'To publish a Go release, run:' -ForegroundColor Yellow
    Write-Host '  pwsh scripts/tag-go-module.ps1 -Version v0.1.0 -Push' -ForegroundColor Yellow
    Write-Host '(skipping)' -ForegroundColor DarkGray
}

# -------- Dispatch --------

$wanted = @{}
foreach ($lang in $Languages) { $wanted[$lang.ToLower()] = $true }

if ($wanted.ContainsKey('dotnet')) { Invoke-PackDotnet }
if ($wanted.ContainsKey('python')) { Invoke-PackPython }
if ($wanted.ContainsKey('rust'))   { Invoke-PackRust }
if ($wanted.ContainsKey('java'))   { Invoke-PackJava }
if ($wanted.ContainsKey('go'))     { Invoke-PackGo }

# -------- Summary --------

Write-Header 'Summary'
$artifacts = @(Get-ChildItem $OutputDir)
if ($artifacts.Count -eq 0) {
    Write-Host '  (no artifacts)' -ForegroundColor DarkGray
} else {
    foreach ($a in $artifacts) {
        Write-Host ('  {0,10}  {1}' -f $a.Length, $a.Name)
    }
}
Write-Host ''
Write-Host "All artifacts in: $OutputDir" -ForegroundColor Green
