# Configuration Encryption Module for Provisioning System
# Provides transparent encryption/decryption for configuration files using SOPS
# Optimized with nu_plugin_kms for 10x performance improvement

use std log
# Selective imports (ADR-025 Phase 3 Layer 2).
# config/accessor star-import was dead — dropped.
use lib_provisioning/sops/lib.nu [get-sops-age-key-file is_sops_file on_sops]
use lib_provisioning/kms/lib.nu [on_kms]
use lib_provisioning/plugins/kms.nu [plugin-kms-decrypt plugin-kms-encrypt plugin-kms-info]

# Detect if a config file is encrypted
export def is-encrypted-config [
    file_path: string
] {
    if not ($file_path | path exists) {
        return false
    }

    # Check if file has SOPS metadata
    is_sops_file $file_path
}

# Load configuration file with automatic decryption
export def load-encrypted-config [
    file_path: string
    --debug = false
] {
    if not ($file_path | path exists) {
        error make {
            msg: $"Configuration file not found: ($file_path)"
        }
    }

    if $debug {
        print $"Loading configuration file: ($file_path)"
    }

    # Check if file is encrypted
    if (is-encrypted-config $file_path) {
        if $debug {
            print $"  Detected encrypted file, decrypting..."
        }

        # Decrypt in memory (never write to disk)
        let decrypted_content = (decrypt-config-memory $file_path --debug=$debug)

        # Parse based on file extension
        let ext = ($file_path | path parse | get extension)
        match $ext {
            "yaml" | "yml" => ($decrypted_content | from yaml)
            "toml" => ($decrypted_content | from toml)
            "json" => ($decrypted_content | from json)
            _ => {
                error make {
                    msg: $"Unsupported encrypted config format: ($ext)"
                }
            }
        }
    } else {
        if $debug {
            print $"  Loading unencrypted file..."
        }
        # Load unencrypted file normally
        open $file_path
    }
}

# Decrypt configuration file in memory only (never writes to disk)
export def decrypt-config-memory [
    file_path: string
    --debug = false
] {
    if not (is-encrypted-config $file_path) {
        error make {
            msg: $"File is not encrypted: ($file_path)"
        }
    }

    # Plugin-based KMS decryption (10x faster for Age/RustyVault)
    # Refactored from try-catch to do/complete for explicit error handling
    let plugin_info = if (which plugin-kms-info | is-not-empty) {
        do { plugin-kms-info } | default { plugin_available: false, default_backend: "age" }
    } else {
        { plugin_available: false, default_backend: "age" }
    }

    if $plugin_info.plugin_available and $plugin_info.default_backend in ["rustyvault", "age"] {
        let start_time = (date now)
        let file_content_result = (do { open -r $file_path } | complete)

        if $file_content_result.exit_code == 0 {
            let file_content = ($file_content_result.stdout | str trim)

            # Check if this is a KMS-encrypted file (not SOPS)
            if not ($file_content | str starts-with "sops:") and not ($file_content | str contains "sops_version") {
                let decrypt_result = (do { plugin-kms-decrypt $file_content --backend $plugin_info.default_backend } | complete)

                if $decrypt_result.exit_code == 0 {
                    let decrypted = ($decrypt_result.stdout | str trim)
                    let elapsed = ((date now) - $start_time)

                    if $debug {
                        print $"⚡ Decrypted in ($elapsed) using plugin ($plugin_info.default_backend)"
                    }

                    return $decrypted
                } else {
                    # Plugin decryption failed, fall through to SOPS
                    if $debug {
                        print $"⚠️  Plugin decryption failed, using SOPS fallback"
                    }
                }
            }
        } else {
            # File read failed, fall through to SOPS
            if $debug {
                print $"⚠️  Could not read file, using SOPS fallback"
            }
        }
    }

    # Use SOPS to decrypt (output goes to stdout, captured in memory)
    let start_time = (date now)
    let decrypted = (on_sops "decrypt" $file_path --quiet)
    let elapsed = ((date now) - $start_time)

    if $debug {
        print $"Decrypted in ($elapsed) using SOPS"
    }

    if ($decrypted | is-empty) {
        error make {
            msg: $"Failed to decrypt configuration file: ($file_path)"
        }
    }

    $decrypted
}

