# serviceHelper.ps1

$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$AM_HOME = (Resolve-Path -Path (Join-Path -Path $scriptDir -ChildPath "..\..")).Path
$PIDFILE = Join-Path $AM_HOME "am-agent.pid"

# Returns true if the given Windows service is installed.
function IsServiceInstalled {
    param (
        [string]$ServiceName
    )

    try {
        $service = Get-Service -Name $ServiceName -ErrorAction Stop
        return $true
    } catch {
        return $false
    }
}

function Log-Info {
    param([string]$Message)
    $scriptName = [System.IO.Path]::GetFileName($MyInvocation.ScriptName)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss.fff zzz"
    Write-Host "[$timestamp] ($scriptName) $Message"
}

# Converts Windows-style paths to Unix-style in a YAML file.
function Convert-WindowsPathsToUnix {
    param(
        [string]$YamlFilePath
    )

    try {
        Log-Info "Converting Windows paths to Unix format in: $YamlFilePath"

        # Read the YAML file
        $content = Get-Content -Path $YamlFilePath -Raw

        # Replace Windows backslashes with forward slashes
        # This regex looks for paths that start with a drive letter and have backslashes
        $content = $content -replace '([A-Za-z]:\\)([^"''\s]+)', '$1$2' -replace '\\', '/'

        # Write the modified content back to the file
        $content | Set-Content -Path $YamlFilePath -NoNewline

        Log-Info "Successfully converted paths in YAML file"
        return $true
    }
    catch {
        Log-Info "Failed to convert paths in YAML file: $_"
        return $false
    }
}

# Converts all Windows-style paths to Unix-style in all YAML files in a directory.
function Convert-AllYamlPaths {
    param(
        [string]$PipelinesDir
    )

    try {
        Log-Info "Converting paths in all YAML files in: $PipelinesDir"

        # Get all YAML files
        $yamlFiles = Get-ChildItem -Path $PipelinesDir -Filter "*.yml"

        foreach ($file in $yamlFiles) {
            Convert-WindowsPathsToUnix -YamlFilePath $file.FullName
        }

        Log-Info "Successfully converted paths in all YAML files"
        return $true
    }
    catch {
        Log-Info "Failed to convert paths in YAML files: $_"
        return $false
    }
}

# Important function to determine the mode in which otel collector should start.
# Determined by install.mode file which is created after install.ps1 is executed.
# Returns install mode or "not_installed" if install.mode file doesn't exist
function Get-InstallMode {
    $modeFile = Join-Path $AM_HOME "install.mode"
    if (Test-Path $modeFile) {
        $mode = (Get-Content $modeFile -Raw).Trim()
        if ($mode -eq "service" -or $mode -eq "process") {
            return $mode
        } else {
            # Invalid content in file
            return "not_installed"
        }
    }
    return "not_installed" # File doesn't exist - install.ps1 needs to be run first
}

# Returns true if any otel-collector process is running.
function Is-Otel-Collector-Running-As-A-Process {
    # Checks if the otel-collector process is running using the PID from the PID file
    $allCollectors = Get-Process -Name "otel-collector" -ErrorAction SilentlyContinue
    if ($allCollectors) {
        $allCollectors | ForEach-Object {
            Write-Host "Otel-collector process running with PID $($_.Id)"
        }
        return $true
    } else {
        return $false
    }
}

# Returns true if the otel-collector Windows service is running.
function Is-Otel-Collector-Running-As-A-Service {
    # Checks if the otel-collector Windows service is running
    $svc = Get-Service -Name "am-otel-collector" -ErrorAction SilentlyContinue
    if ($svc -and $svc.Status -eq "Running") {
#        Write-Host "Service 'am-otel-collector' is running."
        return $true
    } else {
#        Write-Host "Service 'am-otel-collector' is NOT running."
        return $false
    }
}

# Returns true if otel-collector is running as a process (by PID file) or as a service.
function Is-Otel-Collector-Running {
    $mode = Get-InstallMode
    if ($mode -eq "process") {
        # OTel collector is supposed to be running as a process
        return Is-Otel-Collector-Running-As-A-Process
    } elseif ($mode -eq "service") {
        # OTel collector is supposed to be running as a service
        return Is-Otel-Collector-Running-As-A-Service
    } else {
        return $false
    }
}

# Returns true if Java is available in PATH
function Test-JavaAvailable {
    try {
        $javaCmd = Get-Command java -ErrorAction SilentlyContinue
        if ($javaCmd) {
            return $true
        } else {
            return $false
        }
    } catch {
        return $false
    }
}

