param(
  [int]$GatewayPort = 18889,
  [string]$GatewayToken = 'qjc-bundled-runtime-token',
  [string]$SmokeOutput,
  [string]$UserDataPath,
  [string]$LogsPath,
  [int]$TimeoutSeconds = 180,
  [switch]$SkipMaterializeRuntime
)

$ErrorActionPreference = 'Stop'

$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
$electron = Join-Path $repoRoot 'node_modules\.pnpm\electron@34.5.8\node_modules\electron\dist\electron.exe'
$desktopApp = Join-Path $repoRoot 'apps\desktop'
$rendererUrl = Join-Path $repoRoot 'apps\desktop\dist\renderer\index.html'

if (-not (Test-Path $electron)) {
  throw "Electron executable not found at $electron"
}

if (-not $SmokeOutput) {
  $SmokeOutput = Join-Path $repoRoot '.tmp\desktop-single-instance-smoke\result.json'
}
if (-not $UserDataPath) {
  $UserDataPath = Join-Path $repoRoot '.tmp\desktop-single-instance-smoke\user-data'
}
if (-not $LogsPath) {
  $LogsPath = Join-Path $repoRoot '.tmp\desktop-single-instance-smoke\logs'
}

$SmokeOutput = [System.IO.Path]::GetFullPath($SmokeOutput)
$UserDataPath = [System.IO.Path]::GetFullPath($UserDataPath)
$LogsPath = [System.IO.Path]::GetFullPath($LogsPath)
$readyPath = $SmokeOutput + '.ready.json'
$eventPath = $SmokeOutput + '.second-instance.json'
$tracePath = $SmokeOutput + '.trace.log'

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

function Write-SmokeFailureOutput {
  param(
    [string]$Message,
    [string]$Stage
  )

  if (Test-Path $SmokeOutput) {
    return
  }

  $payload = [ordered]@{
    ok = $false
    stage = $Stage
    error = $Message
    finishedAt = (Get-Date).ToUniversalTime().ToString('o')
    smokeOutput = $SmokeOutput
    readyPath = if (Test-Path $readyPath) { $readyPath } else { $null }
    eventPath = if (Test-Path $eventPath) { $eventPath } else { $null }
    tracePath = if (Test-Path $tracePath) { $tracePath } else { $null }
    userDataPath = $UserDataPath
    logsPath = $LogsPath
  }
  Write-Utf8File $SmokeOutput ($payload | ConvertTo-Json -Depth 6)
}

foreach ($pathValue in @($SmokeOutput, $UserDataPath, $LogsPath)) {
  $parent = Split-Path $pathValue -Parent
  if ($parent) {
    New-Item -ItemType Directory -Force -Path $parent | Out-Null
  }
}

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
  }
}

foreach ($cleanupPath in @($SmokeOutput, $readyPath, $eventPath, $tracePath)) {
  if (Test-Path $cleanupPath) {
    Remove-Item $cleanupPath -Force -ErrorAction SilentlyContinue
  }
}
if (Test-Path $UserDataPath) {
  Remove-Item $UserDataPath -Recurse -Force -ErrorAction SilentlyContinue
}
if (Test-Path $LogsPath) {
  Remove-Item $LogsPath -Recurse -Force -ErrorAction SilentlyContinue
}
New-Item -ItemType Directory -Force -Path $UserDataPath, $LogsPath | Out-Null

Remove-Item Env:QJCLAW_SMOKE_CLOUD_API_BASE_URL -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_AUTH_TOKEN -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_RUNTIME_CLOUD_API_KEY -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_PROMPT -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_SKILL_ID -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_VIEW_MODE -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_PROJECT_ID -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_EXPERT_ENTRY_ID -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_SEND_AFTER_EXPERT_ENTRY -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_SUGGESTION_ACTION -ErrorAction SilentlyContinue
Remove-Item Env:QJCLAW_SMOKE_STARTUP_ONLY -ErrorAction SilentlyContinue

