param(
  [switch]$Restart,
  [switch]$ForceKillPorts,
  [switch]$InstallDependencies,
  [switch]$InstallBrowsers,
  [switch]$InitDatabase,
  [switch]$SeedDatabase
)

$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest

$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
$RuntimeDir = Join-Path $RepoRoot ".runtime"

$Services = @(
  @{
    Name = "api"
    Port = 3001
    Command = "npm.cmd run dev:api"
    ReadyUrl = "http://localhost:3001/health"
    ReadyText = '"ok":true'
  },
  @{
    Name = "web"
    Port = 3000
    Command = "npm.cmd run dev:web"
    ReadyUrl = "http://localhost:3000"
    ReadyText = ""
  }
)

function Write-Step([string]$Message) {
  Write-Host "==> $Message" -ForegroundColor Cyan
}

function Write-WarnLine([string]$Message) {
  Write-Host "WARN: $Message" -ForegroundColor Yellow
}

function Get-PidFilePath([string]$Name) {
  Join-Path $RuntimeDir "$Name.pid"
}

function Get-LogPath([string]$Name) {
  Join-Path $RuntimeDir "$Name.log"
}

function Get-ErrLogPath([string]$Name) {
  Join-Path $RuntimeDir "$Name.err.log"
}

function Get-ManagedPid([string]$Name) {
  $pidFile = Get-PidFilePath $Name
  if (-not (Test-Path $pidFile)) {
    return $null
  }

  $raw = Get-Content $pidFile -ErrorAction SilentlyContinue | Select-Object -First 1
  if (-not $raw) {
    return $null
  }

  $parsed = 0
  if ([int]::TryParse($raw, [ref]$parsed)) {
    return $parsed
  }

  return $null
}

function Test-ProcessRunning([int]$ProcessId) {
  try {
    $null = Get-Process -Id $ProcessId -ErrorAction Stop
    return $true
  } catch {
    return $false
  }
}

function Get-ProcessCommandLine([int]$ProcessId) {
  try {
    $process = Get-CimInstance Win32_Process -Filter "ProcessId = $ProcessId"
    if ($null -eq $process -or $null -eq $process.CommandLine) {
      return ""
    }
    return [string]$process.CommandLine
  } catch {
    return ""
  }
}

function Test-SentAIProcess([int]$ProcessId) {
  $commandLine = Get-ProcessCommandLine $ProcessId
  if (-not $commandLine) {
    return $false
  }

  return $commandLine -like "*SentAI*" -or
    $commandLine -like "*@sentai*" -or
    $commandLine -like "*npm.cmd run dev:api*" -or
    $commandLine -like "*npm.cmd run dev:web*"
}

function Stop-ProcessTree([int]$ProcessId) {
  & taskkill /PID $ProcessId /T /F | Out-Null
}

function Get-ListenerOwners([int]$Port) {
  try {
    return @(Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction Stop |
      Select-Object -ExpandProperty OwningProcess -Unique)
  } catch {
    return @()
  }
}

function Test-UrlReady([string]$Url, [string]$ExpectedText) {
  try {
    $response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 3
    if ($response.StatusCode -lt 200 -or $response.StatusCode -ge 400) {
      return $false
    }

    if ([string]::IsNullOrWhiteSpace($ExpectedText)) {
      return $true
    }

    $content = [string]$response.Content
    return $content.Contains($ExpectedText)
  } catch {
    return $false
  }
}

function Wait-ForReady([hashtable]$Service, [int]$TimeoutSeconds = 90) {
  $deadline = (Get-Date).AddSeconds($TimeoutSeconds)
  while ((Get-Date) -lt $deadline) {
    if (Test-UrlReady -Url $Service.ReadyUrl -ExpectedText $Service.ReadyText) {
      return $true
    }
    Start-Sleep -Seconds 2
  }
  return $false
}

