# Provider Loader System
# Dynamic provider loading and interface validation

use registry.nu *
use interface.nu *
use ../utils/logging.nu *

# Load provider dynamically with validation (cached)
export def load-provider [name: string] {
    # Check cache first - provider loading happens multiple times due to wrapper scripts
    let cache_key = $"PROVIDER_CACHE_($name)"
    if ($cache_key in ($env | columns)) {
        return ($env | get $cache_key)
    }

    # Silent loading - only log debug, not errors for repeated loads
    if ($env.PROVISIONING_DEBUG? | default false) {
        log-debug $"Loading provider: ($name)" "provider-loader"
    }

    # Check if provider is available
    if not (is-provider-available $name) {
        if ($env.PROVISIONING_DEBUG? | default false) {
            log-debug $"Provider ($name) not found or not available" "provider-loader"
        }
        load-env { $cache_key: {} }
        return {}
    }

    # Get provider registry entry
    let provider_entry = (get-provider-entry $name)

    # Load the provider module
    let provider_instance = if ($provider_entry.type == "core") {
        load-core-provider $provider_entry
    } else {
        load-extension-provider $provider_entry
    }

    if not ($provider_instance | is-empty) {
        # IMPORTANT: Skip subprocess-based validation for extension providers.
        # Child nu processes don't inherit NICKEL_IMPORT_PATH or the provisioning env,
        # so validate-provider-interface always reports functions missing even when valid.
        # (Same documented fix as registry.nu:132-146 and load-extension-provider above)
        # Core providers are loaded from known paths where subprocess context is reliable.
        let skip_validation = ($provider_entry.type == "extension")
        let validation = if $skip_validation {
            { valid: true, missing_functions: [] }
        } else {
            validate-provider-interface $name $provider_instance
        }
        if $validation.valid {
            load-env { $cache_key: $provider_instance }
            $provider_instance
        } else {
            if ($env.PROVISIONING_DEBUG? | default false) {
                log-error $"Provider ($name) failed interface validation" "provider-loader"
                log-error $"Missing functions: ($validation.missing_functions | str join ', ')" "provider-loader"
            }
            load-env { $cache_key: {} }
            {}
        }
    } else {
        if ($env.PROVISIONING_DEBUG? | default false) {
            log-error $"Failed to load provider module for ($name)" "provider-loader"
        }
        load-env { $cache_key: {} }
        {}
    }
}

# Load core provider
def load-core-provider [provider_entry: record] {
    # For core providers, use direct module loading
    # Core providers should be in the core library path
    let module_path = $provider_entry.entry_point

    # Create provider instance record
    {
        name: $provider_entry.name
        type: "core"
        loaded: true
        entry_point: $module_path
        load_time: (date now)
    }
}

# Load extension provider
def load-extension-provider [provider_entry: record] {
    # IMPORTANT: Do NOT spawn a child nu process to validate the provider.
    # Child processes don't inherit NICKEL_IMPORT_PATH or the provisioning env,
    # causing all providers to fail validation even though they are valid.
    # (Same reason registry.nu skips subprocess validation — see registry.nu:132-146)
    # Just verify the file exists and create the instance directly.
    let module_path = $provider_entry.entry_point

    if not ($module_path | path exists) {
        log-error $"Provider module not found: ($module_path)" "provider-loader"
        return {}
    }

    {
        name: $provider_entry.name
        type: "extension"
        loaded: true
        entry_point: $module_path
        load_time: (date now)
        metadata: {}
    }
}

# Get provider instance (with caching)
export def get-provider [name: string] {
    # Check if already loaded in this session
    let cache_key = $"PROVIDER_LOADED_($name)"
    let cached_value = if ($cache_key in ($env | columns)) { $env | get $cache_key } else { null }
    if $cached_value != null {
        return $cached_value
    }

    # Load and cache the provider
    let provider = (load-provider $name)
    if not ($provider | is-empty) {
        load-env { $cache_key: $provider }
    }
    $provider
}

