<#
.SYNOPSIS
    Local build + Gitea Releases auto-publish.

.DESCRIPTION
    One-shot release: version bump -> tests -> build -> tag push -> Release -> upload.
    No Runner needed. Run on your local PC.

.PARAMETER Version
    Tag in 'v2.2.0' format.

.PARAMETER SkipTests
    Skip pytest + integration (emergency hotfix only).

.PARAMETER DryRun
    Preview without pushing or creating release.

.EXAMPLE
    # First time only: register PAT as env var
    [Environment]::SetEnvironmentVariable('GITEA_TOKEN', 'your_pat', 'User')

    .\release.ps1 v2.2.0

.NOTES
    - Kill any running main.exe before running.
    - PAT scope: repository (read+write).
#>
param(
    [Parameter(Mandatory=$true, Position=0)]
    [ValidatePattern('^v\d+\.\d+\.\d+$')]
    [string]$Version,

    [switch]$SkipTests,
    [switch]$DryRun
)

# PowerShell 5.1: native command stderr triggers NativeCommandError under 'Stop'.
# Use 'Continue' and explicitly check $LASTEXITCODE after each native call.
$ErrorActionPreference = 'Continue'

function Invoke-Native {
    param([Parameter(ValueFromRemainingArguments=$true)][string[]]$Args)
    & $Args[0] $Args[1..($Args.Count-1)] 2>&1 | Out-Host
    return $LASTEXITCODE
}

# ====== Config ======
$GiteaHost  = 'https://kindnick-git.duckdns.org'
$Owner      = 'kindnick'
$Repo       = 'Clock_out_Time_Calculator'
$ApiBase    = "$GiteaHost/api/v1/repos/$Owner/$Repo"
$VersionRaw = $Version.TrimStart('v')

function Step($msg)  { Write-Host "`n=== $msg ===" -ForegroundColor Cyan }
function Info($msg)  { Write-Host "  $msg" -ForegroundColor Gray }
function OkMsg($msg) { Write-Host "  [OK] $msg" -ForegroundColor Green }
function Fail($msg)  { Write-Host "  [FAIL] $msg" -ForegroundColor Red; exit 1 }

# ====== 0. Pre-checks ======
Step "Pre-checks"

if (-not $env:GITEA_TOKEN) {
    Fail "GITEA_TOKEN env var missing. Set it first: `$env:GITEA_TOKEN = 'your_pat'"
}

$running = Get-Process -Name 'main' -ErrorAction SilentlyContinue
if ($running) {
    Fail "main.exe is running (PID: $($running.Id -join ', ')). Kill it first."
}

$gitStatus = git status --porcelain
if ($gitStatus -and -not $DryRun) {
    Write-Host "  WARN: uncommitted changes:" -ForegroundColor Yellow
    $gitStatus | ForEach-Object { Write-Host "    $_" }
    $confirm = Read-Host "  Continue anyway? (y/N)"
    if ($confirm -ne 'y') { Fail "User cancelled" }
}

$existingTag = git tag -l $Version
if ($existingTag) {
    Fail "Tag $Version already exists. Bump version.py/CHANGELOG and use a new tag."
}

OkMsg "All checks passed (Version: $Version)"

# ====== 1. Bump version.py ======
Step "1/7  Bump core/version.py"
$verFile = 'core/version.py'
$verContent = [System.IO.File]::ReadAllText((Resolve-Path $verFile).Path, [System.Text.Encoding]::UTF8)
$newContent = $verContent -replace "__version__ = '[^']+'", "__version__ = '$VersionRaw'"
if ($verContent -eq $newContent) {
    Info "Already at $VersionRaw (no change)"
} else {
    if (-not $DryRun) { [System.IO.File]::WriteAllText((Resolve-Path $verFile).Path, $newContent, [System.Text.Encoding]::UTF8) }
    OkMsg "$verFile -> $VersionRaw"
}

# ====== 2. Tests ======
if (-not $SkipTests) {
    Step "2/7  Run tests"
    Info "pytest unit tests..."
    $rc = Invoke-Native python -m pytest tests -q
    if ($rc -ne 0) { Fail "pytest failed (exit $rc)" }

    Info "integration scenarios..."
    $rc = Invoke-Native python _integration_test.py
    if ($rc -ne 0) { Fail "integration tests failed (exit $rc)" }

    OkMsg "All tests passed"
} else {
    Step "2/7  Skipping tests (--SkipTests)"
}

# ====== 3. Build ======
Step "3/7  PyInstaller build"

# Build updater FIRST so main.spec can embed it as data
Info "Building updater.exe..."
$rc = Invoke-Native python -m PyInstaller --clean updater.spec
if ($rc -ne 0) { Fail "updater.exe build failed (exit $rc)" }
if (-not (Test-Path 'dist/updater.exe')) { Fail "dist/updater.exe missing" }

# Stage updater.exe outside dist/ so main.spec --clean does not wipe it
$stagingDir = 'build/staging'
if (-not (Test-Path $stagingDir)) { New-Item -ItemType Directory -Path $stagingDir | Out-Null }
Copy-Item 'dist/updater.exe' "$stagingDir/updater.exe" -Force
Info "Staged updater.exe -> $stagingDir/updater.exe"

Info "Building main.exe (with embedded updater)..."
$rc = Invoke-Native python -m PyInstaller --clean main.spec
if ($rc -ne 0) { Fail "main.exe build failed (exit $rc)" }
if (-not (Test-Path 'dist/main.exe')) { Fail "dist/main.exe missing" }

# Restore updater.exe to dist/ for separate Release asset upload
if (-not (Test-Path 'dist/updater.exe')) {
    Copy-Item "$stagingDir/updater.exe" 'dist/updater.exe' -Force
    Info "Restored updater.exe to dist/"
}

