<#
.SYNOPSIS
    ROA2WEB SSH Tunnel Manager for Windows (equivalent to ssh-tunnel.sh)

.DESCRIPTION
    Manages SSH tunnels to Oracle servers on Windows.
    Reads configuration from backend/ssh-tunnels.json
    SSH keys should be in backend/secrets/{id}.ssh_key

    Requirements:
    - Windows 10/11 with OpenSSH Client (installed by default)
    - SSH private keys in backend/secrets/

.PARAMETER Action
    start   - Start all configured SSH tunnels
    stop    - Stop all SSH tunnels
    status  - Show status of all tunnels
    restart - Restart all tunnels
    help    - Show this help

.EXAMPLE
    .\ssh-tunnel.ps1 start
    Start all configured tunnels

.EXAMPLE
    .\ssh-tunnel.ps1 status
    Check tunnel status

.NOTES
    Author: ROA2WEB Team
    Version: 1.0
#>

param(
    [Parameter(Position=0)]
    [ValidateSet("start", "stop", "status", "restart", "help", "")]
    [string]$Action = "help"
)

$ErrorActionPreference = "Stop"

# =============================================================================
# CONFIGURATION
# =============================================================================

# Detect paths - script can run from deployment/windows/scripts or project root
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path

# Try to find backend relative to script location
$PossiblePaths = @(
    (Join-Path $ScriptDir "..\..\..\backend"),           # From deployment/windows/scripts
    (Join-Path $ScriptDir "backend"),                     # From project root
    "C:\inetpub\wwwroot\roa2web\backend"                  # Production path
)

$BackendPath = $null
foreach ($path in $PossiblePaths) {
    $resolved = [System.IO.Path]::GetFullPath($path)
    if (Test-Path $resolved) {
        $BackendPath = $resolved
        break
    }
}

if (-not $BackendPath) {
    Write-Host "[ERROR] Cannot find backend directory" -ForegroundColor Red
    Write-Host "Tried: $($PossiblePaths -join ', ')" -ForegroundColor Yellow
    exit 1
}

$Config = @{
    BackendPath = $BackendPath
    TunnelsFile = Join-Path $BackendPath "ssh-tunnels.json"
    SecretsPath = Join-Path $BackendPath "secrets"
    PidDir = Join-Path $env:TEMP "roa_tunnels"
}

# Create PID directory
if (-not (Test-Path $Config.PidDir)) {
    New-Item -ItemType Directory -Path $Config.PidDir -Force | Out-Null
}

# =============================================================================
# HELPER FUNCTIONS
# =============================================================================

function Write-ColorLine {
    param([string]$Text, [string]$Color = "White")
    Write-Host $Text -ForegroundColor $Color
}

function Get-PidFile {
    param([string]$ServerId)
    return Join-Path $Config.PidDir "tunnel_$ServerId.pid"
}

function Test-TunnelRunning {
    param([string]$ServerId)

    $pidFile = Get-PidFile $ServerId
    if (Test-Path $pidFile) {
        $tunnelPid = Get-Content $pidFile -ErrorAction SilentlyContinue
        if ($tunnelPid) {
            $process = Get-Process -Id $tunnelPid -ErrorAction SilentlyContinue
            if ($process -and $process.ProcessName -eq "ssh") {
                return $true
            }
        }
        # Clean up stale PID file
        Remove-Item $pidFile -Force -ErrorAction SilentlyContinue
    }
    return $false
}

function Test-PortOpen {
    param([int]$Port)

    try {
        $tcpClient = New-Object System.Net.Sockets.TcpClient
        $tcpClient.Connect("127.0.0.1", $Port)
        $tcpClient.Close()
        return $true
    } catch {
        return $false
    }
}

function Read-TunnelConfig {
    if (-not (Test-Path $Config.TunnelsFile)) {
        Write-ColorLine "[WARNING] ssh-tunnels.json not found at: $($Config.TunnelsFile)" "Yellow"
        return @()
    }

    try {
        $json = Get-Content $Config.TunnelsFile -Raw | ConvertFrom-Json
        return $json
    } catch {
        Write-ColorLine "[ERROR] Failed to parse ssh-tunnels.json: $_" "Red"
        return @()
    }
}

# =============================================================================
# TUNNEL MANAGEMENT
# =============================================================================