# Call a provider function dynamically
export def call-provider-function [
    provider_name: string
    function_name: string
    ...args
] {
    # Get provider entry
    let provider_entry = (get-provider-entry $provider_name)

    if ($provider_entry | is-empty) {
        log-error $"Provider ($provider_name) not found" "provider-loader"
        return null
    }

    # Use direct import and call via a wrapper script
    let temp_dir = ($env.TMPDIR? | default "/tmp")

    # Save arguments as a list to a single file
    let args_file = ($temp_dir | path join $"provider_args_(random chars).nuon")
    $args | to nuon | save --force $args_file

    # Create wrapper script that loads args and calls function
    # Build individual arg references based on count
    let wrapper_script = ($temp_dir | path join $"provider_wrapper_(random chars).nu")
    let arg_count = ($args | length)

    let call_line = if $arg_count == 0 {
        $"($function_name) | to json"
    } else if $arg_count == 1 {
        $"($function_name) \(\$args | get 0\) | to json"
    } else if $arg_count == 2 {
        $"($function_name) \(\$args | get 0\) \(\$args | get 1\) | to json"
    } else if $arg_count == 3 {
        $"($function_name) \(\$args | get 0\) \(\$args | get 1\) \(\$args | get 2\) | to json"
    } else if $arg_count == 4 {
        $"($function_name) \(\$args | get 0\) \(\$args | get 1\) \(\$args | get 2\) \(\$args | get 3\) | to json"
    } else if $arg_count == 5 {
        $"($function_name) \(\$args | get 0\) \(\$args | get 1\) \(\$args | get 2\) \(\$args | get 3\) \(\$args | get 4\) | to json"
    } else {
        log-error $"Too many arguments \(($arg_count)\) for provider function" "provider-loader"
        return null
    }

    let script_content = $"
use ($provider_entry.entry_point) *
let args = \(open ($args_file)\)
($call_line)
"
    $script_content | save --force $wrapper_script

    # Execute the wrapper script
    let result = (do --ignore-errors { nu $wrapper_script } | complete)

    # Clean up temp files
    if ($args_file | path exists) { rm -f $args_file }
    if ($wrapper_script | path exists) { rm -f $wrapper_script }

    # Return result if successful, null otherwise
    if $result.exit_code == 0 {
        # Parse output: always try JSON first (handles strings, bools, records, lists)
        # The wrapper script serializes all return values with | to json, so bare JSON
        # strings like "91.98.28.202" must go through from json to strip the quotes.
        let output = ($result.stdout | str trim)
        if ($output | is-empty) {
            null
        } else {
            let parsed = (do -i { $output | from json })
            let value = if ($parsed | is-empty) { $output } else { $parsed }
            log-debug $"($provider_name)::($function_name) → ($value)" "provider-loader"
            $value
        }
    } else {
        log-error $"Provider function call failed: ($result.stderr)" "provider-loader"
        null
    }
}

# Get required provider functions
def get-required-functions [] {
    [
        "get-provider-metadata"
        "query_servers"
        "server_exists"
        "check_server_requirements"
    ]
}

# Validate provider interface compliance
def validate-provider-interface [provider_name: string, provider_instance: record] {
    let required_functions = (get-required-functions)
    mut missing_functions = []
    mut valid = true

    # Check if provider file exists
    let provider_file = $provider_instance.entry_point
    if not ($provider_file | path exists) {
        return {
            valid: false
            missing_functions: ["provider file not found"]
            provider: $provider_name
        }
    }

    # Check each required function
    for func in $required_functions {
        let check_cmd = $"nu -c \"use ($provider_file) *; help commands | where name == '($func)' | length\""
        let check_result = (nu -c $check_cmd | complete)

        if $check_result.exit_code == 0 {
            let func_count = ($check_result.stdout | str trim | into int)
            if $func_count == 0 {
                $missing_functions = ($missing_functions | append $func)
                $valid = false
            }
        } else {
            $missing_functions = ($missing_functions | append $func)
            $valid = false
        }
    }

    {
        valid: $valid
        missing_functions: $missing_functions
        provider: $provider_name
        checked_functions: ($required_functions | length)
        validation_time: (date now)
    }
}

# Load multiple providers
export def load-providers [provider_names: list<string>] {
    mut results = {
        successful: 0
        failed: 0
        total: ($provider_names | length)
        details: []
    }

    for provider_name in $provider_names {
        let result = (load-provider $provider_name)
        if not ($result | is-empty) {
            $results.successful = ($results.successful + 1)
            $results.details = ($results.details | append {
                provider: $provider_name
                status: "success"
                loaded: true
            })
        } else {
            $results.failed = ($results.failed + 1)
            $results.details = ($results.details | append {
                provider: $provider_name
                status: "failed"
                loaded: false
            })
        }
    }

    $results
}

# Check provider health
export def check-provider-health [provider_name: string] {
    let health_check = {
        provider: $provider_name
        available: false
        loadable: false
        interface_valid: false
        metadata_accessible: false
        timestamp: (date now)
    }

    # Check if provider is available
    let available = (is-provider-available $provider_name)
    let updated_health = ($health_check | merge { available: $available })

    if not $available {
        return $updated_health
    }

    # Try to load provider
    let provider_instance = (load-provider $provider_name)
    let loadable = not ($provider_instance | is-empty)
    let updated_health = ($updated_health | merge { loadable: $loadable })

    if not $loadable {
        return $updated_health
    }

    # Check interface validation
    let validation = (validate-provider-interface $provider_name $provider_instance)
    let updated_health = ($updated_health | merge { interface_valid: $validation.valid })

    # Check metadata access
    let provider_entry = (get-provider-entry $provider_name)
    let metadata_cmd = $"nu -c \"use ($provider_entry.entry_point) *; get-provider-metadata\""
    let metadata_result = (nu -c $metadata_cmd | complete)
    let metadata_accessible = ($metadata_result.exit_code == 0)

    $updated_health | merge { metadata_accessible: $metadata_accessible }
}

# Check health of all providers
export def check-all-providers-health [] {
    let providers = (list-providers --available-only)

    $providers | each {|provider|
        check-provider-health $provider.name
    }
}

# Get loader statistics
export def get-loader-stats [] {
    let provider_stats = (get-provider-stats)
    let health_checks = (check-all-providers-health)

    {
        total_providers: $provider_stats.total_providers
        available_providers: $provider_stats.available_providers
        healthy_providers: ($health_checks | where interface_valid == true | length)
        last_check: (date now)
    }
}