# Returns true if DirectoryWatcher Java process is running (process mode check).
function Is-DirectoryWatcher-Running-As-A-Process {
    try {
        $dwProcesses = Get-Process java -ErrorAction SilentlyContinue | Where-Object {
            $_.Path -like "*java.exe" -and (Get-CimInstance Win32_Process -Filter "ProcessId=$($_.Id)" -ErrorAction SilentlyContinue).CommandLine -match "com\.mulesoft\.dias\.mule\.agent\.DirectoryWatcher"
        }
        
        if ($null -ne $dwProcesses -and $dwProcesses.Count -gt 0) {
            # Process is found, log information about each process
            foreach ($process in $dwProcesses) {
                Log-Info "DirectoryWatcher is running with PID $($process.Id)"
            }
            return $true
        } else {
            Log-Info "No DirectoryWatcher process is running"
            return $false
        }
    } catch {
        Log-Info "Error checking for DirectoryWatcher: $_"
        return $false
    }
}

# Returns true if the DirectoryWatcher Windows service is running.
function Is-DirectoryWatcher-Running-As-A-Service {
    # Checks if the DirectoryWatcher Windows service is running
    $svc = Get-Service -Name "DirectoryWatcherService" -ErrorAction SilentlyContinue
    if ($svc -and $svc.Status -eq "Running") {
        return $true
    } else {
        return $false
    }
}

# Returns true if DirectoryWatcher is running as a process (by Java process) or as a service (based on install.mode).
function Is-DirectoryWatcher-Running {
    $mode = Get-InstallMode
    if ($mode -eq "process") {
        # DirectoryWatcher is supposed to be running as a process
        return Is-DirectoryWatcher-Running-As-A-Process
    } elseif ($mode -eq "service") {
        # DirectoryWatcher is supposed to be running as a service
        return Is-DirectoryWatcher-Running-As-A-Service
    } else {
        return $false
    }
}

#-----------------------------------------------------------------------------#
#                    GOMEMLIMIT Configuration Functions                       #
#-----------------------------------------------------------------------------#

# Configures GOMEMLIMIT based on OTEL_PROCESSOR_MEMORY_LIMIT_MB or default
function Set-GoMemLimit {
    param(
        [Parameter(Mandatory=$false)]
        [int]$MemoryLimitMB = 40,
        
        [Parameter(Mandatory=$false)]
        [switch]$Force
    )
    
    try {
        # Check for system-wide GOMEMLIMIT and set if not present
        $currentGoMemLimit = [System.Environment]::GetEnvironmentVariable("GOMEMLIMIT", [System.EnvironmentVariableTarget]::Machine)

        if ($currentGoMemLimit -and -not $Force) {
            $env:GOMEMLIMIT = $currentGoMemLimit
            Log-Info "System-wide GOMEMLIMIT is already set to $currentGoMemLimit. Reusing it."
            return $currentGoMemLimit
        } else {
            # Compute GOMEMLIMIT based on OTEL_PROCESSOR_MEMORY_LIMIT_MB or provided parameter
            if ($env:OTEL_PROCESSOR_MEMORY_LIMIT_MB) {
                $memLimit = [int]$env:OTEL_PROCESSOR_MEMORY_LIMIT_MB
                Log-Info "Using OTEL_PROCESSOR_MEMORY_LIMIT_MB: $memLimit MB"
            } else {
                $memLimit = $MemoryLimitMB
                Log-Info "Using default memory limit: $memLimit MB"
            }
            
            # Calculate GOMEMLIMIT as 67% of memory limit (inspired by otel-pipeline-common.yml)
            $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
            
            if ($Force) {
                Log-Info "System-wide GOMEMLIMIT forcefully updated to $gomemlimitValue"
            } else {
                Log-Info "System-wide GOMEMLIMIT set to $gomemlimitValue"
            }
            
            return $gomemlimitValue
        }
    } catch {
        Log-Info "ERROR: Failed to set GOMEMLIMIT: $($_.Exception.Message)"
        return $null
    }
}

#-----------------------------------------------------------------------------#
#                    FileProcessingUtils Shared Functions                    #
#-----------------------------------------------------------------------------#

