param(
    [string]$ProfilePath = (Join-Path $PSScriptRoot "deploy-ftp.profile.psd1"),
    [string]$Password,
    [switch]$SkipRecycle,
    [switch]$SkipMigrations
)

<#
.SYNOPSIS
    Publish the app and mirror output to an FTP-deployed IIS site.

.DESCRIPTION
    - Reads environment-specific settings from a PowerShell data file profile.
    - Builds with dotnet publish.
    - Uses WinSCP to mirror publish output into remote directory (deletes extraneous files).
    - Optionally recycles IIS app pool and runs EF migrations remotely over WinRM.

.EXAMPLE
    pwsh ./scripts/deploy-ftp.ps1 -ProfilePath ./scripts/deploy-ftp.profile.psd1
#>

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

function Assert-Tool {
    param([Parameter(Mandatory = $true)][string]$Name)
    if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
        throw "Required tool '$Name' not found. Install it or update your deploy profile."
    }
}

function Require-ConfigValue {
    param(
        [Parameter(Mandatory = $true)][hashtable]$Config,
        [Parameter(Mandatory = $true)][string]$Key
    )

    if (-not $Config.ContainsKey($Key) -or [string]::IsNullOrWhiteSpace([string]$Config[$Key])) {
        throw "Missing required deploy profile value '$Key'."
    }
}

function Resolve-ProfilePath {
    param(
        [Parameter(Mandatory = $true)][string]$BaseDirectory,
        [Parameter(Mandatory = $true)][string]$PathValue
    )

    $expanded = [Environment]::ExpandEnvironmentVariables($PathValue)
    if ([System.IO.Path]::IsPathRooted($expanded)) {
        return $expanded
    }

    return [System.IO.Path]::GetFullPath((Join-Path $BaseDirectory $expanded))
}

function Read-PlainOrPrompt {
    param(
        [string]$Value,
        [Parameter(Mandatory = $true)][string]$Prompt,
        [bool]$Secure = $false
    )

    if (-not [string]::IsNullOrWhiteSpace($Value)) {
        return $Value
    }

    if ($Secure) {
        $pwd = Read-Host -Prompt $Prompt -AsSecureString
        $ptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd)
        try {
            return [Runtime.InteropServices.Marshal]::PtrToStringUni($ptr)
        }
        finally {
            if ($ptr -ne [IntPtr]::Zero) {
                [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr)
            }
        }
    }

    return Read-Host -Prompt $Prompt
}

function Invoke-WinRmScript {
    param(
        [Parameter(Mandatory = $true)][hashtable]$Config,
        [Parameter(Mandatory = $true)][string]$PasswordValue,
        [Parameter(Mandatory = $true)][scriptblock]$ScriptBlock,
        [object[]]$ArgumentList = @()
    )

    Require-ConfigValue $Config "WinRmComputer"
    Require-ConfigValue $Config "WinRmCredentialUser"

    $secure = ConvertTo-SecureString $PasswordValue -AsPlainText -Force
    $cred = New-Object pscredential($Config.WinRmCredentialUser, $secure)

    $invokeParams = @{
        ComputerName = $Config.WinRmComputer
        Credential   = $cred
        ScriptBlock  = $ScriptBlock
        ArgumentList = $ArgumentList
    }

    if ($Config.ContainsKey("UseWinRmHttps") -and [bool]$Config.UseWinRmHttps) {
        $invokeParams["UseSSL"] = $true
    }

    if ($Config.ContainsKey("WinRmAuth") -and -not [string]::IsNullOrWhiteSpace([string]$Config.WinRmAuth)) {
        $invokeParams["Authentication"] = [string]$Config.WinRmAuth
    }

    Invoke-Command @invokeParams
}

if (-not (Test-Path $ProfilePath)) {
    throw "Deploy profile not found: $ProfilePath. Copy scripts/deploy-ftp.profile.sample.psd1 and fill environment-specific values."
}

$resolvedProfilePath = (Resolve-Path $ProfilePath).Path
$profileDirectory = Split-Path -Parent $resolvedProfilePath
$config = Import-PowerShellDataFile -Path $resolvedProfilePath

Require-ConfigValue $config "ProjectPath"
Require-ConfigValue $config "Configuration"
Require-ConfigValue $config "Runtime"
Require-ConfigValue $config "PublishDir"
Require-ConfigValue $config "WinScpPath"
Require-ConfigValue $config "RemoteDir"

$winScpSessionName = if ($config.ContainsKey("WinScpSessionName")) { [string]$config.WinScpSessionName } else { "" }
$useStoredSession = -not [string]::IsNullOrWhiteSpace($winScpSessionName)

if (-not $useStoredSession) {
    Require-ConfigValue $config "FtpHost"
    Require-ConfigValue $config "FtpUser"
}

$projectPath = Resolve-ProfilePath $profileDirectory ([string]$config.ProjectPath)
$publishDir = Resolve-ProfilePath $profileDirectory ([string]$config.PublishDir)
$winScpPath = Resolve-ProfilePath $profileDirectory ([string]$config.WinScpPath)
$selfContained = if ($config.ContainsKey("SelfContained")) { [bool]$config.SelfContained } else { $false }
$recycleAppPool = if ($config.ContainsKey("RecycleAppPool")) { [bool]$config.RecycleAppPool } else { $false }
$runEfMigrations = if ($config.ContainsKey("RunEfMigrations")) { [bool]$config.RunEfMigrations } else { $false }
$recycleAppPool = $recycleAppPool -and -not $SkipRecycle
$runEfMigrations = $runEfMigrations -and -not $SkipMigrations

