param(
  [int]$GatewayPort = 18889,
  [string]$GatewayToken = 'qjc-bundled-runtime-token',
  [int]$SmokePort = 4318,
  [string]$SmokeToken = 'smoke-token',
  [string]$BaseOutputDir,
  [int]$TimeoutSeconds = 120,
  [switch]$SkipMaterializeRuntime
)

$ErrorActionPreference = 'Stop'

function Write-Utf8File {
  param([string]$filePath, [string]$content)
  $encoding = New-Object System.Text.UTF8Encoding $false
  [System.IO.File]::WriteAllText($filePath, $content, $encoding)
}

function Invoke-ElectronSmokeWithRetry {
  param(
    [string]$ScriptPath,
    [string]$Label,
    [string[]]$ArgumentList,
    [int]$MaxAttempts = 2
  )

  for ($attempt = 1; $attempt -le $MaxAttempts; $attempt += 1) {
    powershell -ExecutionPolicy Bypass -File $ScriptPath @ArgumentList
    if ($LASTEXITCODE -eq 0) {
      return
    }
    if ($attempt -ge $MaxAttempts) {
      exit $LASTEXITCODE
    }
    Write-Warning "$Label failed on attempt $attempt. Retrying..."
    Start-Sleep -Seconds 2
  }
}

$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
if (-not $BaseOutputDir) {
  $BaseOutputDir = Join-Path $repoRoot '.tmp\cloud-bundle-smoke'
}
$BaseOutputDir = [System.IO.Path]::GetFullPath($BaseOutputDir)
$userDataPath = Join-Path $BaseOutputDir 'user-data'
$phase1LogsPath = Join-Path $BaseOutputDir 'phase1-logs'
$phase2LogsPath = Join-Path $BaseOutputDir 'phase2-logs'
$phase3LogsPath = Join-Path $BaseOutputDir 'phase3-logs'
$phase1Output = Join-Path $BaseOutputDir 'phase1-result.json'
$phase2Output = Join-Path $BaseOutputDir 'phase2-result.json'
$phase3Output = Join-Path $BaseOutputDir 'phase3-result.json'
$bundleSourceRoot = Join-Path $BaseOutputDir 'bundle-src'
$bundleSourceRootA = Join-Path $bundleSourceRoot 'variant-a'
$bundleSourceRootB = Join-Path $bundleSourceRoot 'variant-b'
$bundleRootA = Join-Path $bundleSourceRootA 'cloud-bundle-smoke'
$bundleRootB = Join-Path $bundleSourceRootB 'cloud-bundle-smoke'
$bundleZipPathA = Join-Path $BaseOutputDir 'cloud-bundle-smoke-a.zip'
$bundleZipPathB = Join-Path $BaseOutputDir 'cloud-bundle-smoke-b.zip'
$bundleFileName = 'cloud-bundle-smoke.zip'
$bundleProjectId = 'cloud-bundle-smoke'
$bundleProjectName = 'Cloud Bundle Smoke'
$bundleSkillId = 'cloud-bundle-smoke-skill'
$bundleConfigVersionA = '2026-03-31T09:30:00.000Z'
$bundleConfigVersionB = '2026-03-31T10:30:00.000Z'
$bundleReadmeMarkerA = 'Cloud bundle replacement marker A.'
$bundleReadmeMarkerB = 'Cloud bundle replacement marker B.'
$bundleSharedSkillA = 'cloud-bundle-shared-skill-a'
$bundleSharedSkillB = 'cloud-bundle-shared-skill-b'
$bundleSharedCronA = 'cloud-bundle-task-a.txt'
$bundleSharedCronB = 'cloud-bundle-task-b.txt'
$expectedBundleSourceUrl = "http://127.0.0.1:$SmokePort/downloads/$bundleFileName"
$electronSmokeScript = Join-Path $repoRoot 'build\scripts\electron-smoke.ps1'

if (Test-Path $BaseOutputDir) {
  Remove-Item $BaseOutputDir -Recurse -Force -ErrorAction SilentlyContinue
}
New-Item -ItemType Directory -Force -Path $BaseOutputDir, $bundleRootA, $bundleRootB | Out-Null