# Executes FileProcessingUtils to process configuration files (mode agnostic)
# FileProcessingUtils is idempotent - safe to run multiple times
function Invoke-FileProcessingUtils {
    param(
        [Parameter(Mandatory=$true)]
        [string]$MuleHome,
        
        [Parameter(Mandatory=$false)]
        [string]$AmHome
    )
    
    if (-not $AmHome) {
        $AmHome = Join-Path $MuleHome "am"
    }
    
    $configPipelines = Join-Path $AmHome "config\pipelines"
    $classpath = Join-Path $AmHome "lib\*"
    $logsDir = Join-Path $AmHome "logs"
    
    # Ensure logs directory exists
    if (!(Test-Path $logsDir)) {
        New-Item -ItemType Directory -Path $logsDir -Force | Out-Null
        Log-Info "Created logs directory: $logsDir"
    }
    
    Log-Info "Invoking FileProcessingUtils to process $configPipelines"
    
    # Validate prerequisites - check multiple locations for Java
    $javaExe = $null
    
    # Priority 1: Check JAVA_HOME environment variable
    if ($env:JAVA_HOME) {
        $candidateJava = Join-Path $env:JAVA_HOME "bin\java.exe"
        if (Test-Path $candidateJava) {
            $javaExe = $candidateJava
            Log-Info "Found Java via JAVA_HOME: $javaExe"
        }
    }
    
    # Priority 2: Try Get-Command (PATH lookup)
    if (-not $javaExe) {
        try {
            $javaCmd = Get-Command java -ErrorAction SilentlyContinue
            if ($javaCmd) {
                $javaExe = $javaCmd.Source
                Log-Info "Found Java via PATH: $javaExe"
            }
        } catch {
            # PATH lookup failed
        }
    }
    
    # Priority 3: Common Java installation locations
    if (-not $javaExe) {
        $commonJavaLocations = @(
            "${env:ProgramFiles}\Java\*\bin\java.exe",
            "${env:ProgramFiles(x86)}\Java\*\bin\java.exe",
            "C:\Program Files\Java\*\bin\java.exe",
            "C:\Program Files (x86)\Java\*\bin\java.exe"
        )
        
        foreach ($location in $commonJavaLocations) {
            $foundJava = Get-ChildItem $location -ErrorAction SilentlyContinue | Select-Object -First 1
            if ($foundJava) {
                $javaExe = $foundJava.FullName
                Log-Info "Found Java at common location: $javaExe"
                break
            }
        }
    }
    
    if (-not $javaExe) {
        Log-Info "ERROR: Java executable not found. Checked JAVA_HOME, PATH, and common locations"
        Log-Info "Current JAVA_HOME: $env:JAVA_HOME"
        Log-Info "Current PATH: $env:PATH"
        return $false
    }
    
    if (-not (Test-Path $configPipelines)) {
        Log-Info "ERROR: Configuration directory not found: $configPipelines"
        return $false
    }
    
    # Check for configuration files
    $configFiles = Get-ChildItem -Path $configPipelines -Filter "*.yml" -File -ErrorAction SilentlyContinue
    if ($configFiles.Count -eq 0) {
        Log-Info "WARN: No YAML configuration files found in $configPipelines"
        return $true  # Not an error - just nothing to process
    }
    
    try {
        $fpuLog = Join-Path $logsDir "fileprocessingutils.log"
        $fpuErr = Join-Path $logsDir "fileprocessingutils.err"
        
        Log-Info "Using classpath: $classpath"
        Log-Info "Output will be logged to: $fpuLog"
        
        # Execute FileProcessingUtils with discovered Java executable
        $fpuProcess = Start-Process -FilePath $javaExe `
                                   -ArgumentList @(
                                       "-DmuleHome=`"$MuleHome`""
                                       "-cp"
                                       "`"$classpath`""
                                       "com.mulesoft.dias.util.FileProcessingUtils"
                                       "`"$configPipelines`""
                                   ) `
                                   -RedirectStandardError $fpuErr `
                                   -RedirectStandardOutput $fpuLog `
                                   -NoNewWindow `
                                   -Wait `
                                   -PassThru
        
        if ($fpuProcess.ExitCode -eq 0) {
            Log-Info "FileProcessingUtils completed successfully with exit code: 0"
            return $true
        } else {
            Log-Info "ERROR: FileProcessingUtils failed with exit code: $($fpuProcess.ExitCode)"
            
            # Show error details if available
            if (Test-Path $fpuErr) {
                $errorContent = Get-Content $fpuErr -Raw -ErrorAction SilentlyContinue
                if (-not [string]::IsNullOrWhiteSpace($errorContent)) {
                    Log-Info "FileProcessingUtils error details:"
                    $errorContent.Split("`n") | ForEach-Object { 
                        if (-not [string]::IsNullOrWhiteSpace($_)) {
                            Log-Info "  $_"
                        }
                    }
                }
            }
            
            return $false
        }
    } catch {
        Log-Info "ERROR: Exception running FileProcessingUtils: $($_.Exception.Message)"
        return $false
    }
}