# Encrypt a configuration file
export def encrypt-config [
    source_path: string
    output_path?: string
    --kms: string = "age"  # age, rustyvault, aws-kms, vault, cosmian
    --in-place = false
    --debug = false
] {
    if not ($source_path | path exists) {
        error make {
            msg: $"Source file not found: ($source_path)"
        }
    }

    # Check if already encrypted
    if (is-encrypted-config $source_path) {
        print $"⚠️  File is already encrypted: ($source_path)"
        return
    }

    # Determine output path
    let target = if $in_place {
        $source_path
    } else if ($output_path | is-not-empty) {
        $output_path
    } else {
        $"($source_path).enc"
    }

    if $debug {
        print $"Encrypting ($source_path) → ($target) using ($kms)"
    }

    # Plugin-based encryption for age and rustyvault (10x faster)
    # Refactored from try-catch to do/complete for explicit error handling
    let plugin_info = if (which plugin-kms-info | is-not-empty) {
        do { plugin-kms-info } | default { plugin_available: false, default_backend: "age" }
    } else {
        { plugin_available: false, default_backend: "age" }
    }

    if $plugin_info.plugin_available and $kms in ["age", "rustyvault"] {
        let start_time = (date now)
        let file_content_result = (do { open -r $source_path } | complete)

        if $file_content_result.exit_code == 0 {
            let file_content = ($file_content_result.stdout | str trim)
            let encrypt_result = (do { plugin-kms-encrypt $file_content --backend $kms } | complete)

            if $encrypt_result.exit_code == 0 {
                let encrypted = ($encrypt_result.stdout | str trim)
                let elapsed = ((date now) - $start_time)

                let ciphertext = if (($encrypted | describe) | str starts-with "record") and "ciphertext" in $encrypted {
                    $encrypted.ciphertext
                } else {
                    $encrypted
                }

                let save_result = (do { $ciphertext | save --force $target } | complete)

                if $save_result.exit_code == 0 {
                    if $debug {
                        print $"⚡ Encrypted in ($elapsed) using plugin ($kms)"
                    }
                    print $"✅ Encrypted successfully with plugin ($kms): ($target)"
                    return
                }
            }
        }

        # Plugin encryption failed, fall through to SOPS/CLI
        if $debug {
            print $"⚠️  Plugin encryption failed, using fallback"
        }
    }

    # Fallback: Encrypt based on KMS backend using SOPS/CLI
    let start_time = (date now)

    match $kms {
        "age" | "rustyvault" => {
            let encrypted = (on_sops "encrypt" $source_path)
            if ($encrypted | is-empty) {
                error make {
                    msg: $"Failed to encrypt file with age: ($source_path)"
                }
            }
            $encrypted | save --force $target
            let elapsed = ((date now) - $start_time)
            if $debug {
                print $"Encrypted in ($elapsed) using SOPS"
            }
            print $"✅ Encrypted successfully: ($target)"
        }
        "aws-kms" => {
            # For AWS KMS, SOPS will use AWS KMS backend if configured
            let encrypted = (on_sops "encrypt" $source_path)
            if ($encrypted | is-empty) {
                error make {
                    msg: $"Failed to encrypt file with AWS KMS: ($source_path)"
                }
            }
            $encrypted | save --force $target
            let elapsed = ((date now) - $start_time)
            if $debug {
                print $"Encrypted in ($elapsed) using SOPS with AWS KMS"
            }
            print $"✅ Encrypted successfully with AWS KMS: ($target)"
        }
        "vault" | "cosmian" => {
            # Use KMS client for Vault or Cosmian KMS
            let encrypted = (on_kms "encrypt" $source_path)
            if ($encrypted | is-empty) {
                error make {
                    msg: $"Failed to encrypt file with ($kms): ($source_path)"
                }
            }
            $encrypted | save --force $target
            let elapsed = ((date now) - $start_time)
            if $debug {
                print $"Encrypted in ($elapsed) using KMS ($kms)"
            }
            print $"✅ Encrypted successfully with ($kms): ($target)"
        }
        _ => {
            error make {
                msg: $"Unsupported KMS backend: ($kms). Supported: age, rustyvault, aws-kms, vault, cosmian"
            }
        }
    }
}