$env:QJCLAW_RENDERER_URL = $rendererUrl
$env:QJCLAW_SECRET_BACKEND = 'file-fallback'
$env:QJCLAW_USER_DATA_PATH = $UserDataPath
$env:QJCLAW_LOGS_PATH = $LogsPath
$env:QJCLAW_RUNTIME_MODE = 'bundled-runtime'
$env:QJCLAW_SMOKE_OUTPUT = $SmokeOutput
$env:QJCLAW_SMOKE_SINGLE_INSTANCE = '1'
$env:QJCLAW_SMOKE_SINGLE_INSTANCE_READY_PATH = $readyPath
$env:QJCLAW_SMOKE_SINGLE_INSTANCE_EVENT_PATH = $eventPath

$firstProcess = $null
$secondProcess = $null

try {
  Write-Host "Launching first Electron instance with isolated userData at $UserDataPath"
  $firstProcess = Start-Process -FilePath $electron -ArgumentList $desktopApp -PassThru

  $readyDeadline = (Get-Date).AddSeconds([Math]::Min($TimeoutSeconds, 90))
  while ((Get-Date) -lt $readyDeadline) {
    if ((Test-Path $readyPath) -or (Test-Path $SmokeOutput)) {
      break
    }
    Start-Sleep -Milliseconds 500
  }

  if (-not (Test-Path $readyPath)) {
    if (-not (Test-Path $SmokeOutput)) {
      Write-SmokeFailureOutput -Message "First Electron instance never reported single-instance readiness. Trace: $tracePath" -Stage 'desktop-single-instance-ready-timeout'
    }
  } else {
    Write-Host 'Launching second Electron instance to trigger single-instance wake-up'
    Remove-Item Env:QJCLAW_SMOKE_OUTPUT -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_SMOKE_SINGLE_INSTANCE -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_SMOKE_SINGLE_INSTANCE_READY_PATH -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_SMOKE_SINGLE_INSTANCE_EVENT_PATH -ErrorAction SilentlyContinue

    $secondProcess = Start-Process -FilePath $electron -ArgumentList $desktopApp -PassThru
    Wait-Process -Id $secondProcess.Id -Timeout 20 -ErrorAction SilentlyContinue
    if (Get-Process -Id $secondProcess.Id -ErrorAction SilentlyContinue) {
      Stop-Process -Id $secondProcess.Id -Force -ErrorAction SilentlyContinue
      throw "Second Electron instance did not exit promptly after failing to acquire the single-instance lock."
    }
  }

  $outputDeadline = (Get-Date).AddSeconds($TimeoutSeconds)
  while ((Get-Date) -lt $outputDeadline) {
    if (Test-Path $SmokeOutput) {
      break
    }
    Start-Sleep -Milliseconds 500
  }

  if (-not (Test-Path $SmokeOutput)) {
    Write-SmokeFailureOutput -Message "Single-instance smoke did not produce an output payload. Trace: $tracePath" -Stage 'desktop-single-instance-no-output'
  }

  if ($firstProcess -and (Get-Process -Id $firstProcess.Id -ErrorAction SilentlyContinue)) {
    Wait-Process -Id $firstProcess.Id -Timeout 10 -ErrorAction SilentlyContinue
    if (Get-Process -Id $firstProcess.Id -ErrorAction SilentlyContinue) {
      Stop-Process -Id $firstProcess.Id -Force -ErrorAction SilentlyContinue
    }
  }

  $validator = @'
const fs = require('fs');
const path = require('path');

const [smokeOutput, logsPath] = process.argv.slice(1);
const result = JSON.parse(fs.readFileSync(smokeOutput, 'utf8'));
if (!result.ok) {
  throw new Error(result.error || 'Single-instance smoke failed.');
}

const singleInstance = result.singleInstance || {};
const lastEvent = singleInstance.lastSecondInstanceEventSnapshot || {};
const finalSnapshot = singleInstance.finalWindowSnapshot || {};
const launchStart = Number(singleInstance.initialLaunchCommandCount || 0);
const launchEnd = Number(singleInstance.finalLaunchCommandCount || 0);

if (launchStart < 1) {
  throw new Error('Bundled runtime never launched during the first instance startup.');
}
if (launchEnd !== launchStart) {
  throw new Error('Second launch triggered an extra bundled runtime start. before=' + launchStart + ' after=' + launchEnd);
}
if (Number(singleInstance.secondInstanceEventCount || 0) < 1) {
  throw new Error('Primary instance did not record any second-instance event.');
}
if (Number(lastEvent.windowCount || 0) !== 1) {
  throw new Error('Second-instance handler observed an unexpected window count: ' + Number(lastEvent.windowCount || 0));
}
if (Number(lastEvent.visibleWindowCount || 0) !== 1) {
  throw new Error('Second-instance handler did not keep a single visible main window: ' + Number(lastEvent.visibleWindowCount || 0));
}
if (!lastEvent.mainWindow || !lastEvent.mainWindow.exists) {
  throw new Error('Second-instance handler did not keep a live main window reference.');
}
if (Number(finalSnapshot.windowCount || 0) !== 1) {
  throw new Error('Final window snapshot observed an unexpected window count: ' + Number(finalSnapshot.windowCount || 0));
}

const forbiddenPatterns = [
  'gateway already running',
  'Port 18889 is already in use',
  'schtasks /End /TN'
];

const matchingLogHits = [];
const scan = (target) => {
  const stat = fs.statSync(target);
  if (stat.isDirectory()) {
    for (const entry of fs.readdirSync(target)) {
      scan(path.join(target, entry));
    }
    return;
  }
  const content = fs.readFileSync(target, 'utf8');
  for (const pattern of forbiddenPatterns) {
    if (content.includes(pattern)) {
      matchingLogHits.push({ filePath: target, pattern });
    }
  }
};

if (fs.existsSync(logsPath)) {
  scan(logsPath);
}
if (matchingLogHits.length > 0) {
  throw new Error('Conflict markers were found in logs: ' + JSON.stringify(matchingLogHits));
}

const summary = {
  ok: true,
  smokeOutput,
  logsPath,
  initialLaunchCommandCount: launchStart,
  finalLaunchCommandCount: launchEnd,
  secondInstanceEventCount: Number(singleInstance.secondInstanceEventCount || 0),
  windowCount: Number(lastEvent.windowCount || 0),
  visibleWindowCount: Number(lastEvent.visibleWindowCount || 0),
  focusedWindowCount: Number(lastEvent.focusedWindowCount || 0),
  runtimeManagerLogPath: String(singleInstance.runtimeManagerLogPath || ''),
};
console.log(JSON.stringify(summary, null, 2));
'@

  $summary = & node -e $validator $SmokeOutput $LogsPath
  if ($LASTEXITCODE -ne 0) {
    throw 'Desktop single-instance smoke validation failed.'
  }
  Write-Output $summary
}
finally {
  if ($secondProcess -and (Get-Process -Id $secondProcess.Id -ErrorAction SilentlyContinue)) {
    Stop-Process -Id $secondProcess.Id -Force -ErrorAction SilentlyContinue
  }
  if ($firstProcess -and (Get-Process -Id $firstProcess.Id -ErrorAction SilentlyContinue)) {
    Stop-Process -Id $firstProcess.Id -Force -ErrorAction SilentlyContinue
  }

  Remove-Item Env:QJCLAW_RENDERER_URL -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SECRET_BACKEND -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_USER_DATA_PATH -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_LOGS_PATH -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_RUNTIME_MODE -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_OUTPUT -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_SINGLE_INSTANCE -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_SINGLE_INSTANCE_READY_PATH -ErrorAction SilentlyContinue
  Remove-Item Env:QJCLAW_SMOKE_SINGLE_INSTANCE_EVENT_PATH -ErrorAction SilentlyContinue
}
