param(
  [string]$AdminUsername = 'e2e_admin',
  [string]$AdminPassword = 'E2EAdmin@123456',
  [string]$AdminEmail = 'e2e_admin@example.com',
  [int]$BrowserPort = 0,
  [int]$BackendPort = 0,
  [int]$FrontendPort = 0
)

$ErrorActionPreference = 'Stop'

$projectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path
$frontendRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
$tempCacheRoot = Join-Path $env:TEMP 'ums-e2e-cache'
$goCacheDir = Join-Path $tempCacheRoot 'go-build'
$goModCacheDir = Join-Path $tempCacheRoot 'gomod'
$goPathDir = Join-Path $tempCacheRoot 'gopath'
$serverExePath = Join-Path $env:TEMP ("ums-server-playwright-e2e-" + [guid]::NewGuid().ToString('N') + '.exe')
$e2eRunRoot = Join-Path $env:TEMP ("ums-playwright-e2e-" + [guid]::NewGuid().ToString('N'))
$e2eDataRoot = Join-Path $e2eRunRoot 'data'
$e2eDbPath = Join-Path $e2eDataRoot 'user_management.e2e.db'
$smtpCaptureFile = Join-Path $e2eRunRoot 'smtp-capture.jsonl'

New-Item -ItemType Directory -Force $goCacheDir, $goModCacheDir, $goPathDir, $e2eDataRoot | Out-Null

function Get-FreeTcpPort {
  $listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
  $listener.Start()
  try {
    return ([System.Net.IPEndPoint]$listener.LocalEndpoint).Port
  } finally {
    $listener.Stop()
  }
}

function Test-UrlReady {
  param(
    [Parameter(Mandatory = $true)][string]$Url
  )

  try {
    $response = Invoke-WebRequest $Url -UseBasicParsing -TimeoutSec 2
    return $response.StatusCode -ge 200 -and $response.StatusCode -lt 500
  } catch {
    return $false
  }
}

function Wait-UrlReady {
  param(
    [Parameter(Mandatory = $true)][string]$Url,
    [Parameter(Mandatory = $true)][string]$Label,
    [int]$RetryCount = 120,
    [int]$DelayMs = 500
  )

  for ($i = 0; $i -lt $RetryCount; $i++) {
    if (Test-UrlReady -Url $Url) {
      return
    }
    Start-Sleep -Milliseconds $DelayMs
  }

  throw "$Label did not become ready: $Url"
}

