#!/usr/bin/env nu

# Provider Loader System
# Loads selected providers into workspace or infrastructure (Layer 2 or Layer 3)

use discover.nu *
use ../lib_provisioning/layers/resolver.nu *

# Load providers into workspace or infrastructure
export def load-providers [
    target_path: string,
    providers: list<string>,
    --force = false  # Overwrite existing
    --level: string = "auto"  # "workspace", "infra", or "auto"
] {
    # Determine target layer
    let layer_info = (determine-layer --workspace $target_path --infra $target_path --level $level)
    let load_path = $layer_info.path

    print $"Loading providers into ($layer_info.layer) layer: ($load_path)"

    # Validate target path
    if not ($load_path | path exists) {
        error make { msg: $"Target path not found: ($load_path)" }
    }

    # Validate providers exist in system
    let validation = (validate-providers $providers)
    if not $validation.valid {
        error make { msg: $"Missing providers: ($validation.missing)" }
    }

    # Create providers directory at target layer
    let providers_dir = ($load_path | path join ".providers")
    mkdir $providers_dir

    # Load each provider
    let results = ($providers | each { |name|
        load-single-provider $load_path $name $force $layer_info.layer
    })

    # Generate imports file
    generate-providers-imports $load_path $providers $layer_info.layer

    # Create/update manifest
    update-providers-manifest $load_path $providers $layer_info.layer

    {
        target: $load_path
        layer: $layer_info.layer
        loaded: ($results | where status == "success" | get name)
        failed: ($results | where status == "error" | get name)
        summary: $"Loaded (($results | where status == 'success' | length)) of (($providers | length)) providers at ($layer_info.layer) layer"
    }
}

# Load a single provider
def load-single-provider [target_path: string, name: string, force: bool, layer: string] {
    let result = (do {
        let provider_info = (get-provider-info $name)
        let target_dir = ($target_path | path join ".providers" $name)

        # Check if already exists
        if ($target_dir | path exists) and (not $force) {
            print $"⚠️  Provider ($name) already loaded at ($layer) layer (add --force flag to overwrite)"
            return {
                name: $name
                status: "skipped"
                message: "already exists"
            }
        }

        # Copy Nickel files and directories
        mkdir $target_dir
        let source_items = (ls $provider_info.schema_path | get name)
        for $item in $source_items {
            cp -r $item $target_dir
        }

        print $"✅ Loaded provider: ($name)"
        {
            name: $name
            status: "success"
            path: $target_dir
            version: $provider_info.version
            type: $provider_info.provider_type
        }
    } | complete)

    if $result.exit_code != 0 {
        print $"❌ Failed to load provider ($name): ($result.stderr)"
        {
            name: $name
            status: "error"
            error: $result.stderr
        }
    } else {
        $result.stdout | from json
    }
}

# Generate providers.ncl import file
def generate-providers-imports [target_path: string, providers: list<string>, layer: string] {
    # Generate individual imports for each provider
    let imports = ($providers | each { |name|
        # Check provider structure and import appropriately
        let main_files = [
            ($target_path | path join ".providers" $name ($"provision_($name).ncl"))
            ($target_path | path join ".providers" $name ($"server_($name).ncl"))
            ($target_path | path join ".providers" $name ($"defaults_($name).ncl"))
            ($target_path | path join ".providers" $name ($name + ".ncl"))
        ]

        # Find the main provider file
        let main_file = ($main_files | where { |file| $file | path exists } | first)
        if ($main_file | is-empty) {
            $"import .providers.($name) as ($name)_provider"
        } else {
            let file_stem = ($main_file | path basename | str replace '.ncl' '')
            $"import .providers.($name).($file_stem) as ($name)_provider"
        }
    } | str join "\n")

    # Generate schema exports
    let exports = ($providers | each { |name|
        $"    ($name): ($name)_provider"
    } | str join ",\n")

    # Create the complete imports file
    let content = $"# Auto-generated provider imports ($layer) layer
# Generated: (date now | format date '%Y-%m-%d %H:%M:%S')
# Loaded providers: ($providers | str join ', ')

($imports)

# Export all loaded provider schemas
providers = {
($exports)
}

providers"

    # Save the imports file
    $content | save -f ($target_path | path join "providers.ncl")

    # Also create individual alias files for easier direct imports
    for $name in $providers {
        let alias_content = $"# Provider alias for ($name)
# Generated: (date now | format date '%Y-%m-%d %H:%M:%S')
# Layer: ($layer)

import .providers.($name) as ($name)

# Re-export for convenience
($name)"
        $alias_content | save -f ($target_path | path join $"provider_($name).ncl")
    }
}

