# Encryption Module for Password Protection
# This module provides functions to encrypt and decrypt sensitive properties
# Usage: Import-Module Helper-Encryption.psm1

# Define the password properties that should be encrypted
$script:PasswordProperties = @(
    "ORACLE_ADMIN_PASSWORD",
    "POSTGRES_ADMIN_PASSWORD",
    "ORACLE_PASSWORD",
    "POSTGRES_USER_PASSWORD"
)

# Encryption key - In production, this should be configurable or use Windows DPAPI
$script:EncryptionKey = "axiUmMigration2024Key123!"

# Function to create AES key from password
function Get-AesKey {
    param([string]$Password)

    # Create a SHA256 hash of the password
    $sha256 = [System.Security.Cryptography.SHA256]::Create()
    $hash = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Password))

    # Take first 32 bytes for AES-256
    $key = New-Object byte[] 32
    [Array]::Copy($hash, 0, $key, 0, 32)

    return $key
}

# Function to encrypt a string using AES
function Protect-Value {
    param([string]$PlainText)

    if ([string]::IsNullOrEmpty($PlainText)) {
        return $PlainText
    }

    try {
        $aes = [System.Security.Cryptography.Aes]::Create()
        $aes.Key = Get-AesKey -Password $script:EncryptionKey
        $aes.IV = New-Object byte[] 16  # AES block size is 16 bytes

        $encryptor = $aes.CreateEncryptor()

        $plainBytes = [System.Text.Encoding]::UTF8.GetBytes($PlainText)
        $encryptedBytes = $encryptor.TransformFinalBlock($plainBytes, 0, $plainBytes.Length)

        # Combine IV and encrypted data
        $result = $aes.IV + $encryptedBytes

        # Convert to base64 for storage
        $encryptedString = [Convert]::ToBase64String($result)

        return "ENC:$encryptedString"
    }
    catch {
        Show-Error -Message "Failed to encrypt value: $($_.Exception.Message)" -LogPrefix "Encryption"
        throw
    }
    finally {
        if ($aes) { $aes.Dispose() }
        if ($encryptor) { $encryptor.Dispose() }
    }
}

# Function to decrypt a string using AES
function Unprotect-Value {
    param([string]$EncryptedText)

    if ([string]::IsNullOrEmpty($EncryptedText)) {
        return $EncryptedText
    }

    # Check if the value is encrypted (starts with ENC:)
    if (-not $EncryptedText.StartsWith("ENC:")) {
        # Return as-is if not encrypted
        return $EncryptedText
    }

    try {
        $aes = [System.Security.Cryptography.Aes]::Create()
        $aes.Key = Get-AesKey -Password $script:EncryptionKey

        # Remove ENC: prefix and decode from base64
        $encryptedData = [Convert]::FromBase64String($EncryptedText.Substring(4))

        # Extract IV (first 16 bytes) and encrypted data
        $aes.IV = $encryptedData[0..15]
        $encryptedBytes = $encryptedData[16..($encryptedData.Length - 1)]

        $decryptor = $aes.CreateDecryptor()

        $decryptedBytes = $decryptor.TransformFinalBlock($encryptedBytes, 0, $encryptedBytes.Length)
        $decryptedString = [System.Text.Encoding]::UTF8.GetString($decryptedBytes)

        return $decryptedString
    }
    catch {
        Show-Error -Message "Failed to decrypt value: $($_.Exception.Message)" -LogPrefix "Encryption"
        # Return original value if decryption fails
        return $EncryptedText
    }
    finally {
        if ($aes) { $aes.Dispose() }
        if ($decryptor) { $decryptor.Dispose() }
    }
}

# Function to check if a value is encrypted
function Test-EncryptedValue {
    param([string]$Value)

    return $Value.StartsWith("ENC:")
}

# Function to encrypt properties in a hashtable
function Protect-Properties {
    param([hashtable]$Properties)

    $encryptedProperties = @{}

    foreach ($key in $Properties.Keys) {
        $value = $Properties[$key]

        if ($script:PasswordProperties -contains $key -and -not (Test-EncryptedValue -Value $value)) {
            # Encrypt password properties that aren't already encrypted
            $encryptedProperties[$key] = Protect-Value -PlainText $value
        } else {
            # Keep non-password properties and already encrypted values as-is
            $encryptedProperties[$key] = $value
        }
    }

    return $encryptedProperties
}

# Function to decrypt properties in a hashtable
function Unprotect-Properties {
    param([hashtable]$Properties)

    $decryptedProperties = @{}

    foreach ($key in $Properties.Keys) {
        $value = $Properties[$key]

        if ($script:PasswordProperties -contains $key -and (Test-EncryptedValue -Value $value)) {
            # Decrypt password properties
            $decryptedProperties[$key] = Unprotect-Value -EncryptedText $value
        } else {
            # Keep non-password properties and non-encrypted values as-is
            $decryptedProperties[$key] = $value
        }
    }

    return $decryptedProperties
}

# Function to detect and encrypt plaintext passwords in properties file
function Convert-PlaintextPasswords {
    param(
        [string]$PropertiesFilePath,
        [bool]$ShowWarnings = $true
    )

    if (-not (Test-Path $PropertiesFilePath)) {
        return $false
    }

    if ($ShowWarnings) {
        Show-Info -Message "Checking for plaintext passwords in properties file..." -LogPrefix "Encryption"
    }

    $content = Get-Content $PropertiesFilePath -ErrorAction Stop
    $modified = $false
    $updatedContent = @()

    foreach ($line in $content) {
        if ($line -match '^([^=]+)=(.*)$') {
            $key = $matches[1].Trim()
            $value = $matches[2].Trim()

            # Check if this is a password property with plaintext value
            if ($script:PasswordProperties -contains $key -and -not (Test-EncryptedValue -Value $value) -and -not [string]::IsNullOrEmpty($value)) {
                if ($ShowWarnings) {
                    Show-Warning -Message "Found plaintext password for $key, encrypting..." -LogPrefix "Encryption"
                }
                $encryptedValue = Protect-Value -PlainText $value
                $updatedContent += "$key=$encryptedValue"
                $modified = $true
            } else {
                $updatedContent += $line
            }
        } else {
            $updatedContent += $line
        }
    }

    if ($modified) {
        $updatedContent | Out-File -FilePath $PropertiesFilePath -Encoding UTF8 -Force
        Show-Success -Message "Encrypted passwords in properties file" -LogPrefix "Encryption"
        return $true
    } else {
        # File was successfully processed, even if no modifications were needed (passwords already encrypted)
        return $true
    }
}

# Function to clean up toolkit properties files
function Invoke-CleanUpAndExit {
    param([int]$ExitCode = 0)

    $migrationDirectory = $global:MigrationDirectory

    $toolkitPaths = @(
        "C:\Program Files\edb\mtk\etc\toolkit.properties",
        (Join-Path $migrationDirectory "toolkit.properties")
    )

    $filesRemoved = 0

    foreach ($path in $toolkitPaths) {
        if (Test-Path $path) {
            try {
                Remove-Item -Path $path -Force -ErrorAction Stop
                Show-Info -Message "Removed toolkit properties file: $path" -LogPrefix "Cleanup"
                $filesRemoved++
            }
            catch {
                Show-Warning -Message "Failed to remove toolkit properties file: $path - $($_.Exception.Message)" -LogPrefix "Cleanup"
            }
        }
    }

    exit $ExitCode
}

# Export the functions
Export-ModuleMember -Function Protect-Value, Unprotect-Value, Test-EncryptedValue, Protect-Properties, Unprotect-Properties, Convert-PlaintextPasswords, Invoke-CleanUpAndExit
