param(
  [string]$InstallRoot = "C:\Program Files\VpnProxy\sing-box",
  [string]$ServiceName = "VpnProxySingBox",
  [string]$ConfigSource = "C:\ProgramData\VpnProxy\generated\sing-box-config.json",
  [switch]$PlanOnly,
  [switch]$Force,
  [switch]$Uninstall
)

$ErrorActionPreference = "Stop"

$SingBoxReleaseApi = "https://api.github.com/repos/SagerNet/sing-box/releases/latest"
$WinSwReleaseApi = "https://api.github.com/repos/winsw/winsw/releases/latest"
$WrapperFile = "$ServiceName.exe"
$ConfigFile = "config.json"

function New-Result {
  param(
    [bool]$Success,
    [string]$Action,
    [bool]$Changed,
    [string]$Message,
    [hashtable]$Details = @{}
  )

  [ordered]@{
    success = $Success
    action = $Action
    changed = $Changed
    message = $Message
    details = $Details
  } | ConvertTo-Json -Depth 8
}

function Test-IsAdministrator {
  $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
  $principal = [Security.Principal.WindowsPrincipal]::new($identity)
  $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Get-NativeArchitecture {
  $processor = Get-CimInstance Win32_Processor | Select-Object -First 1
  if ($null -ne $processor -and $processor.Architecture -eq 12) { return "arm64" }
  if ([Environment]::Is64BitOperatingSystem) { return "amd64" }
  return "386"
}

function Get-WinSwArchitecture {
  param([string]$Arch)
  if ($Arch -eq "arm64") { return "arm64" }
  if ($Arch -eq "386") { return "x86" }
  return "x64"
}

function Invoke-Download {
  param([string]$Uri, [string]$Path)
  Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $Path -Headers @{ "User-Agent" = "vpn-proxy-windows-client" }
}

function Select-Asset {
  param(
    [object[]]$Assets,
    [string]$Pattern,
    [string]$Label
  )

  $asset = $Assets | Where-Object { $_.name -match $Pattern } | Select-Object -First 1
  if ($null -eq $asset) {
    throw "Не найден release asset для $Label по шаблону $Pattern."
  }
  return $asset
}

function Test-SafeInstallRoot {
  param([string]$Path)
  $full = [System.IO.Path]::GetFullPath($Path).TrimEnd("\")
  $leaf = Split-Path -Leaf $full
  $parent = Split-Path -Parent $full
  if ($leaf -ne "sing-box") { return $false }
  return $parent -match "\\VpnProxy$|\\vpn-proxy$"
}

function Backup-File {
  param([string]$Path)
  if (Test-Path -LiteralPath $Path) {
    $backup = "$Path.bak"
    Copy-Item -LiteralPath $Path -Destination $backup -Force
    return $backup
  }
  return $null
}

function Write-Utf8NoBomFile {
  param(
    [string]$Path,
    [string]$Value
  )

  $encoding = New-Object System.Text.UTF8Encoding $false
  [System.IO.File]::WriteAllText($Path, $Value, $encoding)
}

function Write-WinSwConfig {
  param(
    [string]$Root,
    [string]$Name
  )

  $xmlPath = Join-Path $Root "$Name.xml"
  $logDir = Join-Path $Root "logs"
  New-Item -ItemType Directory -Path $logDir -Force | Out-Null
  $xml = @"
<service>
  <id>$Name</id>
  <name>VPN Proxy Local sing-box</name>
  <description>Local sing-box runtime managed by VPN Proxy Windows client.</description>
  <executable>%BASE%\sing-box.exe</executable>
  <arguments>run -c "%BASE%\config.json"</arguments>
  <logpath>%BASE%\logs</logpath>
  <log mode="roll-by-size">
    <sizeThreshold>10485760</sizeThreshold>
    <keepFiles>4</keepFiles>
  </log>
  <onfailure action="restart" delay="5 sec"/>
</service>
"@
  Write-Utf8NoBomFile -Path $xmlPath -Value $xml
  return $xmlPath
}

function Stop-And-Uninstall-Service {
  param(
    [string]$Root,
    [string]$Name
  )

  $wrapper = Join-Path $Root "$Name.exe"
  $service = Get-Service -Name $Name -ErrorAction SilentlyContinue
  if ($null -ne $service -and $service.Status -ne "Stopped") {
    Stop-Service -Name $Name -Force -ErrorAction SilentlyContinue
    $service = Get-Service -Name $Name -ErrorAction SilentlyContinue
    if ($null -ne $service) {
      try { $service.WaitForStatus("Stopped", [TimeSpan]::FromSeconds(15)) } catch {}
    }
  }

  if (Test-Path -LiteralPath $wrapper) {
    Push-Location $Root
    try { & $wrapper uninstall | Out-Null } finally { Pop-Location }
  }

  $service = Get-Service -Name $Name -ErrorAction SilentlyContinue
  if ($null -ne $service) {
    sc.exe delete $Name | Out-Null
  }
}

try {
  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
  $installRootFull = [System.IO.Path]::GetFullPath($InstallRoot)
  $details = @{
    installRoot = $installRootFull
    serviceName = $ServiceName
    configSource = $ConfigSource
    singboxReleaseApi = $SingBoxReleaseApi
    winswReleaseApi = $WinSwReleaseApi
    planOnly = [bool]$PlanOnly
    uninstall = [bool]$Uninstall
  }

  if ($PlanOnly) {
    $details.items = @(
      @{ id = "sing-box-binary"; name = "sing-box.exe"; source = $SingBoxReleaseApi; target = (Join-Path $installRootFull "sing-box.exe") },
      @{ id = "winsw-wrapper"; name = $WrapperFile; source = $WinSwReleaseApi; target = (Join-Path $installRootFull $WrapperFile) },
      @{ id = "windows-service"; name = $ServiceName; target = "Windows Service" },
      @{ id = "config"; name = $ConfigFile; source = $ConfigSource; target = (Join-Path $installRootFull $ConfigFile) }
    )
    New-Result -Success $true -Action "install-singbox.plan" -Changed $false -Message "Local sing-box install plan is ready." -Details $details
    exit 0
  }

  if (-not (Test-IsAdministrator)) {
    New-Result -Success $false -Action "install-singbox" -Changed $false -Message "Administrator rights are required." -Details $details
    exit 1
  }

  if ($Uninstall) {
    if (-not (Test-SafeInstallRoot -Path $installRootFull)) {
      New-Result -Success $false -Action "uninstall-singbox" -Changed $false -Message "Unsafe InstallRoot for recursive uninstall." -Details $details
      exit 2
    }

    Stop-And-Uninstall-Service -Root $installRootFull -Name $ServiceName
    if (Test-Path -LiteralPath $installRootFull) {
      Remove-Item -LiteralPath $installRootFull -Recurse -Force
    }
    New-Result -Success $true -Action "uninstall-singbox" -Changed $true -Message "Local sing-box service and install folder were removed." -Details $details
    exit 0
  }

  $changed = $false
  New-Item -ItemType Directory -Path $installRootFull -Force | Out-Null
  $workDir = Join-Path ([System.IO.Path]::GetTempPath()) ("vpn-proxy-singbox-" + [guid]::NewGuid().ToString("N"))
  $extractDir = Join-Path $workDir "extract"
  New-Item -ItemType Directory -Path $extractDir -Force | Out-Null

  try {
    $arch = Get-NativeArchitecture
    $winswArch = Get-WinSwArchitecture -Arch $arch
    $details.architecture = $arch
    $details.winswArchitecture = $winswArch

    $singboxRelease = Invoke-RestMethod -Uri $SingBoxReleaseApi -Headers @{ "User-Agent" = "vpn-proxy-windows-client" }
    $singboxAsset = Select-Asset $singboxRelease.assets "windows-$arch\.zip$" "sing-box"
    $singboxZip = Join-Path $workDir $singboxAsset.name
    Invoke-Download $singboxAsset.browser_download_url $singboxZip
    Expand-Archive -LiteralPath $singboxZip -DestinationPath $extractDir -Force
    $singboxExe = Get-ChildItem -LiteralPath $extractDir -Recurse -Filter "sing-box.exe" | Select-Object -First 1
    if ($null -eq $singboxExe) { throw "В архиве sing-box не найден sing-box.exe." }
    Copy-Item -LiteralPath $singboxExe.FullName -Destination (Join-Path $installRootFull "sing-box.exe") -Force
    $changed = $true

    $winswRelease = Invoke-RestMethod -Uri $WinSwReleaseApi -Headers @{ "User-Agent" = "vpn-proxy-windows-client" }
    $winswAsset = Select-Asset $winswRelease.assets "WinSW-$winswArch\.exe$" "WinSW"
    Invoke-Download $winswAsset.browser_download_url (Join-Path $installRootFull $WrapperFile)
    $changed = $true

    $configTarget = Join-Path $installRootFull $ConfigFile
    $backupPath = Backup-File -Path $configTarget
    if ($backupPath) { $details.backupPath = $backupPath }
    if (Test-Path -LiteralPath $ConfigSource) {
      Copy-Item -LiteralPath $ConfigSource -Destination $configTarget -Force
    } elseif (-not (Test-Path -LiteralPath $configTarget)) {
      Write-Utf8NoBomFile -Path $configTarget -Value '{"log":{"level":"info","timestamp":true},"inbounds":[],"outbounds":[{"type":"direct","tag":"direct"}],"route":{"final":"direct"}}'
    }

    $xmlPath = Write-WinSwConfig -Root $installRootFull -Name $ServiceName
    $details.configPath = $configTarget
    $details.wrapperConfigPath = $xmlPath

    if ($Force) {
      Stop-And-Uninstall-Service -Root $installRootFull -Name $ServiceName
    }

    Push-Location $installRootFull
    try {
      $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
      if ($null -eq $service) {
        & ".\$WrapperFile" install
        if ($LASTEXITCODE -ne 0) { throw "WinSW install завершился с кодом $LASTEXITCODE." }
        $changed = $true
      }
      & ".\$WrapperFile" start
      if ($LASTEXITCODE -ne 0) {
        Start-Service -Name $ServiceName -ErrorAction Stop
      }
    } finally {
      Pop-Location
    }
  } finally {
    if (Test-Path -LiteralPath $workDir) {
      Remove-Item -LiteralPath $workDir -Recurse -Force -ErrorAction SilentlyContinue
    }
  }

  New-Result -Success $true -Action "install-singbox" -Changed $changed -Message "Local sing-box service is installed and started." -Details $details
} catch {
  New-Result -Success $false -Action "install-singbox" -Changed $false -Message $_.Exception.Message
  exit 1
}
