#Requires -RunAsAdministrator
# ============================================================
# Synura Mirth Connect Installer
# Double-Click Done - stunnel + channel, one step
# ============================================================

param(
    [string]$Subdomain,
    [string]$ApiKey,
    [string]$SendingFacility,
    [string]$MirthAdminUser = "admin",
    [string]$MirthAdminPassword,
    [int]$MirthPort = 0,
    [int]$PollIntervalMs = 300000,
    [int]$BatchSize = 1
)

# Force TLS 1.2 — Windows Server 2016 defaults to TLS 1.0 which fails HTTPS downloads
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$ErrorActionPreference = "Continue"
$InstallerDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$BaseDir = Split-Path -Parent $InstallerDir
$SynuraDir = "$env:ProgramFiles\Synura"
$StunnelDir = "$SynuraDir\stunnel"
$LogFile = "$SynuraDir\install.log"
New-Item -ItemType Directory -Path $SynuraDir -Force | Out-Null

function Write-Log {
    param([string]$Message)
    $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    "$ts  $Message" | Tee-Object -FilePath $LogFile -Append
}

function Test-MirthInstall {
    # Check standard Mirth install locations
    $paths = @(
        "$env:ProgramFiles\Mirth Connect",
        "$env:ProgramFiles (x86)\Mirth Connect",
        "${env:ProgramFiles}\NextGen\Connect",
        "C:\Mirth Connect",
        "C:\Program Files\Mirth Connect"
    )
    foreach ($p in $paths) {
        if (Test-Path "$p\conf\mirth.properties") {
            return $p
        }
    }
    return $null
}

function Get-JavaHome {
    param([string]$MirthPath)
    # Try Mirth's bundled JRE first
    $mirthJre = "$MirthPath\jre"
    if (Test-Path "$mirthJre\bin\keytool.exe") { return $mirthJre }
    $mirthJre = "$MirthPath\jre\jre"
    if (Test-Path "$mirthJre\bin\keytool.exe") { return $mirthJre }
    # Try JAVA_HOME
    if ($env:JAVA_HOME -and (Test-Path "$env:JAVA_HOME\bin\keytool.exe")) { return $env:JAVA_HOME }
    # Search common Java install locations
    $searchPaths = @(
        "$env:ProgramFiles\Zulu\*\bin\keytool.exe",
        "$env:ProgramFiles\Java\*\bin\keytool.exe",
        "$env:ProgramFiles\Eclipse Adoptium\*\bin\keytool.exe",
        "$env:ProgramFiles\Amazon Corretto\*\bin\keytool.exe",
        "${env:ProgramFiles(x86)}\Java\*\bin\keytool.exe"
    )
    foreach ($pattern in $searchPaths) {
        $found = Get-ChildItem $pattern -ErrorAction SilentlyContinue | Select-Object -First 1
        if ($found) { return $found.Directory.Parent.FullName }
    }
    return $null
}

function Get-MirthApiBase {
    param([string]$MirthPath)
    # Read mirth.properties for the API port (default 8443)
    $props = Get-Content "$MirthPath\conf\mirth.properties" -ErrorAction SilentlyContinue
    $portLine = $props | Where-Object { $_ -match "^https\.port\s*=" }
    $port = if ($portLine) { ($portLine -split "=")[1].Trim() } else { "8443" }
    return "https://localhost:$port/api"
}

# ============================================================
# INTERACTIVE PROMPTS (if params not provided)
# ============================================================

Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "  Synura Mirth Connect Installer" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""

if (-not $Subdomain) {
    $Subdomain = Read-Host "Your Synura subdomain (e.g. 'questlab')"
}
if (-not $ApiKey) {
    $ApiKey = Read-Host "Your Synura API key"
}
if (-not $SendingFacility) {
    $SendingFacility = Read-Host "Your Sending Facility code (MSH-4, e.g. 'RJ1')"
}
if (-not $MirthAdminPassword) {
    $secPwd = Read-Host "Mirth admin password" -AsSecureString
    $MirthAdminPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
        [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secPwd)
    )
}

# ============================================================
# STEP 1: Detect Mirth
# ============================================================

Write-Log "Step 1: Detecting Mirth Connect installation..."