$bundleProjectJson = [ordered]@{
  id = $bundleProjectId
  name = $bundleProjectName
  description = 'Remote bundle smoke fixture for project isolation.'
  version = '1.0.0'
}
Write-Utf8File (Join-Path $bundleRootA 'project.json') ($bundleProjectJson | ConvertTo-Json -Depth 5)
Write-Utf8File (Join-Path $bundleRootB 'project.json') ($bundleProjectJson | ConvertTo-Json -Depth 5)
$agentContentA = @(
  '# Cloud Bundle Smoke',
  '',
  'This project exists to validate cloud zip sync and project-isolated workspace execution.',
  'If asked, confirm the project root and that execution happened inside this project workspace.',
  'Bundle variant A is currently active.'
) -join [Environment]::NewLine
$agentContentB = @(
  '# Cloud Bundle Smoke',
  '',
  'This project exists to validate cloud zip sync and project-isolated workspace execution.',
  'If asked, confirm the project root and that execution happened inside this project workspace.',
  'Bundle variant B replaced the previous contents for the same project.'
) -join [Environment]::NewLine
Write-Utf8File (Join-Path $bundleRootA 'AGENT.md') $agentContentA
Write-Utf8File (Join-Path $bundleRootB 'AGENT.md') $agentContentB
$readmeContentA = @(
  '# Cloud Bundle Smoke',
  '',
  'This README should be injected into the workspace-entry prompt after the bundle is downloaded and materialized.',
  $bundleReadmeMarkerA
) -join [Environment]::NewLine
$readmeContentB = @(
  '# Cloud Bundle Smoke',
  '',
  'This README should be injected into the workspace-entry prompt after the bundle is downloaded and materialized.',
  $bundleReadmeMarkerB
) -join [Environment]::NewLine
Write-Utf8File (Join-Path $bundleRootA 'README.md') $readmeContentA
Write-Utf8File (Join-Path $bundleRootB 'README.md') $readmeContentB
New-Item -ItemType Directory -Force -Path (Join-Path $bundleRootA 'memory'), (Join-Path $bundleRootB 'memory') | Out-Null
Write-Utf8File (Join-Path $bundleRootA 'memory\summary.md') 'Cloud bundle smoke memory marker A.'
Write-Utf8File (Join-Path $bundleRootB 'memory\summary.md') 'Cloud bundle smoke memory marker B.'
New-Item -ItemType Directory -Force -Path (Join-Path (Join-Path $bundleRootA 'skills') $bundleSharedSkillA), (Join-Path (Join-Path $bundleRootB 'skills') $bundleSharedSkillB), (Join-Path $bundleRootA 'cron'), (Join-Path $bundleRootB 'cron') | Out-Null
Write-Utf8File (Join-Path $bundleRootA "skills\$bundleSharedSkillA\SKILL.md") '# Cloud Bundle Shared Skill A'
Write-Utf8File (Join-Path $bundleRootB "skills\$bundleSharedSkillB\SKILL.md") '# Cloud Bundle Shared Skill B'
Write-Utf8File (Join-Path $bundleRootA "cron\$bundleSharedCronA") 'cloud bundle replacement task A'
Write-Utf8File (Join-Path $bundleRootB "cron\$bundleSharedCronB") 'cloud bundle replacement task B'

foreach ($bundleZipPath in @($bundleZipPathA, $bundleZipPathB)) {
  if (Test-Path $bundleZipPath) {
    Remove-Item $bundleZipPath -Force
  }
}
Compress-Archive -Path (Join-Path $bundleSourceRootA '*') -DestinationPath $bundleZipPathA -Force
Compress-Archive -Path (Join-Path $bundleSourceRootB '*') -DestinationPath $bundleZipPathB -Force

if (-not $SkipMaterializeRuntime) {
  Write-Host "Materializing bundled runtime payload on port $GatewayPort"
  powershell -ExecutionPolicy Bypass -File (Join-Path $repoRoot 'build\scripts\materialize-runtime-payload.ps1') -GatewayPort $GatewayPort -GatewayToken $GatewayToken
  if ($LASTEXITCODE -ne 0) {
    exit $LASTEXITCODE
  }
}

$env:QJCLAW_SMOKE_BUNDLE_ZIP_PATH = $bundleZipPathA
$env:QJCLAW_SMOKE_BUNDLE_FILE_NAME = $bundleFileName
$env:QJCLAW_SMOKE_BUNDLE_SKILL_ID = $bundleSkillId
$env:QJCLAW_SMOKE_BUNDLE_SKILL_TITLE = 'Cloud Bundle Smoke Skill'
$env:QJCLAW_SMOKE_BUNDLE_SKILL_DESCRIPTION = 'Remote zip-backed project bundle for smoke validation.'
$env:QJCLAW_SMOKE_BUNDLE_CONFIG_VERSION = $bundleConfigVersionA