# Decrypt a configuration file
export def decrypt-config [
    source_path: string
    output_path?: string
    --in-place = false
    --debug = false
] {
    if not ($source_path | path exists) {
        error make {
            msg: $"Source file not found: ($source_path)"
        }
    }

    # Check if encrypted
    if not (is-encrypted-config $source_path) {
        print $"⚠️  File is not encrypted: ($source_path)"
        return
    }

    # Determine output path
    let target = if $in_place {
        $source_path
    } else if ($output_path | is-not-empty) {
        $output_path
    } else {
        # Remove .enc extension if present
        if ($source_path | str ends-with ".enc") {
            $source_path | str substring 0..-5
        } else {
            $"($source_path).dec"
        }
    }

    if $debug {
        print $"Decrypting ($source_path) → ($target)"
    }

    # Decrypt using SOPS (works with any backend)
    let decrypted = (on_sops "decrypt" $source_path)
    if ($decrypted | is-empty) {
        error make {
            msg: $"Failed to decrypt file: ($source_path)"
        }
    }

    $decrypted | save --force $target
    print $"✅ Decrypted successfully: ($target)"
}

# Edit encrypted config file (decrypt, edit, re-encrypt)
export def edit-encrypted-config [
    file_path: string
    --editor: string = ""
    --debug = false
] {
    if not ($file_path | path exists) {
        error make {
            msg: $"File not found: ($file_path)"
        }
    }

    # Check if encrypted
    if not (is-encrypted-config $file_path) {
        print $"⚠️  File is not encrypted, opening normally..."
        let editor_cmd = if ($editor | is-not-empty) {
            $editor
        } else {
            $env.EDITOR? | default "vim"
        }
        ^$editor_cmd $file_path
        return
    }

    # Use SOPS editor functionality (handles decrypt -> edit -> encrypt)
    print $"🔒 Opening encrypted file with SOPS editor..."

    # SOPS has built-in editor support
    let sops_config = (find-sops-config-path)
    if ($sops_config | is-not-empty) {
        ^sops --config $sops_config $file_path
    } else {
        ^sops $file_path
    }

    print $"✅ File saved and re-encrypted"
}

# Rotate encryption keys for a file
export def rotate-encryption-keys [
    file_path: string
    new_key_id: string
    --debug = false
] {
    if not ($file_path | path exists) {
        error make {
            msg: $"File not found: ($file_path)"
        }
    }

    if not (is-encrypted-config $file_path) {
        error make {
            msg: $"File is not encrypted: ($file_path)"
        }
    }

    print $"🔄 Rotating encryption keys for ($file_path)"
    print $"   New key: ($new_key_id)"

    # Create temporary decrypted file
    let temp_file = ($file_path | str replace ".yaml" ".tmp.yaml")

    do {
        # Decrypt to temp
        let decrypted = (on_sops "decrypt" $file_path)
        $decrypted | save --force $temp_file

        # Update SOPS config to use new key
        # This requires updating .sops.yaml to reference new key
        print $"⚠️  Manual step required: Update .sops.yaml with new key ($new_key_id)"
        print $"   Then run: sops updatekeys ($file_path)"

        # Re-encrypt with new key
        ^sops updatekeys $file_path

        # Remove temp file
        rm --force $temp_file

        print $"✅ Key rotation completed"
    } | complete | if $in.exit_code != 0 {
        # Clean up temp file on error
        if ($temp_file | path exists) {
            rm --force $temp_file
        }
        error make {
            msg: $"Key rotation failed for ($file_path)"
        }
    }
}

# Validate encryption configuration
export def validate-encryption-config [] {
    mut errors = []
    mut warnings = []

    # Check if SOPS is installed
    let sops_check = (^which sops | complete)
    if $sops_check.exit_code != 0 {
        $errors = ($errors | append {
            type: "missing_binary"
            severity: "error"
            message: "SOPS binary not found. Install with: brew install sops"
        })
    }

    # Check if Age is installed (if using Age)
    let use_sops = (get-provisioning-use-sops)
    if ($use_sops | str contains "age") {
        let age_check = (^which age | complete)
        if $age_check.exit_code != 0 {
            $warnings = ($warnings | append {
                type: "missing_binary"
                severity: "warning"
                message: "Age binary not found. Install with: brew install age"
            })
        }

        # Check for Age keys
        let age_key_file = (get-sops-age-key-file)
        if ($age_key_file | is-empty) {
            $warnings = ($warnings | append {
                type: "missing_key"
                severity: "warning"
                message: "Age key file not configured (PROVISIONING_KAGE)"
            })
        } else if not ($age_key_file | path exists) {
            $errors = ($errors | append {
                type: "missing_key_file"
                severity: "error"
                message: $"Age key file not found: ($age_key_file)"
            })
        }
    }

    # Check SOPS configuration file
    let sops_config = (find-sops-config-path)
    if ($sops_config | is-empty) {
        $warnings = ($warnings | append {
            type: "missing_config"
            severity: "warning"
            message: "SOPS config file (.sops.yaml) not found in project"
        })
    }

    # Check KMS configuration if enabled
    let kms_enabled = (get-kms-enabled)
    if $kms_enabled {
        let kms_server = (get-kms-server)
        if ($kms_server | is-empty) {
            $errors = ($errors | append {
                type: "missing_kms_config"
                severity: "error"
                message: "KMS enabled but server URL not configured"
            })
        }
    }

    {
        valid: (($errors | length) == 0)
        errors: $errors
        warnings: $warnings
        summary: {
            sops_installed: ($sops_check.exit_code == 0)
            age_backend: ($use_sops | str contains "age")
            kms_enabled: $kms_enabled
            total_errors: ($errors | length)
            total_warnings: ($warnings | length)
        }
    }
}