$passwordFromEnv = $env:PICKNPLAY_FTP_PASSWORD
$passwordFromInput = if (-not [string]::IsNullOrWhiteSpace($Password)) { $Password } else { $passwordFromEnv }
$needsFtpPassword = -not $useStoredSession
$needsWinRmPassword = $recycleAppPool -or $runEfMigrations
$sharedPassword = ""

if ($needsFtpPassword -or $needsWinRmPassword) {
    $prompt = if ($needsFtpPassword -and $needsWinRmPassword) { "FTP/WinRM password" } elseif ($needsFtpPassword) { "FTP password" } else { "WinRM password" }
    $sharedPassword = Read-PlainOrPrompt -Value $passwordFromInput -Prompt $prompt -Secure $true
}

$passwordForSession = if ($needsFtpPassword) { $sharedPassword } else { "" }
$passwordForWinRm = if ($needsWinRmPassword) { $sharedPassword } else { "" }

Assert-Tool "dotnet"
Assert-Tool $winScpPath

Write-Host "1) Publishing..." -ForegroundColor Cyan
if (Test-Path $publishDir) {
    Remove-Item $publishDir -Recurse -Force -ErrorAction SilentlyContinue
}
New-Item -ItemType Directory -Force -Path $publishDir | Out-Null

$publishArgs = @("publish", $projectPath, "-c", [string]$config.Configuration, "-r", [string]$config.Runtime, "-o", $publishDir)
if (-not $selfContained) {
    $publishArgs += "--self-contained=false"
}
dotnet @publishArgs

Write-Host "2) Skipping legacy index.html app-base rewrite (Blazor frontend)." -ForegroundColor Cyan

if ($recycleAppPool) {
    Require-ConfigValue $config "AppPoolName"
    $appPoolName = [string]$config.AppPoolName
    Write-Host "2) Stopping IIS app pool via WinRM..." -ForegroundColor Cyan
    try {
        Invoke-WinRmScript -Config $config -PasswordValue $passwordForWinRm -ScriptBlock {
            param($poolName)
            Import-Module WebAdministration
            Stop-WebAppPool -Name $poolName -ErrorAction SilentlyContinue
            Get-Process GameList -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
            Get-Process dotnet -ErrorAction SilentlyContinue | Where-Object { $_.Path -like "*picknplay*" } | Stop-Process -Force -ErrorAction SilentlyContinue
        } -ArgumentList @($appPoolName)
    }
    catch {
        Write-Warning "WinRM stop failed: $($_.Exception.Message)."
    }
}

Write-Host "3) Syncing via WinSCP..." -ForegroundColor Cyan
$openCommand = if ($useStoredSession) {
    "open `"$winScpSessionName`""
}
else {
    $ftpUser = [Uri]::EscapeDataString([string]$config.FtpUser)
    $ftpPassword = [Uri]::EscapeDataString($passwordForSession.Replace("`n", "").Replace("`r", ""))
    $ftpHost = [string]$config.FtpHost
    "open ftp://$ftpUser`:$ftpPassword@$ftpHost"
}

$tempScript = New-TemporaryFile
@(
    "option batch continue"
    "option confirm off"
    $openCommand
    "lcd `"$publishDir`""
    "cd $([string]$config.RemoteDir)"
    "synchronize remote . -delete -filemask=`"|web.config;App_Data/;logs/;GameList.Tests/`""
    "exit"
) | Set-Content -Path $tempScript -Encoding UTF8

& $winScpPath "/ini=nul" "/script=$tempScript"
Remove-Item $tempScript -ErrorAction SilentlyContinue

if ($recycleAppPool) {
    Write-Host "4) Starting IIS app pool via WinRM..." -ForegroundColor Cyan
    try {
        Invoke-WinRmScript -Config $config -PasswordValue $passwordForWinRm -ScriptBlock {
            param($poolName)
            Import-Module WebAdministration
            Start-WebAppPool -Name $poolName
        } -ArgumentList @($appPoolName)
    }
    catch {
        Write-Warning "WinRM start failed: $($_.Exception.Message)."
    }
}

if ($runEfMigrations) {
    Require-ConfigValue $config "RemoteSitePath"
    Write-Host "5) Running EF Core migrations on remote site..." -ForegroundColor Cyan
    try {
        Invoke-WinRmScript -Config $config -PasswordValue $passwordForWinRm -ScriptBlock {
            param($sitePath)
            Set-Location $sitePath
            if (-not (Get-Command dotnet -ErrorAction SilentlyContinue)) {
                throw "dotnet is not available on remote host."
            }

            dotnet ef database update --no-build
        } -ArgumentList @([string]$config.RemoteSitePath)
    }
    catch {
        Write-Warning "WinRM migrations failed: $($_.Exception.Message)."
    }
}

Write-Host "Done." -ForegroundColor Green
