param()

$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
$buildRoot = Join-Path $projectRoot '.build/metered-core'
$sourceArchive = Join-Path $projectRoot 'third-party/sing-box-1.14.0-source.zip'
$sourceHash = '76fdd3a079153e0f5e10ce94e618a72f25a3d6927676bf41de3509ca2ff6d271'
$goVersion = 'go1.26.7'
$goFilename = "$goVersion.windows-amd64.zip"
$goHash = 'f4f534a486e4bc3387fa18f08208f2f854b7aaea8a08f2a2d829a914a05abb11'
$metadataUrl = 'https://go.dev/dl/?mode=json&include=all'
$downloadUrl = "https://go.dev/dl/$goFilename"
$tags = 'with_gvisor,with_clash_api,with_v2ray_api'
$linkFlags = '-X runtime.godebugDefault=multipathtcp=0,tlssha1=1 -checklinkname=0 -X github.com/sagernet/sing-box/constant.Version=1.14.0 -s -w -buildid='

if (!(Test-Path -LiteralPath $sourceArchive -PathType Leaf) -or
    (Get-FileHash -LiteralPath $sourceArchive -Algorithm SHA256).Hash -ne $sourceHash) {
    throw 'Pinned sing-box source archive is missing or has an unexpected SHA-256.'
}
# Authenticate the pinned toolchain hash against the official HTTPS metadata before executing it.
$metadata = Invoke-RestMethod -Uri $metadataUrl
$release = @($metadata | Where-Object { $_.version -eq $goVersion })
$archiveMetadata = @($release.files | Where-Object { $_.filename -eq $goFilename })
if ($archiveMetadata.Count -ne 1 -or $archiveMetadata[0].sha256 -ne $goHash) {
    throw 'Official Go metadata did not match the pinned Windows toolchain.'
}
[void][IO.Directory]::CreateDirectory($buildRoot)
$toolArchive = Join-Path $buildRoot $goFilename
if (!(Test-Path -LiteralPath $toolArchive -PathType Leaf)) {
    Invoke-WebRequest -Uri $downloadUrl -OutFile $toolArchive
}
if ((Get-FileHash -LiteralPath $toolArchive -Algorithm SHA256).Hash -ne $goHash) {
    throw 'Downloaded Go toolchain SHA-256 mismatch; it was not executed.'
}

# Fresh extraction on every build prevents stale or locally edited sources/tools from entering it.
$runRoot = Join-Path $buildRoot ('build-' + [Guid]::NewGuid().ToString('N'))
[void][IO.Directory]::CreateDirectory($runRoot)
[IO.Compression.ZipFile]::ExtractToDirectory($toolArchive, (Join-Path $runRoot 'toolchain'))
[IO.Compression.ZipFile]::ExtractToDirectory($sourceArchive, (Join-Path $runRoot 'source'))
$go = Join-Path $runRoot 'toolchain/go/bin/go.exe'
$sourceRoot = Join-Path $runRoot 'source/sing-box-1.14.0'
$outputFile = Join-Path $runRoot 'sing-box.exe'
$settings = @{
    CGO_ENABLED = '0'
    GOOS = 'windows'
    GOARCH = 'amd64'
    GOAMD64 = 'v1'
    GOTOOLCHAIN = 'local'
    GOENV = 'off'
    GOWORK = 'off'
    GOFLAGS = ''
    GOEXPERIMENT = ''
    GOCACHEPROG = ''
    GOPRIVATE = ''
    GONOSUMDB = ''
    GONOPROXY = ''
    GOPROXY = 'https://proxy.golang.org'
    GOSUMDB = 'sum.golang.org'
    GOPATH = (Join-Path $buildRoot 'gopath')
    GOMODCACHE = (Join-Path $buildRoot 'modules')
    GOCACHE = (Join-Path $buildRoot 'cache')
    GOROOT = (Join-Path $runRoot 'toolchain/go')
}
$previous = @{}
foreach ($name in $settings.Keys) {
    $previous[$name] = [Environment]::GetEnvironmentVariable($name, 'Process')
    [Environment]::SetEnvironmentVariable($name, $settings[$name], 'Process')
}
Push-Location -LiteralPath $sourceRoot
try {
    $toolVersion = & $go version
    if ($LASTEXITCODE -ne 0 -or $toolVersion -ne 'go version go1.26.7 windows/amd64') {
        throw "Unexpected verified Go executable version: $toolVersion"
    }
    Write-Host $toolVersion
    & $go build -mod=readonly -trimpath -buildvcs=false -tags $tags -ldflags $linkFlags -o $outputFile ./cmd/sing-box
    if ($LASTEXITCODE -ne 0) { throw 'Metered sing-box build failed; the existing runtime was not changed.' }
    $versionOutput = @(& $outputFile version)
    if ($LASTEXITCODE -ne 0 -or ($versionOutput -join "`n") -notmatch 'with_v2ray_api') {
        throw 'The compiled core did not report the required with_v2ray_api build tag.'
    }
    $moduleOutput = @(& $go version -m $outputFile)
    if ($LASTEXITCODE -ne 0) { throw 'Could not read compiled Go module provenance.' }
    $moduleOutput[0] = "sing-box.exe: $goVersion"
    $recordedEnvironment = @{}
    foreach ($name in $settings.Keys) {
        if ($name -notin @('GOPATH', 'GOMODCACHE', 'GOCACHE', 'GOROOT')) {
            $recordedEnvironment[$name] = $settings[$name]
        }
    }
    $binaryHash = (Get-FileHash -LiteralPath $outputFile -Algorithm SHA256).Hash.ToLowerInvariant()
    $manifest = [ordered]@{
        description = 'TeamTunnel self-compiled upstream sing-box; not an official prebuilt binary'
        source = 'https://codeload.github.com/SagerNet/sing-box/zip/refs/tags/v1.14.0'
        sourceSha256 = $sourceHash
        goVersion = $toolVersion
        goMetadata = $metadataUrl
        goDownload = $downloadUrl
        goArchiveSha256 = $goHash
        tags = $tags
        buildFlags = '-mod=readonly -trimpath -buildvcs=false'
        linkFlags = $linkFlags
        environment = $recordedEnvironment
        binarySha256 = $binaryHash
        versionOutput = $versionOutput
        modules = $moduleOutput
        builtUtc = [DateTime]::UtcNow.ToString('O')
    }
    $manifest | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $runRoot 'build-info.json') -Encoding utf8
    $targetDirectory = Join-Path $projectRoot 'runtime/metered'
    [void][IO.Directory]::CreateDirectory($targetDirectory)
    $targetFile = Join-Path $targetDirectory 'sing-box.exe'
    if (Test-Path -LiteralPath $targetFile) {
        if ((Get-FileHash -LiteralPath $targetFile -Algorithm SHA256).Hash -ne $binaryHash) {
            throw "Different metered core already exists; fresh output retained at $outputFile"
        }
    } else {
        Copy-Item -LiteralPath $outputFile -Destination $targetFile
    }
    Copy-Item -LiteralPath (Join-Path $sourceRoot 'LICENSE') -Destination (Join-Path $targetDirectory 'LICENSE')
    Copy-Item -LiteralPath (Join-Path $runRoot 'build-info.json') -Destination $targetDirectory
    Write-Host ($versionOutput -join "`n")
    Write-Host "Self-compiled core: $targetFile"
    Write-Host "SHA-256: $binaryHash"
    Write-Host 'No VPN, TUN, service or system-network configuration was started or modified.'
} finally {
    Pop-Location
    foreach ($name in $settings.Keys) {
        [Environment]::SetEnvironmentVariable($name, $previous[$name], 'Process')
    }
}
