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

function Require-Tool {
    param(
        [Parameter(Mandatory = $true)][string]$Name
    )

    if ($null -eq (Get-Command $Name -ErrorAction SilentlyContinue)) {
        throw "Required tool not found: $Name"
    }
}

function Invoke-NativeCommand {
    param(
        [Parameter(Mandatory = $true)][string]$Name,
        [Parameter(Mandatory = $true)][scriptblock]$Action
    )

    Write-Host $Name
    & $Action
    if ($LASTEXITCODE -ne 0) {
        throw "Step failed: $Name"
    }
}

function Get-RemoteScript {
    param(
        [Parameter(Mandatory = $true)][string]$RemoteReleaseDir,
        [Parameter(Mandatory = $true)][string]$RemoteCurrentLink,
        [Parameter(Mandatory = $true)][string]$ContainerName,
        [Parameter(Mandatory = $true)][string]$ImageName,
        [Parameter(Mandatory = $true)][string]$ReleaseTimestamp,
        [Parameter(Mandatory = $true)][string]$RemoteDataDir,
        [Parameter(Mandatory = $true)][string]$ContainerPort,
        [Parameter(Mandatory = $true)][string]$HostPort
    )

    $script = @'
set -euo pipefail

remote_release_dir='__REMOTE_RELEASE_DIR__'
remote_current_link='__REMOTE_CURRENT_LINK__'
container_name='__CONTAINER_NAME__'
image_name='__IMAGE_NAME__'
release_timestamp='__RELEASE_TIMESTAMP__'
remote_data_dir='__REMOTE_DATA_DIR__'
container_port='__CONTAINER_PORT__'
host_port='__HOST_PORT__'

previous_current_target=''
if [ -L "${remote_current_link}" ]; then
    previous_current_target="$(readlink -f "${remote_current_link}")"
fi

docker build -t "${image_name}:${release_timestamp}" -t "${image_name}:latest" "${remote_release_dir}"
ln -sfn "${remote_release_dir}" "${remote_current_link}"

if docker ps -aq --filter "name=^/${container_name}$" | grep -q .; then
    docker rm -f "${container_name}" >/dev/null
fi

if ! docker run -d \
    --name "${container_name}" \
    --restart unless-stopped \
    -p "127.0.0.1:${host_port}:${container_port}" \
    -e ASPNETCORE_ENVIRONMENT=Production \
    -e ASPNETCORE_URLS="http://+:${container_port}" \
    -e ConnectionStrings__RpgRoller="Data Source=/app/data/rpgroller.db" \
    -v "${remote_data_dir}:/app/data" \
    "${image_name}:${release_timestamp}" >/dev/null; then
    if [ -n "${previous_current_target}" ]; then
        ln -sfn "${previous_current_target}" "${remote_current_link}"
    fi
    exit 1
fi
'@

    return $script.
        Replace("__REMOTE_RELEASE_DIR__", $RemoteReleaseDir).
        Replace("__REMOTE_CURRENT_LINK__", $RemoteCurrentLink).
        Replace("__CONTAINER_NAME__", $ContainerName).
        Replace("__IMAGE_NAME__", $ImageName).
        Replace("__RELEASE_TIMESTAMP__", $ReleaseTimestamp).
        Replace("__REMOTE_DATA_DIR__", $RemoteDataDir).
        Replace("__CONTAINER_PORT__", $ContainerPort).
        Replace("__HOST_PORT__", $HostPort)
}

$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$repoRoot = Split-Path -Parent $scriptDir
$projectPath = Join-Path $repoRoot "RpgRoller\RpgRoller.csproj"

$remoteHost = "myvserver"
$remoteRoot = "/root/docker/rpgroller"
$remoteReleasesDir = "$remoteRoot/releases"
$remoteCurrentLink = "$remoteRoot/current"
$remoteDataDir = "$remoteRoot/data"

$containerName = "rpgroller"
$imageName = "rpgroller"
$containerPort = "8080"
$hostPort = "8082"
$releaseTimestamp = (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss")
$localStageDir = Join-Path $repoRoot "artifacts\deploy\$releaseTimestamp"
$localPublishDir = Join-Path $localStageDir "publish"
$remoteReleaseDir = "$remoteReleasesDir/$releaseTimestamp"

Write-Host "Deploying release $releaseTimestamp"

Require-Tool -Name "dotnet"
Require-Tool -Name "ssh"
Require-Tool -Name "scp"

try {
    New-Item -ItemType Directory -Path $localPublishDir -Force | Out-Null

    Invoke-NativeCommand -Name "1) Publishing app locally..." -Action {
        dotnet publish $projectPath -c Release -o $localPublishDir
    }

    @'
FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
ENV ASPNETCORE_URLS=http://+:8080
ENV DOTNET_EnableDiagnostics=0
EXPOSE 8080
COPY publish/ ./
RUN mkdir -p /app/data
ENTRYPOINT ["dotnet", "RpgRoller.dll"]
'@ | Set-Content -Path (Join-Path $localStageDir "Dockerfile") -NoNewline

    Invoke-NativeCommand -Name "2) Preparing remote release directory..." -Action {
        ssh $remoteHost "mkdir -p '$remoteReleasesDir' '$remoteDataDir' && test ! -e '$remoteReleaseDir' && mkdir -p '$remoteReleaseDir'"
    }

    Invoke-NativeCommand -Name "3) Uploading release payload..." -Action {
        Push-Location $localStageDir
        try {
            scp -r "Dockerfile" "publish" "${remoteHost}:$remoteReleaseDir/"
        }
        finally {
            Pop-Location
        }
    }

    $remoteScript = Get-RemoteScript `
        -RemoteReleaseDir $remoteReleaseDir `
        -RemoteCurrentLink $remoteCurrentLink `
        -ContainerName $containerName `
        -ImageName $imageName `
        -ReleaseTimestamp $releaseTimestamp `
        -RemoteDataDir $remoteDataDir `
        -ContainerPort $containerPort `
        -HostPort $hostPort

    Invoke-NativeCommand -Name "4) Building image and restarting container on remote host..." -Action {
        $remoteScript | ssh $remoteHost "bash -se"
    }

    Write-Host "5) Deployment complete."
}
finally {
    if (Test-Path $localStageDir) {
        Remove-Item -Path $localStageDir -Recurse -Force -ErrorAction SilentlyContinue
    }
}