function Ensure-PortAvailable([hashtable]$Service) {
  [array]$owners = @(Get-ListenerOwners $Service.Port)
  if ($owners.Count -eq 0) {
    return
  }

  foreach ($owner in $owners) {
    if ($ForceKillPorts -or (Test-SentAIProcess $owner)) {
      Write-WarnLine "Stopping process $owner on port $($Service.Port)."
      Stop-ProcessTree $owner
      continue
    }

    $commandLine = Get-ProcessCommandLine $owner
    throw "Port $($Service.Port) is already used by process $owner. Use -ForceKillPorts if you want the script to clear it. CommandLine: $commandLine"
  }

  Start-Sleep -Seconds 2
  [array]$remaining = @(Get-ListenerOwners $Service.Port)
  if ($remaining.Count -gt 0) {
    throw "Port $($Service.Port) is still occupied after cleanup."
  }
}

function Ensure-ManagedProcessState([hashtable]$Service) {
  $managedPid = Get-ManagedPid $Service.Name
  if (-not $managedPid) {
    return
  }

  if (-not (Test-ProcessRunning $managedPid)) {
    Remove-Item (Get-PidFilePath $Service.Name) -ErrorAction SilentlyContinue
    return
  }

  if ($Restart) {
    Write-WarnLine "Restart requested. Stopping existing managed $($Service.Name) process $managedPid."
    Stop-ProcessTree $managedPid
    Remove-Item (Get-PidFilePath $Service.Name) -ErrorAction SilentlyContinue
    Start-Sleep -Seconds 2
    return
  }

  if (Test-UrlReady -Url $Service.ReadyUrl -ExpectedText $Service.ReadyText) {
    Write-Step "$($Service.Name) is already running on port $($Service.Port)."
    $Service.SkipStart = $true
    return
  }

  Write-WarnLine "Managed $($Service.Name) process $managedPid exists but is not healthy. Restarting it."
  Stop-ProcessTree $managedPid
  Remove-Item (Get-PidFilePath $Service.Name) -ErrorAction SilentlyContinue
  Start-Sleep -Seconds 2
}

function Start-ServiceProcess([hashtable]$Service) {
  if ($Service.ContainsKey("SkipStart") -and $Service.SkipStart) {
    return
  }

  $stdout = Get-LogPath $Service.Name
  $stderr = Get-ErrLogPath $Service.Name
  Remove-Item $stdout, $stderr -ErrorAction SilentlyContinue

  Write-Step "Starting $($Service.Name) with '$($Service.Command)'."
  $process = Start-Process `
    -FilePath "cmd.exe" `
    -ArgumentList "/c", $Service.Command `
    -WorkingDirectory $RepoRoot `
    -RedirectStandardOutput $stdout `
    -RedirectStandardError $stderr `
    -PassThru

  Set-Content -Path (Get-PidFilePath $Service.Name) -Value $process.Id

  if (-not (Wait-ForReady -Service $Service)) {
    throw "$($Service.Name) did not become ready in time. Check $stdout and $stderr"
  }

  Write-Step "$($Service.Name) is ready at $($Service.ReadyUrl)."
}

New-Item -ItemType Directory -Path $RuntimeDir -Force | Out-Null
Set-Location $RepoRoot

if ($InstallDependencies) {
  Write-Step "Installing npm dependencies."
  & npm.cmd install
}

if ($InitDatabase) {
  Write-Step "Initializing database schema."
  & npm.cmd run db:init
}

if ($SeedDatabase) {
  Write-Step "Seeding database."
  & npm.cmd run db:seed
}

if ($InstallBrowsers) {
  Write-Step "Installing Playwright browsers."
  & npm.cmd run install:browsers
}

foreach ($service in $Services) {
  Ensure-ManagedProcessState $service
  Ensure-PortAvailable $service
  Start-ServiceProcess $service
}

Write-Host ""
Write-Host "SentAI local environment is running." -ForegroundColor Green
Write-Host "  Web: http://localhost:3000"
Write-Host "  API: http://localhost:3001/health"