$mainSize    = "{0:N1}MB" -f ((Get-Item dist/main.exe).Length / 1MB)
$updaterSize = "{0:N1}MB" -f ((Get-Item dist/updater.exe).Length / 1MB)
OkMsg "main.exe ($mainSize) + updater.exe ($updaterSize)"

# Optional: Authenticode signing if cert available (env vars)
# Set CODE_SIGN_CERT (path to .pfx) and CODE_SIGN_PASS to enable
if ($env:CODE_SIGN_CERT -and (Test-Path $env:CODE_SIGN_CERT)) {
    Info "Code signing exes (Authenticode)..."
    $signtool = Get-Command signtool.exe -ErrorAction SilentlyContinue
    if (-not $signtool) {
        Info "  signtool.exe not found in PATH — skipping signature"
    } else {
        $tsUrl = if ($env:CODE_SIGN_TIMESTAMP) { $env:CODE_SIGN_TIMESTAMP } else { 'http://timestamp.digicert.com' }
        foreach ($exe in 'dist/main.exe', 'dist/updater.exe') {
            $args = @('sign', '/f', $env:CODE_SIGN_CERT)
            if ($env:CODE_SIGN_PASS) { $args += '/p'; $args += $env:CODE_SIGN_PASS }
            $args += '/tr'; $args += $tsUrl; $args += '/td'; $args += 'sha256'
            $args += '/fd'; $args += 'sha256'; $args += $exe
            $rc = Invoke-Native signtool $args
            if ($rc -ne 0) { Info "  WARN: sign failed for $exe (exit $rc)" }
            else { OkMsg "  signed $exe" }
        }
    }
}

# ====== 4. ZIP ======
Step "4/7  ZIP packaging"
$zipPath = "dist/ClockOutCalculator-$Version.zip"
if (Test-Path $zipPath) { Remove-Item $zipPath }
Compress-Archive -Path 'dist/main.exe', 'dist/updater.exe' -DestinationPath $zipPath
$zipSize = "{0:N1}MB" -f ((Get-Item $zipPath).Length / 1MB)
OkMsg "$zipPath ($zipSize)"

# ====== 5. Git commit + tag + push ======
Step "5/7  Git commit + tag + push"

if ($DryRun) {
    Info "DryRun mode - skipping git ops"
} else {
    git diff --quiet $verFile
    $needsCommit = $LASTEXITCODE -ne 0
    if ($needsCommit) {
        $rc = Invoke-Native git add $verFile CHANGELOG.md
        $rc = Invoke-Native git commit -m "Release $Version"
        if ($rc -ne 0) { Fail "git commit failed (exit $rc)" }
        OkMsg "Committed version bump"
    }

    $rc = Invoke-Native git tag $Version
    if ($rc -ne 0) { Fail "git tag failed (exit $rc)" }

    Info "Pushing main + tag..."
    $rc = Invoke-Native git push origin main
    if ($rc -ne 0) { Fail "git push main failed (exit $rc)" }
    $rc = Invoke-Native git push origin $Version
    if ($rc -ne 0) { Fail "git push tag failed (exit $rc)" }
    OkMsg "Pushed main + $Version"
}

# ====== 6. Create Gitea Release ======
Step "6/7  Create Gitea Release"

if ($DryRun) {
    Info "DryRun mode - skipping API call"
    Info "Would POST $ApiBase/releases (tag=$Version)"
    exit 0
}

$headers = @{ Authorization = "token $env:GITEA_TOKEN" }

# Extract notes from CHANGELOG.md
# IMPORTANT: PowerShell 5.1 Get-Content defaults to ANSI; use .NET API for UTF-8 (Korean).
$notes = "Release $Version"
if (Test-Path CHANGELOG.md) {
    $changelogPath = (Resolve-Path CHANGELOG.md).Path
    $changelog = [System.IO.File]::ReadAllText($changelogPath, [System.Text.Encoding]::UTF8)
    $pattern = "## \[$([regex]::Escape($VersionRaw))\][\s\S]*?(?=`n## \[|\z)"
    $regexMatch = [regex]::Match($changelog, $pattern)
    if ($regexMatch.Success) { $notes = $regexMatch.Value.Trim() }
}

$bodyObj = @{
    tag_name         = $Version
    target_commitish = 'main'
    name             = $Version
    body             = $notes
    draft            = $false
    prerelease       = $false
}
$bodyJson  = $bodyObj | ConvertTo-Json -Compress
$bodyBytes = [System.Text.Encoding]::UTF8.GetBytes($bodyJson)

try {
    $release = Invoke-RestMethod -Uri "$ApiBase/releases" -Method Post -Headers $headers -ContentType 'application/json' -Body $bodyBytes
    OkMsg "Release created (id=$($release.id))"
} catch {
    Fail "Release creation failed: $($_.Exception.Message)"
}

# ====== 7. Upload assets ======
Step "7/7  Upload assets"

$assets = @('dist/main.exe', 'dist/updater.exe', $zipPath)
foreach ($f in $assets) {
    if (-not (Test-Path $f)) { Info "Skip (missing): $f"; continue }
    $name = Split-Path $f -Leaf
    Info "Uploading $name..."
    try {
        $uploadUrl = "$ApiBase/releases/$($release.id)/assets?name=$name"
        $null = Invoke-RestMethod -Uri $uploadUrl -Method Post -Headers $headers -ContentType 'application/octet-stream' -InFile $f
        OkMsg $name
    } catch {
        Fail "Upload failed ($name): $($_.Exception.Message)"
    }
}

Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host " Release $Version published!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host " URL: $GiteaHost/$Owner/$Repo/releases/tag/$Version" -ForegroundColor White
Write-Host ""
Write-Host " Users will see the update on next app launch." -ForegroundColor Gray