function Start-SingleTunnel {
    param([PSCustomObject]$Tunnel)

    $id = $Tunnel.id
    $name = $Tunnel.name
    $localPort = $Tunnel.local_port
    $sshHost = $Tunnel.ssh_host
    $sshPort = if ($Tunnel.ssh_port) { $Tunnel.ssh_port } else { 22 }
    $sshUser = if ($Tunnel.ssh_user) { $Tunnel.ssh_user } else { "root" }
    $oracleHost = if ($Tunnel.oracle_host) { $Tunnel.oracle_host } else { "127.0.0.1" }
    $oraclePort = if ($Tunnel.oracle_port) { $Tunnel.oracle_port } else { 1521 }

    Write-ColorLine "[$id] $name" "Cyan"
    Write-Host "        Tunnel: localhost:$localPort -> ${oracleHost}:$oraclePort"
    Write-Host "        Via:    ${sshUser}@${sshHost}:$sshPort"

    # Skip if no SSH host configured
    if (-not $sshHost) {
        Write-ColorLine "        [SKIP] No ssh_host configured (direct connection)" "Yellow"
        return $true
    }

    # Check if already running
    if (Test-TunnelRunning $id) {
        $pidFile = Get-PidFile $id
        $tunnelPid = Get-Content $pidFile
        Write-ColorLine "        [RUNNING] Already running (PID: $tunnelPid)" "Yellow"
        return $true
    }

    # Find SSH key
    $sshKeyPath = $null
    $possibleKeys = @(
        (Join-Path $Config.SecretsPath "$id.ssh_key"),
        (Join-Path $Config.SecretsPath "${id}_rsa"),
        (Join-Path $Config.SecretsPath "id_rsa")
    )

    # Also check if key path is specified in config
    if ($Tunnel.ssh_key) {
        $configKey = $Tunnel.ssh_key
        if (-not [System.IO.Path]::IsPathRooted($configKey)) {
            $configKey = Join-Path $Config.BackendPath $configKey
        }
        $possibleKeys = @($configKey) + $possibleKeys
    }

    foreach ($keyPath in $possibleKeys) {
        if (Test-Path $keyPath) {
            $sshKeyPath = $keyPath
            break
        }
    }

    # Check for password file
    $sshPassPath = Join-Path $Config.SecretsPath "$id.ssh_pass"
    $hasPassword = Test-Path $sshPassPath
    $sshPassword = $null
    if ($hasPassword) {
        $sshPassword = (Get-Content $sshPassPath -Raw).Trim()
    }

    if (-not $sshKeyPath -and -not $hasPassword) {
        Write-ColorLine "        [ERROR] No SSH key or password found" "Red"
        Write-ColorLine "        Expected: $($Config.SecretsPath)\$id.ssh_key" "Yellow"
        Write-ColorLine "             or: $($Config.SecretsPath)\$id.ssh_pass" "Yellow"
        return $false
    }

    # Build SSH command based on auth method
    # Note: Windows ssh.exe doesn't support -f (background), so we use Start-Process
    $sshArgs = @(
        "-N",                                           # No remote command
        "-L", "${localPort}:${oracleHost}:${oraclePort}",  # Port forwarding
        "-p", $sshPort,                                 # SSH port
        "-o", "StrictHostKeyChecking=no",               # Skip host key check
        "-o", "ServerAliveInterval=60",                 # Keep-alive
        "-o", "ServerAliveCountMax=3",                  # Retry count
        "-o", "ExitOnForwardFailure=yes"                # Fail if port forward fails
    )

    if ($sshKeyPath) {
        $sshArgs += @("-i", "`"$sshKeyPath`"")
        Write-ColorLine "        Using SSH key authentication" "Gray"
    }

    $sshArgs += @("${sshUser}@${sshHost}")

    try {
        $process = $null

        if ($hasPassword -and -not $sshKeyPath) {
            # Password authentication - use plink if available, otherwise show instructions
            $plinkPath = Get-Command plink.exe -ErrorAction SilentlyContinue

            if ($plinkPath) {
                # Use plink for password auth
                Write-ColorLine "        Using password authentication (plink)" "Gray"
                $plinkArgs = @(
                    "-ssh",
                    "-batch",  # Non-interactive mode (skip "Press Return" prompt)
                    "-N",
                    "-L", "${localPort}:${oracleHost}:${oraclePort}",
                    "-P", $sshPort,
                    "-pw", $sshPassword
                )
                # Add hostkey if specified (required for non-interactive/batch mode)
                if ($Tunnel.ssh_hostkey) {
                    $plinkArgs += @("-hostkey", $Tunnel.ssh_hostkey)
                }
                $plinkArgs += "${sshUser}@${sshHost}"

                $process = Start-Process -FilePath "plink.exe" `
                    -ArgumentList $plinkArgs `
                    -WindowStyle Hidden `
                    -PassThru
            } else {
                # Try with sshpass if available (e.g., via Git Bash or WSL)
                $sshpassPath = Get-Command sshpass -ErrorAction SilentlyContinue

                if ($sshpassPath) {
                    Write-ColorLine "        Using password authentication (sshpass)" "Gray"
                    $process = Start-Process -FilePath "sshpass" `
                        -ArgumentList @("-p", $sshPassword, "ssh") + $sshArgs `
                        -WindowStyle Hidden `
                        -PassThru
                } else {
                    Write-ColorLine "        [ERROR] Password auth requires plink.exe (PuTTY) or sshpass" "Red"
                    Write-ColorLine "        Install PuTTY: choco install putty" "Yellow"
                    Write-ColorLine "        Or create SSH key: ssh-keygen -t rsa -f secrets\$id.ssh_key" "Yellow"
                    Write-ColorLine "        Then copy public key to server: ssh-copy-id -p $sshPort ${sshUser}@${sshHost}" "Yellow"
                    return $false
                }
            }
        } else {
            # SSH key authentication
            $process = Start-Process -FilePath "ssh.exe" `
                -ArgumentList $sshArgs `
                -WindowStyle Hidden `
                -PassThru
        }

        # Wait a moment for connection
        Start-Sleep -Seconds 2

        # Check if process is still running
        $process.Refresh()
        if ($process.HasExited) {
            Write-ColorLine "        [ERROR] SSH tunnel exited immediately (code: $($process.ExitCode))" "Red"
            return $false
        }

        # Save PID
        $pidFile = Get-PidFile $id
        $process.Id | Out-File -FilePath $pidFile -NoNewline

        Write-ColorLine "        [OK] Started (PID: $($process.Id))" "Green"

        # Test port connectivity
        if (Test-PortOpen $localPort) {
            Write-ColorLine "        [OK] Port $localPort accessible" "Green"
        } else {
            Write-ColorLine "        [WARN] Port $localPort not responding yet" "Yellow"
        }

        return $true
    } catch {
        Write-ColorLine "        [ERROR] Failed to start tunnel: $_" "Red"
        return $false
    }
}

function Stop-SingleTunnel {
    param([PSCustomObject]$Tunnel)

    $id = $Tunnel.id
    $name = $Tunnel.name

    Write-ColorLine "[$id] $name" "Cyan"

    $pidFile = Get-PidFile $id
    if (Test-Path $pidFile) {
        $tunnelPid = Get-Content $pidFile -ErrorAction SilentlyContinue
        if ($tunnelPid) {
            $process = Get-Process -Id $tunnelPid -ErrorAction SilentlyContinue
            if ($process) {
                Stop-Process -Id $tunnelPid -Force -ErrorAction SilentlyContinue
                Write-ColorLine "        [OK] Stopped (was PID: $tunnelPid)" "Green"
            } else {
                Write-ColorLine "        [WARN] Was not running" "Yellow"
            }
        }
        Remove-Item $pidFile -Force -ErrorAction SilentlyContinue
    } else {
        Write-ColorLine "        [WARN] Was not running" "Yellow"
    }
}

function Get-SingleTunnelStatus {
    param([PSCustomObject]$Tunnel)

    $id = $Tunnel.id
    $name = $Tunnel.name
    $localPort = $Tunnel.local_port
    $sshHost = $Tunnel.ssh_host

    # Skip tunnels without SSH host (direct connection)
    if (-not $sshHost) {
        Write-Host "  " -NoNewline
        Write-ColorLine "[$id] $name" "Cyan"
        Write-ColorLine "     Direct connection (no tunnel needed)" "Gray"
        return
    }

    if (Test-TunnelRunning $id) {
        $pidFile = Get-PidFile $id
        $tunnelPid = Get-Content $pidFile

        Write-Host "  " -NoNewline
        Write-Host "[OK]" -ForegroundColor Green -NoNewline
        Write-ColorLine " [$id] $name" "Cyan"
        Write-Host "     localhost:$localPort (PID: $tunnelPid)"

        if (Test-PortOpen $localPort) {
            Write-ColorLine "     Port accessible" "Green"
        } else {
            Write-ColorLine "     Port not responding" "Yellow"
        }
    } else {
        Write-Host "  " -NoNewline
        Write-Host "[--]" -ForegroundColor Red -NoNewline
        Write-ColorLine " [$id] $name" "Cyan"
        Write-Host "     localhost:$localPort (stopped)"
    }
}

# =============================================================================
# MAIN COMMANDS
# =============================================================================

function Show-Header {
    Write-Host ""
    Write-ColorLine "============================================" "Blue"
    Write-ColorLine "  ROA2WEB SSH Tunnel Manager (Windows)" "Blue"
    Write-ColorLine "============================================" "Blue"
    Write-Host ""
}

function Invoke-Start {
    Show-Header

    $tunnels = Read-TunnelConfig
    if ($tunnels.Count -eq 0) {
        Write-ColorLine "[WARNING] No tunnels configured" "Yellow"
        return
    }

    Write-ColorLine "Starting $($tunnels.Count) tunnel(s)..." "Blue"
    Write-Host ""

    $failed = 0
    foreach ($tunnel in $tunnels) {
        if (-not (Start-SingleTunnel $tunnel)) {
            $failed++
        }
        Write-Host ""
    }

    if ($failed -gt 0) {
        Write-ColorLine "[WARNING] $failed tunnel(s) failed to start" "Yellow"
    } else {
        Write-ColorLine "[OK] All tunnels started successfully" "Green"
    }
}

function Invoke-Stop {
    Show-Header

    $tunnels = Read-TunnelConfig

    Write-ColorLine "Stopping tunnels..." "Blue"
    Write-Host ""

    foreach ($tunnel in $tunnels) {
        Stop-SingleTunnel $tunnel
    }

    Write-Host ""
    Write-ColorLine "[OK] All tunnels stopped" "Green"
}

function Invoke-Status {
    Show-Header

    $tunnels = Read-TunnelConfig

    Write-ColorLine "Tunnel Status:" "Blue"
    Write-Host "--------------------------------------------"

    foreach ($tunnel in $tunnels) {
        Get-SingleTunnelStatus $tunnel
        Write-Host ""
    }
}

function Invoke-Restart {
    Invoke-Stop
    Start-Sleep -Seconds 2
    Invoke-Start
}

function Show-Help {
    Show-Header

    Write-Host "Usage: .\ssh-tunnel.ps1 <action>"
    Write-Host ""
    Write-ColorLine "Actions:" "Blue"
    Write-Host "  start    - Start all configured SSH tunnels"
    Write-Host "  stop     - Stop all SSH tunnels"
    Write-Host "  status   - Show status of all tunnels"
    Write-Host "  restart  - Restart all tunnels"
    Write-Host "  help     - Show this help"
    Write-Host ""
    Write-ColorLine "Configuration:" "Blue"
    Write-Host "  Tunnels file: $($Config.TunnelsFile)"
    Write-Host "  Secrets dir:  $($Config.SecretsPath)"
    Write-Host ""
    Write-ColorLine "SSH Key Setup:" "Blue"
    Write-Host "  1. Copy your SSH private key to: backend\secrets\{server_id}.ssh_key"
    Write-Host "  2. Ensure the key has correct permissions (readable only by you)"
    Write-Host ""
    Write-ColorLine "Example ssh-tunnels.json:" "Blue"
    Write-Host '  [{"id": "vending", "name": "Vending Master", "local_port": 1521,'
    Write-Host '    "ssh_host": "79.119.86.134", "ssh_port": 22122, "ssh_user": "romfast",'
    Write-Host '    "oracle_host": "127.0.0.1", "oracle_port": 1521}]'
    Write-Host ""
}

# =============================================================================
# MAIN
# =============================================================================

# Check OpenSSH availability
$sshPath = Get-Command ssh.exe -ErrorAction SilentlyContinue
if (-not $sshPath) {
    Write-ColorLine "[ERROR] OpenSSH not found!" "Red"
    Write-Host ""
    Write-Host "OpenSSH Client is required. Install it via:"
    Write-Host "  Settings -> Apps -> Optional Features -> Add a feature -> OpenSSH Client"
    Write-Host ""
    Write-Host "Or via PowerShell (Admin):"
    Write-Host "  Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0"
    exit 1
}

switch ($Action) {
    "start"   { Invoke-Start }
    "stop"    { Invoke-Stop }
    "status"  { Invoke-Status }
    "restart" { Invoke-Restart }
    "help"    { Show-Help }
    default   { Show-Help }
}