function Start-ManagedProcess {
  param(
    [Parameter(Mandatory = $true)][string]$Name,
    [Parameter(Mandatory = $true)][string]$FilePath,
    [string[]]$ArgumentList = @(),
    [Parameter(Mandatory = $true)][string]$WorkingDirectory
  )

  $stdoutPath = Join-Path $env:TEMP "$Name-stdout.log"
  $stderrPath = Join-Path $env:TEMP "$Name-stderr.log"
  Remove-Item $stdoutPath, $stderrPath -Force -ErrorAction SilentlyContinue

  if ($ArgumentList -and $ArgumentList.Count -gt 0) {
    $process = Start-Process `
      -FilePath $FilePath `
      -ArgumentList $ArgumentList `
      -WorkingDirectory $WorkingDirectory `
      -PassThru `
      -WindowStyle Hidden `
      -RedirectStandardOutput $stdoutPath `
      -RedirectStandardError $stderrPath
  } else {
    $process = Start-Process `
      -FilePath $FilePath `
      -WorkingDirectory $WorkingDirectory `
      -PassThru `
      -WindowStyle Hidden `
      -RedirectStandardOutput $stdoutPath `
      -RedirectStandardError $stderrPath
  }

  return [pscustomobject]@{
    Name = $Name
    Process = $process
    StdOut = $stdoutPath
    StdErr = $stderrPath
  }
}

function Stop-ManagedProcess {
  param(
    [Parameter(Mandatory = $false)]$Handle
  )

  if (-not $Handle) {
    return
  }

  if ($Handle.Process -and -not $Handle.Process.HasExited) {
    try {
      taskkill /PID $Handle.Process.Id /T /F *> $null
    } catch {
      Stop-Process -Id $Handle.Process.Id -Force -ErrorAction SilentlyContinue
    }
  }
}

function Show-ManagedProcessLogs {
  param(
    [Parameter(Mandatory = $false)]$Handle
  )

  if (-not $Handle) {
    return
  }

  if (Test-Path $Handle.StdOut) {
    Get-Content $Handle.StdOut -ErrorAction SilentlyContinue
  }
  if (Test-Path $Handle.StdErr) {
    Get-Content $Handle.StdErr -ErrorAction SilentlyContinue
  }
}

function Remove-ManagedProcessLogs {
  param(
    [Parameter(Mandatory = $false)]$Handle
  )

  if (-not $Handle) {
    return
  }

  Remove-Item $Handle.StdOut, $Handle.StdErr -Force -ErrorAction SilentlyContinue
}

$backendHandle = $null
$frontendHandle = $null
$smtpHandle = $null
$selectedBackendPort = if ($BackendPort -gt 0) { $BackendPort } else { Get-FreeTcpPort }
$selectedFrontendPort = if ($FrontendPort -gt 0) { $FrontendPort } else { Get-FreeTcpPort }
$selectedSMTPPort = Get-FreeTcpPort
$backendBaseUrl = "http://127.0.0.1:$selectedBackendPort"
$frontendBaseUrl = "http://127.0.0.1:$selectedFrontendPort"

try {
  $serverSrcPath = Join-Path $projectRoot 'cmd\server'
  try {
    $env:GOCACHE = $goCacheDir
    go build -o $serverExePath $serverSrcPath
    if ($LASTEXITCODE -ne 0) {
      throw 'server build failed'
    }
  } finally {
    Pop-Location
    Remove-Item Env:GOCACHE -ErrorAction SilentlyContinue
  }

  $env:SERVER_PORT = "$selectedBackendPort"
  $env:DATABASE_DBNAME = $e2eDbPath
$env:SERVER_MODE = 'debug'
$env:SERVER_FRONTEND_URL = $frontendBaseUrl
$env:CORS_ALLOWED_ORIGINS = "$frontendBaseUrl,http://localhost:$selectedFrontendPort"
  $env:LOGGING_OUTPUT = 'stdout'
  $env:EMAIL_HOST = '127.0.0.1'
  $env:EMAIL_PORT = "$selectedSMTPPort"
  $env:EMAIL_FROM_EMAIL = 'noreply@test.local'
  $env:EMAIL_FROM_NAME = 'UMS E2E'
  # JWT secret must be at least 32 bytes
  $env:JWT_SECRET = 'e2e-test-jwt-secret-at-least-32-bytes-long-for-security'

  Write-Host "playwright e2e backend: $backendBaseUrl"
  Write-Host "playwright e2e frontend: $frontendBaseUrl"
  Write-Host "playwright e2e smtp: 127.0.0.1:$selectedSMTPPort"
  Write-Host "playwright e2e sqlite: $e2eDbPath"

  $smtpHandle = Start-ManagedProcess `
    -Name 'ums-smtp-capture' `
    -FilePath 'node' `
    -ArgumentList @((Join-Path $PSScriptRoot 'mock-smtp-capture.mjs'), '--port', "$selectedSMTPPort", '--output', $smtpCaptureFile) `
    -WorkingDirectory $frontendRoot

  Start-Sleep -Milliseconds 500
  if ($smtpHandle.Process -and $smtpHandle.Process.HasExited) {
    Show-ManagedProcessLogs $smtpHandle
    throw 'smtp capture server failed to start'
  }

  $backendHandle = Start-ManagedProcess `
    -Name 'ums-backend-playwright' `
    -FilePath $serverExePath `
    -WorkingDirectory $projectRoot

  try {
    Wait-UrlReady -Url "$backendBaseUrl/health" -Label 'backend'
  } catch {
    Show-ManagedProcessLogs $backendHandle
    throw
  }

  $env:VITE_API_PROXY_TARGET = $backendBaseUrl
  $env:VITE_API_BASE_URL = '/api/v1'
  $env:NODE_ENV = 'development'
  $frontendHandle = Start-ManagedProcess `
    -Name 'ums-frontend-playwright' `
    -FilePath 'npm.cmd' `
    -ArgumentList @('run', 'dev', '--', '--host', '127.0.0.1', '--port', "$selectedFrontendPort") `
    -WorkingDirectory $frontendRoot

  try {
    Wait-UrlReady -Url $frontendBaseUrl -Label 'frontend'
  } catch {
    Show-ManagedProcessLogs $frontendHandle
    throw
  }

  $env:E2E_LOGIN_USERNAME = $AdminUsername
  $env:E2E_LOGIN_PASSWORD = $AdminPassword
  $env:E2E_LOGIN_EMAIL = $AdminEmail
  $env:E2E_EXPECT_ADMIN_BOOTSTRAP = '1'
  $env:E2E_EXTERNAL_WEB_SERVER = '1'
  $env:E2E_BASE_URL = $frontendBaseUrl
  $env:E2E_SMTP_CAPTURE_FILE = $smtpCaptureFile

  Push-Location $frontendRoot
  try {
    $lastError = $null
    for ($attempt = 1; $attempt -le 2; $attempt++) {
      try {
        & (Join-Path $PSScriptRoot 'run-cdp-smoke.ps1') `
          -Port $BrowserPort `
          -Command @('node', './scripts/run-playwright-cdp-e2e.mjs')
        $lastError = $null
        break
      } catch {
        $lastError = $_
        if ($attempt -ge 2) {
          throw
        }
        $retryReason = if ($_.Exception -and $_.Exception.Message) { $_.Exception.Message } else { $_ | Out-String }
        Write-Host "playwright-cdp suite retry: restarting browser and rerunning attempt $($attempt + 1) :: $retryReason"
        Start-Sleep -Seconds 1
      }
    }

    if ($lastError) {
      throw $lastError
    }
  } finally {
    Pop-Location
    Remove-Item Env:E2E_LOGIN_USERNAME -ErrorAction SilentlyContinue
    Remove-Item Env:E2E_LOGIN_PASSWORD -ErrorAction SilentlyContinue
    Remove-Item Env:E2E_LOGIN_EMAIL -ErrorAction SilentlyContinue
    Remove-Item Env:E2E_EXPECT_ADMIN_BOOTSTRAP -ErrorAction SilentlyContinue
    Remove-Item Env:E2E_EXTERNAL_WEB_SERVER -ErrorAction SilentlyContinue
    Remove-Item Env:E2E_BASE_URL -ErrorAction SilentlyContinue
    Remove-Item Env:E2E_SMTP_CAPTURE_FILE -ErrorAction SilentlyContinue
  }
} finally {
  Stop-ManagedProcess $frontendHandle
  Remove-ManagedProcessLogs $frontendHandle
  Stop-ManagedProcess $backendHandle
  Remove-ManagedProcessLogs $backendHandle
  Stop-ManagedProcess $smtpHandle
  Remove-ManagedProcessLogs $smtpHandle
  Remove-Item Env:SERVER_PORT -ErrorAction SilentlyContinue
  Remove-Item Env:DATABASE_DBNAME -ErrorAction SilentlyContinue
  Remove-Item Env:SERVER_MODE -ErrorAction SilentlyContinue
  Remove-Item Env:SERVER_FRONTEND_URL -ErrorAction SilentlyContinue
  Remove-Item Env:CORS_ALLOWED_ORIGINS -ErrorAction SilentlyContinue
  Remove-Item Env:LOGGING_OUTPUT -ErrorAction SilentlyContinue
  Remove-Item Env:EMAIL_HOST -ErrorAction SilentlyContinue
  Remove-Item Env:EMAIL_PORT -ErrorAction SilentlyContinue
  Remove-Item Env:EMAIL_FROM_EMAIL -ErrorAction SilentlyContinue
  Remove-Item Env:EMAIL_FROM_NAME -ErrorAction SilentlyContinue
    Remove-Item Env:VITE_API_PROXY_TARGET -ErrorAction SilentlyContinue
    Remove-Item Env:VITE_API_BASE_URL -ErrorAction SilentlyContinue
    Remove-Item Env:NODE_ENV -ErrorAction SilentlyContinue
    Remove-Item Env:JWT_SECRET -ErrorAction SilentlyContinue
Remove-Item Env:DEFAULT_ADMIN_EMAIL -ErrorAction SilentlyContinue
  Remove-Item Env:DEFAULT_ADMIN_PASSWORD -ErrorAction SilentlyContinue
  Remove-Item $serverExePath -Force -ErrorAction SilentlyContinue
  Remove-Item $e2eRunRoot -Recurse -Force -ErrorAction SilentlyContinue
}