$MirthPath = Test-MirthInstall
if (-not $MirthPath) {
    Write-Log "ERROR: Mirth Connect not found. Please install Mirth Connect first."
    Write-Host "Mirth Connect not found in standard locations." -ForegroundColor Red
    Write-Host "If installed elsewhere, set MIRTH_HOME environment variable and re-run." -ForegroundColor Yellow
    exit 1
}
Write-Log "  Found Mirth at: $MirthPath"

$JavaHome = Get-JavaHome -MirthPath $MirthPath
if (-not $JavaHome) {
    Write-Log "ERROR: Java not found. Mirth's bundled JRE or JAVA_HOME required."
    exit 1
}
Write-Log "  Found Java at: $JavaHome"

if ($MirthPort -gt 0) {
    $MirthApi = "https://localhost:$MirthPort/api"
} else {
    $MirthApi = Get-MirthApiBase -MirthPath $MirthPath
}
Write-Log "  Mirth API at: $MirthApi"

# ============================================================
# STEP 2: Install stunnel
# ============================================================

Write-Log "Step 2: Installing stunnel..."

# Check for existing stunnel in common locations
$stunnelExe = $null
$stunnelPaths = @(
    "$StunnelDir\stunnel.exe",
    "$StunnelDir\tstunnel.exe",
    "$env:ProgramFiles\stunnel\bin\stunnel.exe",
    "$env:ProgramFiles\stunnel\bin\tstunnel.exe",
    "${env:ProgramFiles(x86)}\stunnel\bin\stunnel.exe",
    "${env:ProgramFiles(x86)}\stunnel\bin\tstunnel.exe",
    "C:\stunnel\bin\stunnel.exe",
    "C:\stunnel\bin\tstunnel.exe"
)
foreach ($p in $stunnelPaths) {
    if (Test-Path $p) {
        $stunnelExe = $p
        $StunnelDir = Split-Path -Parent $p
        break
    }
}

if ($stunnelExe) {
    Write-Log "  stunnel already installed at: $stunnelExe"
} else {
    New-Item -ItemType Directory -Path $StunnelDir -Force | Out-Null

    # Copy bundled stunnel binary
    $bundledStunnel = "$BaseDir\stunnel"
    if (Test-Path "$bundledStunnel\stunnel.exe") {
        Copy-Item "$bundledStunnel\*" -Destination $StunnelDir -Recurse -Force
        $stunnelExe = "$StunnelDir\stunnel.exe"
        Write-Log "  stunnel binary installed"
    } elseif (Test-Path "$bundledStunnel\tstunnel.exe") {
        Copy-Item "$bundledStunnel\*" -Destination $StunnelDir -Recurse -Force
        $stunnelExe = "$StunnelDir\tstunnel.exe"
        Write-Log "  stunnel binary installed"
    } else {
        # Install stunnel from bundled installer
        $bundledInstaller = "$BaseDir\stunnel\stunnel-5.78-win64-installer.exe"
        if (Test-Path $bundledInstaller) {
            Write-Log "  Installing stunnel from bundled installer..."
            Start-Process $bundledInstaller -ArgumentList '/S' -Wait
            Start-Sleep -Seconds 3
            # Find where it installed
            foreach ($p in @("$env:ProgramFiles\stunnel\bin\stunnel.exe", "${env:ProgramFiles(x86)}\stunnel\bin\stunnel.exe")) {
                if (Test-Path $p) { $stunnelExe = $p; $StunnelDir = Split-Path -Parent $p; break }
            }
            if (-not $stunnelExe) {
                Write-Log "ERROR: stunnel installed but binary not found."
                exit 1
            }
            Write-Log "  stunnel installed at: $stunnelExe"
        } else {
            Write-Log "ERROR: Bundled stunnel installer not found at: $bundledInstaller"
            Write-Log "  Please install stunnel manually from https://www.stunnel.org/downloads.html and re-run."
            exit 1
        }
    }
}

# Write stunnel config — find the config directory (stunnel reads from config\ not bin\)
$stunnelConfDir = Split-Path -Parent $StunnelDir
$stunnelConfPath = "$stunnelConfDir\config\stunnel.conf"
if (-not (Test-Path "$stunnelConfDir\config")) {
    # Config might be alongside the binary
    $stunnelConfPath = "$StunnelDir\stunnel.conf"
}

$stunnelConf = @"
[synura]
client = yes
accept = 127.0.0.1:6661
connect = $Subdomain.synura.io:443
sni = $Subdomain.synura.io
"@

