# SOPS Decryption Cache System
# Caches decrypted SOPS content with strict 0600 permissions
# SECURITY: All SOPS cache files must have 0600 permissions
# TTL: 15 minutes (configurable, balances security and performance)
# Follows Nushell 0.109.0+ guidelines

use ./core.nu *
use ./metadata.nu *

# Helper: Compute hash of SOPS file path
def compute-sops-hash [file_path: string] {
    let hash_result = (do {
        $file_path | ^openssl dgst -sha256 -hex
    } | complete)

    if $hash_result.exit_code == 0 {
        ($hash_result.stdout | str trim | split column " " | get column1 | get 0)
    } else {
        ($file_path | hash md5 | str substring 0..32)
    }
}

# Helper: Set file permissions to 0600 (owner read-write only)
def set-sops-file-permissions [cache_file: string] {
    do {
        ^chmod 0600 $cache_file
    } | complete | ignore
}

# Helper: Set directory permissions to 0700 (owner read-write-execute)
def set-sops-dir-permissions [cache_dir: string] {
    do {
        ^chmod 0700 $cache_dir
    } | complete | ignore
}

# Helper: Validate file permissions
def validate-sops-permissions [cache_file: string] {
    let stat_result = (do {
        if ($cache_file | path exists) {
            $cache_file | stat
        } else {
            error make { msg: "File not found" }
        }
    } | complete)

    if $stat_result.exit_code != 0 {
        return { valid: false, reason: "file_stat_error" }
    }

    let perms = ($stat_result.stdout | get permissions)

    # Check for 0600 permissions (read/write for owner only)
    # Acceptable: -rw------- or similar
    if ((not ($perms | str contains "rw")) or
       (($perms | str length) > 1 and (($perms | str substring 4..-1) != "------"))) {
        return { valid: false, reason: "permissions_too_permissive" }
    }

    { valid: true, reason: "permissions_valid" }
}

# ============================================================================
# PUBLIC API: SOPS Cache Operations
# ============================================================================

# Cache decrypted SOPS content with security
export def cache-sops-decrypt [
    file_path: string
    decrypted_content: string
] {
    let cache_key = (compute-sops-hash $file_path)

    let source_files = [$file_path]

    # Write cache with 15-minute TTL (security sensitive)
    cache-write "sops" $cache_key $decrypted_content $source_files --ttl 900

    # CRITICAL: Set 0600 permissions on cache file
    let cache_file = (let home = ($env.HOME? | default "~" | path expand);
                      $home | path join ".provisioning" "cache" "config" "sops" $cache_key)

    if ($cache_file | path exists) {
        set-sops-file-permissions $cache_file
    }

    # Set directory permissions to 0700
    let cache_dir = ($cache_file | path dirname)
    if ($cache_dir | path exists) {
        set-sops-dir-permissions $cache_dir
    }
}

# Lookup cached SOPS decryption
export def lookup-sops-cache [
    file_path: string
] {
    if not ($file_path | path exists) {
        return { valid: false, reason: "file_not_found", data: null }
    }

    let cache_key = (compute-sops-hash $file_path)

    # Try to lookup in cache
    let cache_result = (cache-lookup "sops" $cache_key)

    if not $cache_result.valid {
        return {
            valid: false,
            reason: $cache_result.reason,
            data: null
        }
    }

    # SECURITY: Validate permissions before returning
    let home = ($env.HOME? | default "~" | path expand)
    let cache_file = ($home | path join ".provisioning" "cache" "config" "sops" $cache_key)

    let perm_check = (validate-sops-permissions $cache_file)

    if not $perm_check.valid {
        # Permissions compromised - invalidate cache
        return {
            valid: false,
            reason: $"permission_violation: ($perm_check.reason)",
            data: null
        }
    }

    {
        valid: true,
        reason: "cache_hit",
        data: $cache_result.data
    }
}

# Validate SOPS cache (permissions + TTL + mtime)
def validate-sops-cache [
    cache_file: string
] {
    # Check permissions
    let perm_check = (validate-sops-permissions $cache_file)

    if not $perm_check.valid {
        return $perm_check
    }

    # Load metadata
    let meta_file = $"($cache_file).meta"
    let meta_load = (do {
        open $meta_file
    } | complete)

    if $meta_load.exit_code != 0 {
        return { valid: false, reason: "metadata_not_found" }
    }

    let meta = $meta_load.stdout

    # Check TTL
    let now = (date now | format date "%Y-%m-%dT%H:%M:%SZ")
    if $now > $meta.expires_at {
        return { valid: false, reason: "ttl_expired" }
    }

    # Check source file mtime
    for src_file in $meta.source_files {
        let current_mtime = (do {
            if ($src_file | path exists) {
                $src_file | stat | get modified | into int
            } else {
                -1
            }
        } | complete | get stdout)

        let cached_mtime = ($meta.source_mtimes | get --optional $src_file | default (-1))

        if $current_mtime != $cached_mtime {
            return { valid: false, reason: "sops_file_modified" }
        }
    }

    { valid: true, reason: "validation_passed" }
}

# Clear SOPS cache (security cleanup)
export def clear-sops-cache [
    --pattern: string = "*"    # Optional: clear specific pattern
] {
    cache-clear-type "sops"
}

# Rotate SOPS cache (refresh permissions and TTL)
export def rotate-sops-cache [
    file_path: string
    decrypted_content: string
] {
    # Clear old cache
    let old_cache_key = (compute-sops-hash $file_path)
    let home = ($env.HOME? | default "~" | path expand)
    let old_cache_file = ($home | path join ".provisioning" "cache" "config" "sops" $old_cache_key)

    do {
        rm -f $old_cache_file
        rm -f $"($old_cache_file).meta"
    } | complete | ignore

    # Write new cache with refreshed TTL
    cache-sops-decrypt $file_path $decrypted_content
}

# Get SOPS cache statistics
export def get-sops-cache-stats [] {
    let home = ($env.HOME? | default "~" | path expand)
    let base = ($home | path join ".provisioning" "cache" "config" "sops")

    if not ($base | path exists) {
        return {
            total_entries: 0,
            total_size_mb: 0,
            permission_errors: 0
        }
    }

    mut stats = {
        total_entries: 0,
        total_size_mb: 0,
        permission_errors: 0
    }

    for meta_file in (glob $"($base)/**/*.meta") {
        let cache_file = ($meta_file | str substring 0..-6)

        if ($cache_file | path exists) {
            # Check permissions
            let perm_check = (validate-sops-permissions $cache_file)
            if not $perm_check.valid {
                $stats.permission_errors += 1
            }

            let size_result = (do {
                $cache_file | stat | get size
            } | complete)

            if $size_result.exit_code == 0 {
                let size_mb = ($size_result.stdout / 1048576)
                $stats.total_entries += 1
                $stats.total_size_mb += $size_mb
            }
        }
    }

    $stats
}

# Enforce permissions on all SOPS cache files (security audit)
export def enforce-sops-permissions [] {
    let home = ($env.HOME? | default "~" | path expand)
    let base = ($home | path join ".provisioning" "cache" "config" "sops")

    if not ($base | path exists) {
        return
    }

    # Enforce directory permissions
    set-sops-dir-permissions $base

    # Enforce file permissions
    for cache_file in (glob $"($base)/*" --no-dir) {
        if ($cache_file | path exists) {
            set-sops-file-permissions $cache_file
        }
    }
}