try {
  Write-Host 'Running cloud bundle smoke phase 1 (payload sync path)'
  Invoke-ElectronSmokeWithRetry -ScriptPath $electronSmokeScript -Label 'cloud-bundle phase 1' -ArgumentList @(
    '-SmokeOutput', $phase1Output,
    '-SmokePort', $SmokePort,
    '-SmokeToken', $SmokeToken,
    '-UserDataPath', $userDataPath,
    '-LogsPath', $phase1LogsPath,
    '-RuntimeMode', 'bundled-runtime',
    '-ExpectBundledRuntime',
    '-ExpectWorkspaceEntry',
    '-ExpectRemoteBundle',
    '-WorkspaceProjectId', $bundleProjectId,
    '-WorkspaceProjectName', $bundleProjectName,
    '-SmokePrompt', 'Describe the current project root and confirm cloud bundle workspace execution.',
    '-SmokeSkillId', '__cloud_bundle_workspace_entry_disabled__',
    '-ExpectedBundleSourceUrl', $expectedBundleSourceUrl,
    '-ExpectedBundleConfigVersion', $bundleConfigVersionA,
    '-ExpectedBundleFileName', $bundleFileName,
    '-ExpectedBundleSkillId', $bundleSkillId,
    '-ExpectedReadmeMarker', $bundleReadmeMarkerA,
    '-TimeoutSeconds', $TimeoutSeconds
  )

  $projectPath = Join-Path $userDataPath (Join-Path 'projects' $bundleProjectId)
  $bundleManifestPath = Join-Path $userDataPath 'manifests\project-bundles.json'
  $activeProjectManifestPath = Join-Path $userDataPath 'manifests\active-project.json'
  if (Test-Path $projectPath) {
    Remove-Item $projectPath -Recurse -Force -ErrorAction SilentlyContinue
  }
  if (Test-Path $bundleManifestPath) {
    Remove-Item $bundleManifestPath -Force -ErrorAction SilentlyContinue
  }
  if (Test-Path $activeProjectManifestPath) {
    Remove-Item $activeProjectManifestPath -Force -ErrorAction SilentlyContinue
  }

  Write-Host 'Running cloud bundle smoke phase 2 (cached init path)'
  Invoke-ElectronSmokeWithRetry -ScriptPath $electronSmokeScript -Label 'cloud-bundle phase 2' -ArgumentList @(
    '-SmokeOutput', $phase2Output,
    '-SmokePort', $SmokePort,
    '-SmokeToken', $SmokeToken,
    '-UserDataPath', $userDataPath,
    '-LogsPath', $phase2LogsPath,
    '-RuntimeMode', 'bundled-runtime',
    '-ExpectBundledRuntime',
    '-PreserveUserData',
    '-ExpectWorkspaceEntry',
    '-ExpectRemoteBundle',
    '-WorkspaceProjectId', $bundleProjectId,
    '-WorkspaceProjectName', $bundleProjectName,
    '-SmokePrompt', 'Describe the current project root and confirm cached cloud bundle workspace execution.',
    '-SmokeSkillId', '__cloud_bundle_workspace_entry_disabled__',
    '-ExpectedBundleSourceUrl', $expectedBundleSourceUrl,
    '-ExpectedBundleConfigVersion', $bundleConfigVersionA,
    '-ExpectedBundleFileName', $bundleFileName,
    '-ExpectedBundleSkillId', $bundleSkillId,
    '-ExpectedReadmeMarker', $bundleReadmeMarkerA,
    '-TimeoutSeconds', $TimeoutSeconds
  )

  $env:QJCLAW_SMOKE_BUNDLE_ZIP_PATH = $bundleZipPathB
  $env:QJCLAW_SMOKE_BUNDLE_CONFIG_VERSION = $bundleConfigVersionB

  Write-Host 'Running cloud bundle smoke phase 3 (same-project replacement path)'
  Invoke-ElectronSmokeWithRetry -ScriptPath $electronSmokeScript -Label 'cloud-bundle phase 3' -ArgumentList @(
    '-SmokeOutput', $phase3Output,
    '-SmokePort', $SmokePort,
    '-SmokeToken', $SmokeToken,
    '-UserDataPath', $userDataPath,
    '-LogsPath', $phase3LogsPath,
    '-RuntimeMode', 'bundled-runtime',
    '-ExpectBundledRuntime',
    '-PreserveUserData',
    '-ExpectWorkspaceEntry',
    '-ExpectRemoteBundle',
    '-WorkspaceProjectId', $bundleProjectId,
    '-WorkspaceProjectName', $bundleProjectName,
    '-SmokePrompt', 'Quote the current README marker line and confirm same-project replacement workspace execution.',
    '-SmokeSkillId', '__cloud_bundle_workspace_entry_disabled__',
    '-ExpectedBundleSourceUrl', $expectedBundleSourceUrl,
    '-ExpectedBundleConfigVersion', $bundleConfigVersionB,
    '-ExpectedBundleFileName', $bundleFileName,
    '-ExpectedBundleSkillId', $bundleSkillId,
    '-ExpectedReadmeMarker', $bundleReadmeMarkerB,
    '-UnexpectedReadmeMarker', $bundleReadmeMarkerA,
    '-TimeoutSeconds', $TimeoutSeconds
  )

  $summary = & node -e @"
const fs = require('fs');
const path = require('path');
const [
  phase1Path,
  phase2Path,
  phase3Path,
  expectedUserData,
  bundleZipPathA,
  bundleZipPathB,
  expectedBundleSourceUrl,
  expectedReadmeMarkerA,
  expectedReadmeMarkerB,
  sharedSkillA,
  sharedSkillB,
  sharedCronA,
  sharedCronB
] = process.argv.slice(1);
const phase1 = JSON.parse(fs.readFileSync(phase1Path, 'utf8'));
const phase2 = JSON.parse(fs.readFileSync(phase2Path, 'utf8'));
const phase3 = JSON.parse(fs.readFileSync(phase3Path, 'utf8'));
const readmePath = path.join(expectedUserData, 'projects', 'cloud-bundle-smoke', 'README.md');
const manifestPath = path.join(expectedUserData, 'manifests', 'project-bundles.json');
const readmeContent = fs.readFileSync(readmePath, 'utf8');
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
const manifestRecord = manifest['cloud-bundle-smoke'];
if (!manifestRecord || typeof manifestRecord !== 'object') {
  throw new Error('Cloud bundle smoke phase 3 did not persist a manifest record for cloud-bundle-smoke.');
}
if (!readmeContent.includes(expectedReadmeMarkerB)) {
  throw new Error('Cloud bundle smoke phase 3 did not materialize README marker B on disk.');
}
if (readmeContent.includes(expectedReadmeMarkerA)) {
  throw new Error('Cloud bundle smoke phase 3 still has stale README marker A on disk.');
}
if (!fs.existsSync(path.join(expectedUserData, 'skills', sharedSkillB))) {
  throw new Error('Cloud bundle smoke phase 3 did not materialize replacement shared skill entry.');
}
if (fs.existsSync(path.join(expectedUserData, 'skills', sharedSkillA))) {
  throw new Error('Cloud bundle smoke phase 3 left stale shared skill entry A on disk.');
}
if (!fs.existsSync(path.join(expectedUserData, 'cron', sharedCronB))) {
  throw new Error('Cloud bundle smoke phase 3 did not materialize replacement cron entry.');
}
if (fs.existsSync(path.join(expectedUserData, 'cron', sharedCronA))) {
  throw new Error('Cloud bundle smoke phase 3 left stale cron entry A on disk.');
}
console.log(JSON.stringify({
  ok: true,
  bundleZipPathA,
  bundleZipPathB,
  expectedBundleSourceUrl,
  phase1Output: phase1Path,
  phase2Output: phase2Path,
  phase3Output: phase3Path,
  phase1SessionId: phase1.sendResult.sessionId,
  phase2SessionId: phase2.sendResult.sessionId,
  phase3SessionId: phase3.sendResult.sessionId,
  phase1ProjectId: phase1.finalState.workspaceSummary.currentProjectId,
  phase2ProjectId: phase2.finalState.workspaceSummary.currentProjectId,
  phase3ProjectId: phase3.finalState.workspaceSummary.currentProjectId,
  phase1LatestStatusLabel: phase1.sendResult.streamSmoke.latestStatusLabel,
  phase2LatestStatusLabel: phase2.sendResult.streamSmoke.latestStatusLabel,
  phase3LatestStatusLabel: phase3.sendResult.streamSmoke.latestStatusLabel,
  phase1ExpectedReadmeMarker: expectedReadmeMarkerA,
  phase3ExpectedReadmeMarker: expectedReadmeMarkerB,
  manifestConfigVersionAfterReplacement: manifestRecord.configVersion || null,
  manifestRemoteEtagAfterReplacement: manifestRecord.remoteEtag || null,
  manifestSharedSkillEntriesAfterReplacement: manifestRecord.sharedSkillEntries || [],
  manifestSharedCronEntriesAfterReplacement: manifestRecord.sharedCronEntries || [],
  readmePath
}, null, 2));
"@ $phase1Output $phase2Output $phase3Output $userDataPath $bundleZipPathA $bundleZipPathB $expectedBundleSourceUrl $bundleReadmeMarkerA $bundleReadmeMarkerB $bundleSharedSkillA $bundleSharedSkillB $bundleSharedCronA $bundleSharedCronB
  if ($LASTEXITCODE -ne 0) {
    exit $LASTEXITCODE
  }
  Write-Output $summary
}
finally {
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_ZIP_PATH -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_FILE_NAME -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_SKILL_ID -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_SKILL_TITLE -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_SKILL_DESCRIPTION -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_BUNDLE_CONFIG_VERSION -ErrorAction SilentlyContinue
}