# Update providers manifest
def update-providers-manifest [target_path: string, providers: list<string>, layer: string] {
    let manifest_dir = ($target_path | path join ".manifest")
    mkdir $manifest_dir
    let manifest_path = ($manifest_dir | path join "providers.yaml")
    let existing = if ($manifest_path | path exists) {
        open $manifest_path
    } else {
        {}
    }

    let provider_entries = ($providers | each { |name|
        let info = (get-provider-info $name)
        {
            name: $name
            version: $info.version
            type: $info.provider_type
            layer: $layer
            loaded_at: (date now | format date '%Y-%m-%d %H:%M:%S')
            source_path: $info.schema_path
        }
    })

    let manifest = {
        loaded_providers: $provider_entries
        last_updated: (date now | format date '%Y-%m-%d %H:%M:%S')
        target_path: $target_path
        layer: $layer
    }

    $manifest | to yaml | save -f $manifest_path
}

# Remove provider from workspace
export def unload-provider [workspace: string, name: string] {
    let target_dir = ($workspace | path join ".providers" $name)

    if not ($target_dir | path exists) {
        error make { msg: $"Provider ($name) not loaded in workspace" }
    }

    rm -rf $target_dir

    # Update manifest and imports
    let manifest_path = ($workspace | path join "providers.manifest.yaml")
    if ($manifest_path | path exists) {
        let manifest = (open $manifest_path)
        let updated_providers = ($manifest.loaded_providers | where name != $name)

        if ($updated_providers | is-empty) {
            rm $manifest_path
            rm ($workspace | path join "providers.ncl")
        } else {
            let updated_manifest = ($manifest | update loaded_providers $updated_providers)
            $updated_manifest | to yaml | save $manifest_path

            # Regenerate imports
            let names = ($updated_providers | get name)
            # Determine layer from manifest or default to workspace
            let layer = ($manifest.layer? | default "workspace")
            generate-providers-imports $workspace $names $layer
        }
    }

    print $"✅ Unloaded provider: ($name)"
    {
        name: $name
        status: "unloaded"
        workspace: $workspace
    }
}

# List loaded providers in workspace
export def list-loaded-providers [workspace: string] {
    let manifest_path = ($workspace | path join "providers.manifest.yaml")

    if not ($manifest_path | path exists) {
        return []
    }

    let manifest = (open $manifest_path)
    $manifest.loaded_providers? | default []
}

# Set default provider for workspace
export def set-default-provider [workspace: string, name: string] {
    # Validate provider is loaded
    let loaded = (list-loaded-providers $workspace)
    let provider_loaded = ($loaded | where name == $name | length) > 0

    if not $provider_loaded {
        error make { msg: $"Provider ($name) not loaded in workspace. Load it first." }
    }

    # Update workspace configuration
    let config_path = ($workspace | path join "workspace.config.toml")
    let config = if ($config_path | path exists) {
        open $config_path | from toml
    } else {
        {}
    }

    let updated_config = ($config | upsert default_provider $name)
    $updated_config | to toml | save $config_path

    print $"✅ Set default provider: ($name)"
    {
        workspace: $workspace
        default_provider: $name
        status: "updated"
    }
}