# Find SOPS configuration file
def find-sops-config-path [] {
    # Check common locations
    let locations = [
        ".sops.yaml"
        ".sops.yml"
        ($env.PWD | path join ".sops.yaml")
        ($env.HOME | path join ".config" | path join "provisioning" | path join "sops.yaml")
        (get-sops-config-path)
    ]

    for loc in $locations {
        if ($loc | path exists) {
            return $loc
        }
    }

    ""
}

# Check if config file contains sensitive data (heuristic)
export def contains-sensitive-data [
    file_path: string
] {
    if not ($file_path | path exists) {
        return false
    }

    let content = (open $file_path --raw)

    # Patterns that indicate sensitive data
    let sensitive_patterns = [
        "password" "secret" "api_key" "token" "private_key"
        "credential" "auth" "access_key" "secret_key"
    ]

    for pattern in $sensitive_patterns {
        if ($content | str contains $pattern) {
            return true
        }
    }

    false
}

# Scan directory for unencrypted sensitive configs
export def scan-unencrypted-configs [
    directory: string
    --recursive = true
] {
    mut results = []

    let files = if $recursive {
        glob $"($directory)/**/*.{yaml,yml,toml,json}"
    } else {
        glob $"($directory)/*.{yaml,yml,toml,json}"
    }

    for file in $files {
        if (contains-sensitive-data $file) and not (is-encrypted-config $file) {
            $results = ($results | append {
                file: $file
                encrypted: false
                has_sensitive_data: true
                recommendation: "Should be encrypted"
            })
        }
    }

    $results
}

# Encrypt all sensitive configs in directory
export def encrypt-sensitive-configs [
    directory: string
    --kms: string = "age"
    --dry-run = false
    --recursive = true
] {
    print $"🔍 Scanning for unencrypted sensitive configs in ($directory)"

    let unencrypted = (scan-unencrypted-configs $directory --recursive=$recursive)

    if ($unencrypted | is-empty) {
        print $"✅ No unencrypted sensitive configs found"
        return
    }

    print $"\n📋 Found ($unencrypted | length) unencrypted sensitive configs:"
    print $unencrypted

    if $dry_run {
        print $"\n⚠️  Dry run mode - no files will be encrypted"
        return
    }

    print $"\n🔒 Encrypting files with ($kms)..."

    for file in ($unencrypted | get file) {
        print $"  Encrypting: ($file)"
        encrypt-config $file --kms $kms --in-place=true
    }

    print $"\n✅ Encryption completed for all sensitive configs"
}

# Export key functions for CLI integration
export def main [] {
    print "Configuration Encryption Module"
    print "================================"
    print ""
    print "Available commands:"
    print "  - is-encrypted-config <file>      Check if config is encrypted"
    print "  - load-encrypted-config <file>    Load and decrypt config"
    print "  - encrypt-config <file>           Encrypt config file"
    print "  - decrypt-config <file>           Decrypt config file"
    print "  - edit-encrypted-config <file>    Edit encrypted config"
    print "  - rotate-encryption-keys <file>   Rotate encryption keys"
    print "  - validate-encryption-config      Validate encryption setup"
    print "  - scan-unencrypted-configs <dir>  Find unencrypted sensitive configs"
    print "  - encrypt-sensitive-configs <dir> Encrypt all sensitive configs"
}
