[CmdletBinding()]
param(
  [switch]$Force,
  [switch]$SkipSiteCheck,
  [string]$Service
)

# ---------------------------------------------------------------------------
# Goodwalk production deployment settings
# Update these values before the first real deployment.
# This script targets the main Svelte Goodwalk stack that uses the top-level
# docker-compose.yml. It does not touch the legacy WordPress compose files.
# ---------------------------------------------------------------------------
$SshUser = 'root'
$ServerHost = 'gw-prod'
# Leave blank to use interactive password prompts instead of an SSH key.
$SshKeyPath = ''
$LocalProjectPath = Split-Path -Parent $MyInvocation.MyCommand.Path
$SshConfigPath = Join-Path $LocalProjectPath 'ssh-config'
$RemoteDeploymentPath = '/docker/goodwalk-svelte'
$ComposeFileName = 'docker-compose.prod.yml'
$DockerProjectName = 'goodwalk-svelte'
$NginxConfigSource = 'nginx/goodwalk.co.nz.svelte.conf.example'
$NginxConfigTarget = '/docker/nginx/conf.d/goodwalk.co.nz.conf'
$NginxComposeFile = '/docker/nginx/docker-compose.yml'
$NginxProjectName = 'nginx'
# Host paths used for the maintenance page. The directory must be bind-mounted
# into the shared nginx container at /var/www/maintenance:ro (see DEPLOYMENT.md).
# The flag file lives in the existing conf.d bind mount; nginx ignores non-.conf
# files, so it does not pollute the include glob.
$MaintenanceHostDir = '/docker/nginx/maintenance'
$MaintenanceFlagPath = '/docker/nginx/conf.d/maintenance.flag'

# Optional deployment settings.
$VerifyUrl = 'https://www.goodwalk.co.nz/api/health'
$RemoteArchivePath = '/tmp/goodwalk-deploy.tgz'
$RemoteHelperPath = '/tmp/goodwalk-deploy-remote.sh'
$LocalRemoteHelperPath = Join-Path $LocalProjectPath 'scripts\deploy-remote.sh'
$GeneratedHomepageContentPath = Join-Path $LocalProjectPath 'deploy-data\homepage-content.json'

function Assert-NotBlank {
  param(
    [string]$Name,
    [string]$Value
  )

  if ([string]::IsNullOrWhiteSpace($Value)) {
    throw "Required setting '$Name' is blank. Update deploy.ps1 before running it."
  }
}

function Assert-Command {
  param([string]$Name)

  if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
    throw "Required command '$Name' is not available in PATH."
  }
}

function Invoke-External {
  param(
    [string]$FilePath,
    [string[]]$Arguments
  )

  & $FilePath @Arguments

  if ($LASTEXITCODE -ne 0) {
    throw "Command failed: $FilePath $($Arguments -join ' ')"
  }
}

function Get-SshArgumentList {
  $args = @()

  if (-not [string]::IsNullOrWhiteSpace($SshConfigPath)) {
    $args += @('-F', $SshConfigPath)
  }

  if (-not [string]::IsNullOrWhiteSpace($SshKeyPath)) {
    $args += @('-i', $SshKeyPath)
  }

  return $args
}

function New-DeployArchive {
  param(
    [string]$ProjectPath
  )

  $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
  $archivePath = Join-Path ([System.IO.Path]::GetTempPath()) "goodwalk-deploy-$timestamp.tgz"
  $excludeArgs = @(
    '--exclude=.git',
    '--exclude=node_modules',
    '--exclude=.svelte-kit',
    '--exclude=build',
    '--exclude=.env',
    '--exclude=.env.*',
    '--exclude=logs',
    '--exclude=mail-api/__pycache__',
    '--exclude=mail-api/*.pyc',
    '--exclude=migration-backups'
  )

  if (Test-Path $archivePath) {
    Remove-Item -LiteralPath $archivePath -Force
  }

  Push-Location $ProjectPath
  try {
    $tarArguments = @('-czf', $archivePath) + $excludeArgs + @('.')
    Invoke-External -FilePath 'tar' -Arguments $tarArguments
  }
  finally {
    Pop-Location
  }

  return $archivePath
}