$stunnelConf | Out-File -FilePath $stunnelConfPath -Encoding ASCII -Force
Write-Log "  stunnel config written"

# Install stunnel as Windows service
$stunnelExe = "$StunnelDir\stunnel.exe"
$svcExists = Get-Service -Name "SynuraStunnel" -ErrorAction SilentlyContinue
if (-not $svcExists) {
    & $stunnelExe -install -quiet 2>$null
    # If stunnel's built-in service install doesn't work, fall back to NSSM or sc.exe
    if (-not (Get-Service -Name "stunnel" -ErrorAction SilentlyContinue)) {
        & sc.exe create SynuraStunnel binPath= "`"$stunnelExe`" -service" start= auto 2>$null
    }
    Write-Log "  stunnel service installed"
} else {
    Write-Log "  stunnel service already exists"
}

# Start the service
Start-Service -Name "SynuraStunnel" -ErrorAction SilentlyContinue
Start-Service -Name "stunnel" -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
Write-Log "  stunnel service started"

# ============================================================
# STEP 3: Import TLS certs into Mirth's Java truststore
# ============================================================

Write-Log "Step 3: Importing TLS certificates..."

$keytool = "$JavaHome\bin\keytool.exe"
$truststore = "$MirthPath\conf\truststore.jks"
if (-not (Test-Path $truststore)) {
    # Fall back to Java's default cacerts
    $truststore = "$JavaHome\lib\security\cacerts"
}
$storePass = "changeit"  # Default Java truststore password

# Check if Let's Encrypt certs are already trusted
$certCheck = & $keytool -list -keystore $truststore -storepass $storePass -alias "isrg-root-x1" 2>&1 | Out-String
if ($certCheck -match "isrg-root-x1") {
    Write-Log "  Let's Encrypt root cert already in truststore - skipping"
} else {
    $certFile = "$BaseDir\certs\isrg-root-x1.pem"
    if (Test-Path $certFile) {
        $importResult = & $keytool -importcert -noprompt -trustcacerts -alias "isrg-root-x1" `
            -file $certFile -keystore $truststore -storepass $storePass 2>&1 | Out-String
        Write-Log "  ISRG Root X1 certificate imported"
    } else {
        Write-Log "  WARNING: Root cert not bundled - Let's Encrypt may already be in the default truststore"
    }
}

# ============================================================
# STEP 4: Deploy Mirth channel via REST API
# ============================================================

Write-Log "Step 4: Deploying Synura polling channel..."

# Build auth header
$authPair = "${MirthAdminUser}:${MirthAdminPassword}"
$authBytes = [System.Text.Encoding]::ASCII.GetBytes($authPair)
$authB64 = [Convert]::ToBase64String($authBytes)
$headers = @{
    "Authorization"    = "Basic $authB64"
    "Content-Type"     = "application/xml"
    "X-Requested-With" = "XMLHttpRequest"
}

# Ignore self-signed Mirth cert
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
if (-not ([System.Management.Automation.PSTypeName]'TrustAllCertsPolicy').Type) {
    Add-Type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
    public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem) { return true; }
}
"@
}
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

# Check if channel already exists
$channelName = "Synura - Mailbox Poller"
try {
    $reqCheck = [Net.HttpWebRequest]::Create("$MirthApi/channels")
    $reqCheck.Headers.Add("X-Requested-With", "XMLHttpRequest")
    $reqCheck.Headers.Add("Authorization", "Basic $authB64")
    $reqCheck.Method = "GET"
    $respCheck = $reqCheck.GetResponse()
    $readerCheck = New-Object IO.StreamReader($respCheck.GetResponseStream())
    $channelsXml = $readerCheck.ReadToEnd()
    $respCheck.Close()
    if ($channelsXml -match [regex]::Escape($channelName)) {
        # Extract the channel ID from the XML
        if ($channelsXml -match "<id>([^<]+)</id>\s*<nextMetaDataId>.*?<name>$([regex]::Escape($channelName))</name>") {
            $channelId = $Matches[1]
        }
        Write-Log "  Channel '$channelName' already exists (id: $channelId) - will update"
    }
} catch {
    Write-Log "  WARNING: Could not check existing channels: $_"
}

