#!/usr/bin/pwsh

param(
    [Alias('h')][switch]$ShowHelp,
    [Alias('m')][string]$HomeDir="",
    [Alias('p')][string]$ProxyUrl,
    [Alias('s')][string]$ServerId,
    [Alias('t')][string]$TelemetryPort,
    [Alias('l')][string]$TelemetryLogLevel,
    [Alias('g')][string]$TracingPort,
    [Alias('x')][switch]$SkipServicePrompt
)

$ErrorActionPreference = "Stop"

If ($ShowHelp) {
    Write-Host "DESCRIPTION: Install Anypoint Monitoring OpenTelemetry Collector"
    Write-Host "Usage: .\$($MyInvocation.MyCommand) [options]"
    Write-Host ""
    Write-Host "options:"
    Write-Host "-h          show help and exit"
    Write-Host "-m [DIR]    set home directory - defaults to MULE_HOME"
    Write-Host "-p [URL]    set HTTP/HTTPS proxy server URL"
    Write-Host "            Example: https://user:password@https-server:1080"
    Write-Host "-t [PORT]   set internal telemetry port - defaults to 8888"
    Write-Host "-l [LEVEL]  set internal telemetry logs level (info, debug, warn, error, off) - defaults to off"
    Write-Host "-g [PORT]   set tracing port - defaults disabled"
    Write-Host "-x          skip service installation prompt"
    Write-Host ""
    exit 0
}

if (-not $HomeDir -or -not (Test-Path $HomeDir)) {
    if ($env:MULE_HOME -and (Test-Path $env:MULE_HOME)) {
        $HomeDir = $env:MULE_HOME
    } else {
        $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
        $HomeDir = (Resolve-Path -Path (Join-Path -Path $scriptDir -ChildPath "..\..")).Path
        Write-Host "Determined the MULE_HOME"
    }
}

# Validate that serviceHelper.ps1 exists
$serviceHelperPath = Join-Path -Path $HomeDir -ChildPath "am\bin\tools\serviceHelper.ps1"

if (-not (Test-Path $serviceHelperPath)) {
    Write-Host "Error: Invalid MULE_HOME directory. Could not find $serviceHelperPath"
    Write-Host "Please specify a valid MULE_HOME directory with the -m parameter."
    exit 1
}

# Source the serviceHelper.ps1
. $serviceHelperPath