function Export-HomepageContent {
  param(
    [string]$ProjectPath,
    [string]$OutputPath
  )

  $scriptPath = Join-Path $ProjectPath 'scripts\export-homepage-content.mjs'

  if (-not (Test-Path -LiteralPath $scriptPath)) {
    throw "Homepage content export script not found: $scriptPath"
  }

  $resolverPath = Join-Path $ProjectPath 'scripts\sveltekit-resolver.mjs'

  Push-Location $ProjectPath
  try {
    Invoke-External -FilePath 'node' -Arguments @(
      '--experimental-strip-types',
      "--import=$(([uri]::new([System.IO.Path]::GetFullPath($resolverPath))).AbsoluteUri)",
      $scriptPath,
      $OutputPath
    )
  }
  finally {
    Pop-Location
  }
}

function New-UnixScriptCopy {
  param(
    [string]$SourcePath
  )

  if (-not (Test-Path -LiteralPath $SourcePath)) {
    throw "Script not found: $SourcePath"
  }

  $tempFileName = 'goodwalk-deploy-helper-{0}.sh' -f ([System.Guid]::NewGuid().ToString('N'))
  $tempPath = Join-Path ([System.IO.Path]::GetTempPath()) $tempFileName
  $content = [System.IO.File]::ReadAllText($SourcePath)
  $normalized = $content.Replace("`r`n", "`n").Replace("`r", "`n")
  $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
  [System.IO.File]::WriteAllText($tempPath, $normalized, $utf8NoBom)
  return $tempPath
}

function Invoke-SiteCheck {
  param([string]$Url)

  Write-Host ''
  Write-Host "[deploy] Checking production site: $Url"

  try {
    $response = Invoke-WebRequest -Uri $Url -MaximumRedirection 5 -TimeoutSec 30
    Write-Host "[deploy] Site responded with HTTP $($response.StatusCode)"
  }
  catch {
    Write-Warning "Production site check failed: $($_.Exception.Message)"
  }
}

Assert-Command ssh
Assert-Command scp
Assert-Command tar
Assert-Command node

Assert-NotBlank -Name 'SshUser' -Value $SshUser
Assert-NotBlank -Name 'ServerHost' -Value $ServerHost
Assert-NotBlank -Name 'LocalProjectPath' -Value $LocalProjectPath
Assert-NotBlank -Name 'RemoteDeploymentPath' -Value $RemoteDeploymentPath
Assert-NotBlank -Name 'ComposeFileName' -Value $ComposeFileName
Assert-NotBlank -Name 'DockerProjectName' -Value $DockerProjectName
Assert-NotBlank -Name 'NginxConfigSource' -Value $NginxConfigSource
Assert-NotBlank -Name 'NginxConfigTarget' -Value $NginxConfigTarget
Assert-NotBlank -Name 'NginxComposeFile' -Value $NginxComposeFile
Assert-NotBlank -Name 'NginxProjectName' -Value $NginxProjectName
Assert-NotBlank -Name 'MaintenanceHostDir' -Value $MaintenanceHostDir
Assert-NotBlank -Name 'MaintenanceFlagPath' -Value $MaintenanceFlagPath

if (-not [string]::IsNullOrWhiteSpace($Service)) {
  $Service = $Service.Trim()
}

if (-not [string]::IsNullOrWhiteSpace($SshConfigPath) -and -not (Test-Path -LiteralPath $SshConfigPath)) {
  throw "SSH config file not found: $SshConfigPath"
}

if (-not [string]::IsNullOrWhiteSpace($SshKeyPath) -and -not (Test-Path -LiteralPath $SshKeyPath)) {
  throw "SSH key not found: $SshKeyPath"
}

if (-not (Test-Path -LiteralPath $LocalProjectPath)) {
  throw "Local project path not found: $LocalProjectPath"
}

if (-not (Test-Path -LiteralPath (Join-Path $LocalProjectPath $ComposeFileName))) {
  throw "Compose file not found in local project path: $(Join-Path $LocalProjectPath $ComposeFileName)"
}

if (-not (Test-Path -LiteralPath $LocalRemoteHelperPath)) {
  throw "Remote deployment helper not found: $LocalRemoteHelperPath"
}

$sshTarget = '{0}@{1}' -f $SshUser, $ServerHost
$scpArchiveTarget = '{0}:{1}' -f $sshTarget, $RemoteArchivePath
$scpHelperTarget = '{0}:{1}' -f $sshTarget, $RemoteHelperPath
$sshArgs = Get-SshArgumentList