# Load channel XML template and substitute values
$newChannelId = [guid]::NewGuid().ToString()
$channelXml = Get-Content "$BaseDir\channel\synura-poller-3.8.xml" -Raw
$channelXml = $channelXml -replace '{{CHANNEL_ID}}', $newChannelId
$channelXml = $channelXml -replace '{{SUBDOMAIN}}', $Subdomain
$channelXml = $channelXml -replace '{{API_KEY}}', $ApiKey
$channelXml = $channelXml -replace '{{SENDING_FACILITY}}', $SendingFacility
$channelXml = $channelXml -replace '{{SENDING_APPLICATION}}', "SYNURA_POLLER"
$channelXml = $channelXml -replace '{{POLL_INTERVAL_MS}}', $PollIntervalMs
$channelXml = $channelXml -replace '{{POLL_INTERVAL_MIN}}', ([math]::Round($PollIntervalMs / 60000))
$channelXml = $channelXml -replace '{{BATCH_SIZE}}', $BatchSize
$channelXml = $channelXml -replace '{{STUNNEL_PORT}}', '6661'

try {
    # Create channel using raw HttpWebRequest (Invoke-RestMethod fails on Windows Server 2016 TLS)
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($channelXml)
    $req = [Net.HttpWebRequest]::Create("$MirthApi/channels")
    $req.Headers.Add("X-Requested-With", "XMLHttpRequest")
    $req.Headers.Add("Authorization", "Basic $authB64")
    $req.Method = "POST"
    $req.ContentType = "application/xml"
    $req.ContentLength = $bytes.Length
    $reqStream = $req.GetRequestStream()
    $reqStream.Write($bytes, 0, $bytes.Length)
    $reqStream.Close()
    $resp = $req.GetResponse()
    $reader = New-Object IO.StreamReader($resp.GetResponseStream())
    $createResult = $reader.ReadToEnd()
    $resp.Close()
    Write-Log "  Channel created (id: $newChannelId)"

    # Deploy the channel
    $req2 = [Net.HttpWebRequest]::Create("$MirthApi/channels/$newChannelId/_deploy")
    $req2.Headers.Add("X-Requested-With", "XMLHttpRequest")
    $req2.Headers.Add("Authorization", "Basic $authB64")
    $req2.Method = "POST"
    $req2.ContentLength = 0
    $resp2 = $req2.GetResponse()
    $resp2.Close()
    Write-Log "  Channel deployed and started"

} catch {
    Write-Log "ERROR: Failed to deploy channel: $_"
    Write-Host "Channel deployment failed. Check Mirth is running and credentials are correct." -ForegroundColor Red
    exit 1
}

# ============================================================
# STEP 5: Verify connection
# ============================================================

Write-Log "Step 5: Verifying connection..."

# Wait for first poll cycle
Write-Host "Waiting for first poll..." -ForegroundColor Yellow
Start-Sleep -Seconds 10

try {
    $req3 = [Net.HttpWebRequest]::Create("$MirthApi/channels/statuses")
    $req3.Headers.Add("X-Requested-With", "XMLHttpRequest")
    $req3.Headers.Add("Authorization", "Basic $authB64")
    $resp3 = $req3.GetResponse()
    $reader3 = New-Object IO.StreamReader($resp3.GetResponseStream())
    $statusXml = $reader3.ReadToEnd()
    $resp3.Close()

    if ($statusXml -match "<state>STARTED</state>") {
        Write-Log "  Channel is STARTED"
        Write-Host ""
        Write-Host "========================================" -ForegroundColor Green
        Write-Host "  SUCCESS - Synura is connected!" -ForegroundColor Green
        Write-Host "========================================" -ForegroundColor Green
        Write-Host ""
        Write-Host "  Endpoint: $Subdomain.synura.io" -ForegroundColor White
        Write-Host "  Polling:  every $([math]::Round($PollIntervalMs / 60000)) minutes" -ForegroundColor White
        Write-Host "  Channel:  $channelName" -ForegroundColor White
        Write-Host ""
        Write-Host "  Messages will appear in Mirth's dashboard." -ForegroundColor White
        Write-Host ""
    } else {
        Write-Log "  WARNING: Channel may not be started. Check Mirth dashboard."
        Write-Host "Channel deployed but status unclear. Check Mirth dashboard." -ForegroundColor Yellow
    }
} catch {
    Write-Log "  WARNING: Could not verify channel status: $_"
    Write-Host "Could not verify - check Mirth dashboard manually." -ForegroundColor Yellow
}

Write-Log "Installation complete."