# Java program running on Windows treats paths differently when the trailing character is \
# If MULE_HOME env is not set, HomeDir is autodetected and things are merry.
# But if HomeDir is supplied as an option, with a valid path but ending in \, then AgentConfiguratorApp complains:
# ERROR: The provided Mule Home doesn't exist, please check if the provided directory is correct.
# Hence the TrimEnd
$HomeDir = $HomeDir.TrimEnd('\','/')
$env:MULE_HOME = $HomeDir

Log-Info "MULE_HOME is set to $env:MULE_HOME"

# Set AM_HOME based on the determined HomeDir
$env:AM_HOME = Join-Path -Path $HomeDir -ChildPath "am"
$env:AM_CONFIG = Join-Path -Path $env:AM_HOME -ChildPath "config"
$env:AM_CONFIG_PIPELINES = Join-Path -Path $env:AM_HOME -ChildPath "config\pipelines"
$env:AM_LOGS = Join-Path -Path $env:AM_HOME -ChildPath "logs"

$env:OTEL_COLLECTOR_EXE = Join-Path $env:AM_HOME "otel-collector\windows\otel-collector.exe"
$env:OTEL_COLLECTOR_DATA = Join-Path -Path $HomeDir -ChildPath ".mule\.am\otel-collector\data"
$env:OTEL_COLLECTOR_LOGS = Join-Path -Path $env:AM_HOME -ChildPath "logs"

# Check for system-wide GOMEMLIMIT and set if not present
$currentGoMemLimit = [System.Environment]::GetEnvironmentVariable("GOMEMLIMIT", [System.EnvironmentVariableTarget]::Machine)

if ($currentGoMemLimit) {
    $env:GOMEMLIMIT = $currentGoMemLimit
    Log-Info "System-wide GOMEMLIMIT is already set to $currentGoMemLimit. Reusing it."
} else {
    # Compute GOMEMLIMIT based on OTEL_PROCESSOR_MEMORY_LIMIT_MB or default to 40
    if ($env:OTEL_PROCESSOR_MEMORY_LIMIT_MB) {
        $memLimit = [int]$env:OTEL_PROCESSOR_MEMORY_LIMIT_MB
    } else {
        # Inspired by otel-pipeline-common.yml
        # processors:
        #  memory_limiter:
        #    check_interval: 1s
        #    limit_mib: 40
        $memLimit = 40
    }
    $goMemLimit = [math]::Floor($memLimit * 0.67)
    $gomemlimitValue = "${goMemLimit}MiB"

    # Set as system-wide (machine) environment variable
    [System.Environment]::SetEnvironmentVariable("GOMEMLIMIT", $gomemlimitValue, [System.EnvironmentVariableTarget]::Machine)
    $env:GOMEMLIMIT = $gomemlimitValue
    Log-Info "System-wide GOMEMLIMIT set to $gomemlimitValue"
}

# Ensure OTEL_COLLECTOR_DATA directory exists
if (-not (Test-Path $env:OTEL_COLLECTOR_DATA)) {
    New-Item -Path $env:OTEL_COLLECTOR_DATA -ItemType Directory -Force | Out-Null
}

# Ensure OTEL_COLLECTOR_LOGS directory exists
if (-not (Test-Path $env:OTEL_COLLECTOR_LOGS)) {
    New-Item -Path $env:OTEL_COLLECTOR_LOGS -ItemType Directory -Force | Out-Null
}

$modeFile = Join-Path $env:AM_HOME "install.mode"

Log-Info "AM_HOME is set to $env:AM_HOME"

# Initialize configuration files
function Initialize-ConfigFiles {
    # Remove existing pipeline configuration files
    Log-Info "Removing existing pipeline configuration files under $env:AM_CONFIG_PIPELINES"
    Remove-Item $env:AM_CONFIG_PIPELINES\*.yml -Force -ErrorAction SilentlyContinue

    # Check if ServerId is set
    if (-not $ServerId) {
        # Remove server.id if ServerId is not set
        Remove-Item $env:AM_CONFIG\server.id -Force -ErrorAction SilentlyContinue
    } else {
        # Create and set server.id with ServerId
        [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "server.id"), $ServerId)
        Log-Info "The server id is set to $ServerId in $env:AM_CONFIG\server.id"
    }
}

# Configure proxy URL
function Invoke-ConfigureProxyUrl {
    # Remove existing proxy URL file
    Remove-Item $env:AM_CONFIG\proxy.url -Force -ErrorAction SilentlyContinue

    # Determine the proxy value
    $proxyValue = if ($ProxyUrl) { $ProxyUrl } else { $Env:HTTPS_PROXY }

    # Check if the proxy value is set
    if ($proxyValue) {
        [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "proxy.url"), $proxyValue)
        Log-Info "The proxy value is set to $proxyValue in $env:AM_CONFIG\proxy.url"
    }
}

# Test if a TCP port is free (true) or in use (false)
function Check-PortFree {
    param([int]$Port)
    $isFree = $true
    try {
        if (Get-Command Get-NetTCPConnection -ErrorAction SilentlyContinue) {
            $conns = Get-NetTCPConnection -LocalPort $Port -ErrorAction Stop | Where-Object { $_.State -eq 'Listen' }
            if ($conns) { $isFree = $false }
        }
    } catch {}
    if ($isFree) {
        try {
            $pattern = ":$Port\s"
            $net = netstat -ano | Select-String -Pattern $pattern -ErrorAction SilentlyContinue
            if ($net) { $isFree = $false }
        } catch {}
    }
    return $isFree
}