Write-Host '[deploy] Main Goodwalk website deployment'
Write-Host "[deploy] Local project path: $LocalProjectPath"
Write-Host "[deploy] Remote deployment path: $RemoteDeploymentPath"
Write-Host "[deploy] Remote compose file: $ComposeFileName"
Write-Host "[deploy] Docker project name: $DockerProjectName"
Write-Host "[deploy] Shared nginx config: $NginxConfigTarget"
Write-Host "[deploy] Shared nginx compose file: $NginxComposeFile"
Write-Host "[deploy] Maintenance host dir:  $MaintenanceHostDir (must be bind-mounted at /var/www/maintenance:ro)"
Write-Host "[deploy] Maintenance flag path: $MaintenanceFlagPath"
Write-Host "[deploy] SSH target: $sshTarget"
Write-Host "[deploy] SSH config: $SshConfigPath"
if (-not [string]::IsNullOrWhiteSpace($Service)) {
  Write-Host "[deploy] Target service: $Service"
}
if ([string]::IsNullOrWhiteSpace($SshKeyPath)) {
  Write-Host '[deploy] SSH auth: interactive password prompt'
} else {
  Write-Host "[deploy] SSH auth: key file $SshKeyPath"
}
Write-Host ''
Write-Host '[deploy] Safety notes:'
Write-Host '  - Only the top-level Goodwalk compose project will be updated.'
Write-Host '  - Legacy WordPress/onboarding compose files are not used.'
Write-Host '  - Remote .env files are preserved because they are not uploaded.'
Write-Host '  - No global Docker prune/stop/delete commands are used.'
Write-Host '  - Shared nginx will be updated and reloaded with the Docker-DNS-based config.'

if (-not $Force) {
  $confirmation = Read-Host "Type DEPLOY to continue with the remote path '$RemoteDeploymentPath'"
  if ($confirmation -ne 'DEPLOY') {
    throw 'Deployment cancelled.'
  }
}

$archivePath = $null
$uploadHelperPath = $null

try {
  Write-Host ''
  Write-Host '[deploy] Exporting current homepage content for PostgreSQL sync'
  Export-HomepageContent -ProjectPath $LocalProjectPath -OutputPath $GeneratedHomepageContentPath

  Write-Host ''
  Write-Host '[deploy] Creating deployment archive'
  $archivePath = New-DeployArchive -ProjectPath $LocalProjectPath
  Write-Host "[deploy] Archive ready: $archivePath"

  Write-Host ''
  Write-Host '[deploy] Uploading remote helper'
  $uploadHelperPath = New-UnixScriptCopy -SourcePath $LocalRemoteHelperPath
  Invoke-External -FilePath 'scp' -Arguments ($sshArgs + @($uploadHelperPath, $scpHelperTarget))

  Write-Host ''
  Write-Host '[deploy] Uploading application archive'
  Invoke-External -FilePath 'scp' -Arguments ($sshArgs + @($archivePath, $scpArchiveTarget))

  Write-Host ''
  Write-Host '[deploy] Running remote deployment'
  Invoke-External -FilePath 'ssh' -Arguments ($sshArgs + @(
    $sshTarget,
    'bash',
    $RemoteHelperPath,
    '--archive',
    $RemoteArchivePath,
    '--deploy-path',
    $RemoteDeploymentPath,
    '--compose-file',
    $ComposeFileName,
    '--project-name',
    $DockerProjectName,
    '--nginx-source',
    $NginxConfigSource,
    '--nginx-target',
    $NginxConfigTarget,
    '--nginx-compose-file',
    $NginxComposeFile,
    '--nginx-project-name',
    $NginxProjectName,
    '--maintenance-host-dir',
    $MaintenanceHostDir,
    '--maintenance-flag',
    $MaintenanceFlagPath
  ) + $(if (-not [string]::IsNullOrWhiteSpace($Service)) { @('--service', $Service) } else { @() }))

  Write-Host ''
  Write-Host '[deploy] Cleaning remote temporary files'
  Invoke-External -FilePath 'ssh' -Arguments ($sshArgs + @(
    $sshTarget,
    'rm',
    '-f',
    $RemoteArchivePath,
    $RemoteHelperPath
  ))

  if (-not $SkipSiteCheck) {
    Invoke-SiteCheck -Url $VerifyUrl
  }

  Write-Host ''
  Write-Host '[deploy] Deployment completed successfully'
}
finally {
  if ($archivePath -and (Test-Path -LiteralPath $archivePath)) {
    Remove-Item -LiteralPath $archivePath -Force
  }
  if ($uploadHelperPath -and (Test-Path -LiteralPath $uploadHelperPath)) {
    Remove-Item -LiteralPath $uploadHelperPath -Force
  }
}