# Configure telemetry port
function Invoke-ConfigureTelemetryPort {
    # Remove existing telemetry port file
    Remove-Item $env:AM_CONFIG\telemetry.port -Force -ErrorAction SilentlyContinue

    # Check if the telemetry port value is set
    if ($TelemetryPort) {
        $tp = $TelemetryPort.Trim()
        $port = 0
        if ([int]::TryParse($tp, [ref]$port) -and $port -ge 1024 -and $port -le 65535) {
            if (Check-PortFree -Port $port) {
                [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "telemetry.port"), "$port")
                Log-Info "The telemetry port value is set to $port in $env:AM_CONFIG\telemetry.port"
            } else {
                Log-Info "ERROR: Telemetry port $port is already in use. Please re-run install with -t <free port>."
                exit 1
            }
        } else {
            Log-Info "ERROR: Invalid telemetry port '$TelemetryPort'. Must be an integer between 1024 and 65535."
            exit 1
        }
    } else {
        if (Check-PortFree -Port 8888) {
            [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "telemetry.port"), "8888")
            Log-Info "The telemetry port value is set to default 8888 in $env:AM_CONFIG\telemetry.port"
        } else {
            Log-Info "ERROR: Default telemetry port 8888 is in use. Please re-run install with -t <free port>."
            exit 1
        }
    }
}

# Configure telemetry log level
function Invoke-ConfigureTelemetryLogLevel {
    # Remove existing files
    Remove-Item $env:AM_CONFIG\telemetry.logs.level -Force -ErrorAction SilentlyContinue
    Remove-Item $env:AM_CONFIG\telemetry.logs.output.path -Force -ErrorAction SilentlyContinue
    Remove-Item $env:AM_CONFIG\telemetry.logs.error.path -Force -ErrorAction SilentlyContinue
    Remove-Item $env:AM_LOGS\otel-collector.log -Force -ErrorAction SilentlyContinue
    Remove-Item $env:AM_LOGS\otel-collector.err -Force -ErrorAction SilentlyContinue
    # Write telemetry logs level: use provided value, else default to OFF
    if ($TelemetryLogLevel) {
        $TelemetryLogLevel = $TelemetryLogLevel.ToUpper()
        if ($TelemetryLogLevel -eq "INFO" -or $TelemetryLogLevel -eq "DEBUG" -or $TelemetryLogLevel -eq "WARN" -or $TelemetryLogLevel -eq "ERROR") {
            [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "telemetry.logs.level"), $TelemetryLogLevel)
            [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "telemetry.logs.output.path"), "$env:AM_LOGS\otel-collector.log")
            [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "telemetry.logs.error.path"), "$env:AM_LOGS\otel-collector.err")
            Log-Info "The telemetry logs level value is set to $TelemetryLogLevel in $env:AM_CONFIG\telemetry.logs.level"
        } elseif ($TelemetryLogLevel -eq "OFF") {
            [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "telemetry.logs.level"), "INFO")
            [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "telemetry.logs.output.path"), "nul")
            [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "telemetry.logs.error.path"), "nul")
            Log-Info "The telemetry logs level value is set to OFF in $env:AM_CONFIG\telemetry.logs.level"
        } else {
            Log-Info "ERROR: Invalid telemetry logs level '$TelemetryLogLevel'. Must be INFO, DEBUG, WARN, ERROR or OFF."
            exit 1
        }
    } else {
        [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "telemetry.logs.level"), "ERROR")
        [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "telemetry.logs.output.path"), "nul")
        [System.IO.File]::WriteAllText((Join-Path $env:AM_CONFIG "telemetry.logs.error.path"), "nul")
        Log-Info "The telemetry logs level value is set to OFF in $env:AM_CONFIG\telemetry.logs.level"
    }
}

# Configure tracing port
function Invoke-ConfigureTracingPort {
    # Remove existing tracing files
    Remove-Item $env:AM_CONFIG_PIPELINES\_trace.yml -Force -ErrorAction SilentlyContinue
    # Write tracing port: use provided value, else default disabled
    if ($TracingPort) {
        $tp = $TracingPort.Trim()
        $port = 0
        if ([int]::TryParse($tp, [ref]$port) -and $port -ge 1024 -and $port -le 65535) {
            if (Check-PortFree -Port $port) {
                Copy-Item -Path (Join-Path $env:AM_CONFIG "trace-pipeline-template.yml") -Destination (Join-Path $env:AM_CONFIG_PIPELINES "_trace.yml")
                (Get-Content -Path (Join-Path $env:AM_CONFIG_PIPELINES "_trace.yml") -Raw) -replace '\$tracingPort\$', $port | Set-Content (Join-Path $env:AM_CONFIG_PIPELINES "_trace.yml")
                Log-Info "Set the tracing port to $port"
            } else {
                Log-Info "ERROR: Tracing port $port is already in use. Please re-run install with -g <free port>."
                exit 1
            }
        } else {
            Log-Info "ERROR: Invalid tracing port '$TracingPort'. Must be an integer between 1024 and 65535."
            exit 1
        }
    }
}

# Configure agent
function Invoke-ConfigureAgent {
    Log-Info "Configuring agent..."

    # Check if Java is available
    if (Get-Command java -ErrorAction SilentlyContinue) {
        Log-Info "Java is available at: $((Get-Command java).Source)"

        # Run the Java-based configurator
        $jarPath = Join-Path -Path $env:AM_HOME -ChildPath "lib\agent-configurator.jar"
        if (Test-Path $jarPath) {
            Log-Info "Found agent-configurator.jar at: $jarPath"
            try {
                Log-Info "Starting Java process with arguments: -jar `"$jarPath`" `"${HomeDir}`""

                $process = Start-Process -FilePath "java" `
                         -ArgumentList "-jar `"$jarPath`" `"${HomeDir}`"" `
                         -NoNewWindow `
                         -Wait `
                         -PassThru `
                         -RedirectStandardOutput "$env:AM_HOME\logs\agent-configurator.log" `
                         -RedirectStandardError "$env:AM_HOME\logs\agent-configurator.err"
                Log-Info "Java process completed with exit code: $($process.ExitCode)"

                if (Test-Path "$env:AM_HOME\logs\agent-configurator.log") {
                    $logContent = Get-Content "$env:AM_HOME\logs\agent-configurator.log" -Raw
                    if (-not [string]::IsNullOrWhiteSpace($logContent)) {
                        Log-Info "Agent configurator output:"
                        Write-Host $logContent
                    }
                }

                if (Test-Path "$env:AM_HOME\logs\agent-configurator.err") {
                    $errContent = Get-Content "$env:AM_HOME\logs\agent-configurator.err" -Raw
                    if (-not [string]::IsNullOrWhiteSpace($errContent)) {
                        Log-Info "Agent configurator errors:"
                        Write-Host $errContent
                        return $false
                    }
                }

                if ($process.ExitCode -ne 0) {
                    Log-Info "Error: Failed to configure agent. Exit code: $($process.ExitCode)"
                    Log-Info "Please ensure no other processes are using the agent files and try again."
                    return $false
                }

                # Agent configurator successful
                return $true
            } catch {
                Log-Info "Error: Failed to configure agent: $_"
                Log-Info "Please ensure no other processes are using the agent files and try again."
                return $false
            }
        } else {
            Log-Info "Error: agent-configurator.jar not found at: $jarPath"
            return $false
        }
    } else {
        Log-Info "Error: Java is not installed or not in PATH"
        return $false
    }
}

# Function to create PID file
function Create-PidFile {
    param(
        [bool]$IsService,
        [string]$BaseDir
    )

    try {
        if ($IsService) {
            # For service mode, use elevated privileges
            # Start-Process -FilePath "powershell.exe" -ArgumentList "-Command `"New-Item -Path '$BaseDir' -ItemType Directory -Force; New-Item -Path '$BaseDir\am-agent.pid' -ItemType File -Force`"" -Verb RunAs -Wait
        } else {
            # For standalone mode, use current user privileges
            New-Item -Path $BaseDir -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
            New-Item -Path (Join-Path -Path $BaseDir -ChildPath "am-agent.pid") -ItemType File -Force -ErrorAction SilentlyContinue | Out-Null
        }
    } catch {
        Log-Info "Warning: Failed to create PID file: $_"
    }
}

# Initialize configuration files
Initialize-ConfigFiles

# Configure proxy URL
Invoke-ConfigureProxyUrl

# Configure telemetry port
Invoke-ConfigureTelemetryPort

# Configure telemetry logs level
Invoke-ConfigureTelemetryLogLevel

# Configure tracing port
Invoke-ConfigureTracingPort

# Configure agent and check if it succeeded
$configSuccess = Invoke-ConfigureAgent
if (-not $configSuccess) {
    Log-Info "Agent Configuration failed. Exiting installation."
    exit 1
}

# Handle service installation
if ($SkipServicePrompt) {
    # Create pid file only if running as a process
    Create-PidFile -IsService $false -BaseDir $env:AM_HOME
    Set-Content -Path $modeFile -Value "process"
    Log-Info "Skipping service installation prompt. Anypoint Monitoring OpenTelemetry Collector will run as a standalone process."
    $selectedMode = "process"
} else {
    $REPLY = Read-Host -Prompt "Do you want to use monitoring as a service? [y|n]"
    if ($REPLY -eq 'y') {
        Set-Content -Path $modeFile -Value "service"
        # Warn if services are already installed
        $otelSvcInstalled = IsServiceInstalled -ServiceName "am-otel-collector"
        $dwSvcInstalled = IsServiceInstalled -ServiceName "DirectoryWatcherService"
        if ($otelSvcInstalled -or $dwSvcInstalled) {
            if ($otelSvcInstalled) { Log-Info "WARN: Service 'am-otel-collector' is already installed. It will be reused/updated." }
            if ($dwSvcInstalled) { Log-Info "WARN: Service 'DirectoryWatcherService' is already installed. It will be reused/updated." }
        }
        Log-Info "Anypoint Monitoring OpenTelemetry Collector will run as a Windows service."
        $selectedMode = "service"
    } else {
        # Create pid file only if running as a process
        Create-PidFile -IsService $false -BaseDir $env:AM_HOME
        Set-Content -Path $modeFile -Value "process"
        Log-Info "Anypoint Monitoring OpenTelemetry Collector will run as a standalone process."
        $selectedMode = "process"
    }
}

# Display installation success and next steps
Write-Host ""
Write-Host "[SUCCESS] Anypoint Monitoring installation completed successfully!" -ForegroundColor Green
Write-Host ""
Write-Host "Installation Mode Selected: $selectedMode" -ForegroundColor Yellow
Write-Host ""
Write-Host "Next Steps:" -ForegroundColor Cyan
Write-Host "1. Restart your Mule server completely to ensure all configurations are loaded properly" -ForegroundColor White
Write-Host "2. After restarting Mule, run the setup script to start monitoring:" -ForegroundColor White
Write-Host "   $env:AM_HOME\bin\setup.ps1" -ForegroundColor Yellow
Write-Host ""
Write-Host "The setup script will start both the OpenTelemetry Collector and DirectoryWatcher" -ForegroundColor White
Write-Host "based on your selected installation mode ($selectedMode)." -ForegroundColor White
Write-Host ""

# This script will prompt you to choose whether to run Anypoint Monitoring as a Windows service or as a standalone
# process. Your choice is saved in the 'install.mode' file, so the system knows which mode to use for future
# start/stop commands. We do not rely on the am-agent.pid file for this, because that file is deleted when the
# process stops, and would not reliably indicate your chosen mode. Even if the service cannot be installed now
# (for example, if configuration files are missing), you can still follow the standard installation steps.