# Provider Command Handlers
# Domain: Provider discovery, installation, removal, validation, and information

# REMOVED: use ../../../lib_provisioning * - causes circular import
use ../../flags.nu *

# Validate identifier is safe from path/command injection
def validate_safe_identifier [id: string] {
    # Returns true if INVALID (contains dangerous patterns)
    let has_slash = ($id | str contains "/")
    let has_dotdot = ($id | str contains "..")
    let starts_slash = ($id | str starts-with "/")
    let has_semicolon = ($id | str contains ";")
    let has_pipe = ($id | str contains "|")
    let has_ampersand = ($id | str contains "&")
    let has_dollar = ($id | str contains "$")
    let has_backtick = ($id | str contains "`")

    if $has_slash or $has_dotdot or $starts_slash or $has_semicolon or $has_pipe or $has_ampersand or $has_dollar or $has_backtick {
        return true
    }
    false
}

# Main providers command handler - Manage infrastructure providers
export def handle_providers [ops: string, flags: record] {
    use ../../../lib_provisioning/module_loader.nu *

    # Parse subcommand and arguments
    let parts = if ($ops | is-not-empty) {
        ($ops | str trim | split row " " | where { |x| ($x | is-not-empty) })
    } else {
        []
    }

    let subcommand = if ($parts | length) > 0 { $parts | get 0 } else { "list" }
    let args = if ($parts | length) > 1 { $parts | skip 1 } else { [] }

    match $subcommand {
        "list" => { handle_providers_list $flags $args }
        "info" => { handle_providers_info $args $flags }
        "install" => { handle_providers_install $args $flags }
        "remove" => { handle_providers_remove $args $flags }
        "installed" => { handle_providers_installed $args $flags }
        "validate" => { handle_providers_validate $args $flags }
        "help" | "-h" | "--help" => { show_providers_help }
        _ => {
            print $"❌ Unknown providers subcommand: ($subcommand)"
            print ""
            show_providers_help
            exit 1
        }
    }
}

# List all available providers
def handle_providers_list [flags: record, args: list] {
    use ../../../lib_provisioning/module_loader.nu *

    _print $"(_ansi green)PROVIDERS(_ansi reset) list: \n"

    # Parse flags
    let show_nickel = ($args | any { |x| $x == "--nickel" })
    let format_idx = ($args | enumerate | where item == "--format" | get 0?.index | default (-1))
    let format = if $format_idx >= 0 and ($args | length) > ($format_idx + 1) {
        $args | get ($format_idx + 1)
    } else {
        "table"
    }
    let no_cache = ($args | any { |x| $x == "--no-cache" })

    # Get providers using cached Nickel module loader
    let providers = if $no_cache {
        (discover-nickel-modules "providers")
    } else {
        (discover-nickel-modules-cached "providers")
    }

    match $format {
        "json" => {
            _print ($providers | to json) "json" "result" "table"
        }
        "yaml" => {
            _print ($providers | to yaml) "yaml" "result" "table"
        }
        _ => {
            # Table format - show summary or full with --nickel
            if $show_nickel {
                _print ($providers | to json) "json" "result" "table"
            } else {
                # Show simplified table
                let simplified = ($providers | each {|p|
                    {name: $p.name, type: $p.type, version: $p.version}
                })
                _print ($simplified | to json) "json" "result" "table"
            }
        }
    }
}

# Show detailed provider information
def handle_providers_info [args: list, flags: record] {
    use ../../../lib_provisioning/module_loader.nu *

    if ($args | is-empty) {
        print "❌ Provider name required"
        print "Usage: provisioning providers info <provider> [--nickel] [--no-cache]"
        exit 1
    }

    let provider_name = $args | get 0

    # Validate provider name
    if (validate_safe_identifier $provider_name) {
        error make { msg: "Invalid provider name - contains invalid characters" }
    }

    let show_nickel = ($args | any { |x| $x == "--nickel" })
    let no_cache = ($args | any { |x| $x == "--no-cache" })

    print $"(_ansi blue_bold)📋 Provider Information: ($provider_name)(_ansi reset)"
    print ""

    let providers = if $no_cache {
        (discover-nickel-modules "providers")
    } else {
        (discover-nickel-modules-cached "providers")
    }
    let provider_info = ($providers | where name == $provider_name)

    if ($provider_info | is-empty) {
        print $"❌ Provider not found: ($provider_name)"
        exit 1
    }

    let info = ($provider_info | first)

    print $"  Name: ($info.name)"
    print $"  Type: ($info.type)"
    print $"  Path: ($info.path)"
    print $"  Has Nickel: ($info.has_nickel)"

    if $show_nickel and $info.has_nickel {
        print ""
        print "  (_ansi cyan_bold)Nickel Module:(_ansi reset)"
        print $"    Module Name: ($info.module_name)"
        print $"    Nickel Path: ($info.schema_path)"
        print $"    Version: ($info.version)"
        print $"    Edition: ($info.edition)"

        # Check for nickel.mod file
        let decl_mod = ($info.schema_path | path join "nickel.mod")
        if ($decl_mod | path exists) {
            print ""
            print $"  (_ansi cyan_bold)nickel.mod content:(_ansi reset)"
            open $decl_mod | lines | each {|line| print $"    ($line)"}
        }
    }

    print ""
}

# Install provider for infrastructure
def handle_providers_install [args: list, flags: record] {
    use ../../../lib_provisioning/module_loader.nu *

    if ($args | length) < 2 {
        print "❌ Provider name and infrastructure required"
        print "Usage: provisioning providers install <provider> <infra> [--version <v>]"
        exit 1
    }

    let provider_name = $args | get 0
    let infra_name = $args | get 1

    # Validate provider and infrastructure names
    if (validate_safe_identifier $provider_name) {
        error make { msg: "Invalid provider name - contains invalid characters" }
    }
    if (validate_safe_identifier $infra_name) {
        error make { msg: "Invalid infrastructure name - contains invalid characters" }
    }

    # Extract version flag if present
    let version_idx = ($args | enumerate | where item == "--version" | get 0?.index | default (-1))
    let version = if $version_idx >= 0 and ($args | length) > ($version_idx + 1) {
        $args | get ($version_idx + 1)
    } else {
        "0.0.1"
    }

    # Resolve infrastructure path
    let infra_path = (resolve_infra_path $infra_name)

    if ($infra_path | is-empty) {
        print $"❌ Infrastructure not found: ($infra_name)"
        exit 1
    }

    # Install provider
    install-provider $provider_name $infra_path --version $version

    print ""
    print $"(_ansi yellow_bold)💡 Next steps:(_ansi reset)"
    print $"  1. Check the manifest: ($infra_path)/providers.manifest.yaml"
    print $"  2. Update server definitions to use ($provider_name)"
    print $"  3. Run: nickel run defs/servers.ncl"
}

# Remove provider from infrastructure
def handle_providers_remove [args: list, flags: record] {
    use ../../../lib_provisioning/module_loader.nu *

    if ($args | length) < 2 {
        print "❌ Provider name and infrastructure required"
        print "Usage: provisioning providers remove <provider> <infra> [--force]"
        exit 1
    }

    let provider_name = $args | get 0
    let infra_name = $args | get 1

    # Validate provider and infrastructure names
    if (validate_safe_identifier $provider_name) {
        error make { msg: "Invalid provider name - contains invalid characters" }
    }
    if (validate_safe_identifier $infra_name) {
        error make { msg: "Invalid infrastructure name - contains invalid characters" }
    }

    let force = ($args | any { |x| $x == "--force" })

    # Resolve infrastructure path
    let infra_path = (resolve_infra_path $infra_name)

    if ($infra_path | is-empty) {
        print $"❌ Infrastructure not found: ($infra_name)"
        exit 1
    }

    # Confirmation unless forced
    if not $force {
        print $"(_ansi yellow)⚠️  This will remove provider ($provider_name) from ($infra_name)(_ansi reset)"
        print "   Nickel dependencies will be updated."
        let response = (input "Continue? (y/N): ")

        if ($response | str downcase) != "y" {
            print "❌ Cancelled"
            return
        }
    }

    # Remove provider
    remove-provider $provider_name $infra_path
}

# List installed providers for infrastructure
def handle_providers_installed [args: list, flags: record] {
    if ($args | is-empty) {
        print "❌ Infrastructure name required"
        print "Usage: provisioning providers installed <infra> [--format <fmt>]"
        exit 1
    }

    let infra_name = $args | get 0

    # Validate infrastructure name
    if (validate_safe_identifier $infra_name) {
        error make { msg: "Invalid infrastructure name - contains invalid characters" }
    }

    # Parse format flag
    let format_idx = ($args | enumerate | where item == "--format" | get 0?.index | default (-1))
    let format = if $format_idx >= 0 and ($args | length) > ($format_idx + 1) {
        $args | get ($format_idx + 1)
    } else {
        "table"
    }

    # Resolve infrastructure path
    let infra_path = (resolve_infra_path $infra_name)

    if ($infra_path | is-empty) {
        print $"❌ Infrastructure not found: ($infra_name)"
        exit 1
    }

    let manifest_path = ($infra_path | path join "providers.manifest.yaml")

    if not ($manifest_path | path exists) {
        print $"❌ No providers.manifest.yaml found in ($infra_name)"
        exit 1
    }

    let manifest = (open $manifest_path)
    let providers = if ($manifest | get providers? | is-not-empty) {
        $manifest | get providers
    } else if ($manifest | get loaded_providers? | is-not-empty) {
        $manifest | get loaded_providers
    } else {
        []
    }

    print $"(_ansi blue_bold)📦 Installed providers for ($infra_name):(_ansi reset)"
    print ""

    match $format {
        "json" => {
            _print ($providers | to json) "json" "result" "table"
        }
        "yaml" => {
            _print ($providers | to yaml) "yaml" "result" "table"
        }
        _ => {
            _print ($providers | to json) "json" "result" "table"
        }
    }
}

# Validate provider installation
def handle_providers_validate [args: list, flags: record] {
    use ../../../lib_provisioning/module_loader.nu *

    if ($args | is-empty) {
        print "❌ Infrastructure name required"
        print "Usage: provisioning providers validate <infra> [--no-cache]"
        exit 1
    }

    let infra_name = $args | get 0

    # Validate infrastructure name
    if (validate_safe_identifier $infra_name) {
        error make { msg: "Invalid infrastructure name - contains invalid characters" }
    }

    let no_cache = ($args | any { |x| $x == "--no-cache" })

    print $"(_ansi blue_bold)🔍 Validating providers for ($infra_name)...(_ansi reset)"
    print ""

    # Resolve infrastructure path
    let infra_path = (resolve_infra_path $infra_name)

    if ($infra_path | is-empty) {
        print $"❌ Infrastructure not found: ($infra_name)"
        exit 1
    }

    # Refactored from mutable to immutable accumulation (Rule 3)
    let validation_result = (
        # Check manifest exists
        let manifest_path = ($infra_path | path join "providers.manifest.yaml");
        let initial = {has_manifest: false, errors: []};

        if not ($manifest_path | path exists) {
            $initial | upsert has_manifest false | upsert errors ["providers.manifest.yaml not found"]
        } else {
            # Check each provider in manifest
            let manifest = (open $manifest_path)
            let providers = ($manifest | get providers? | default [])

            # Load providers once using cache
            let all_providers = if $no_cache {
                (discover-nickel-modules "providers")
            } else {
                (discover-nickel-modules-cached "providers")
            }

            # Use reduce --fold to accumulate validation errors (Rule 3)
            let validation = ($providers | reduce --fold {errors: []} {|provider, result|
                print $"  Checking ($provider.name)..."

                # Check if provider exists in cached list
                let available = ($all_providers | where name == $provider.name)

                if ($available | is-empty) {
                    $result | upsert errors ($result.errors | append $"Provider not found: ($provider.name)")
                    print $"    ❌ Not found in extensions"
                } else {
                    let provider_info = ($available | first)

                    # Check if symlink exists
                    let modules_dir = ($infra_path | path join ".nickel-modules")
                    let link_path = ($modules_dir | path join $provider_info.module_name)

                    if not ($link_path | path exists) {
                        $result | upsert errors ($result.errors | append $"Symlink missing: ($link_path)")
                        print $"    ❌ Symlink not found"
                    } else {
                        print $"    ✓ OK"
                        $result
                    }
                }
            })

            # Check nickel.mod
            let nickel_mod_path = ($infra_path | path join "nickel.mod")
            let final_errors = if not ($nickel_mod_path | path exists) {
                ($validation.errors | append "nickel.mod not found")
            } else {
                $validation.errors
            }

            $initial | upsert has_manifest true | upsert errors $final_errors
        }
    )

    print ""

    # Report results
    if ($validation_result.errors | is-empty) {
        print "(_ansi green)✅ Validation passed - all providers correctly installed(_ansi reset)"
    } else {
        print "(_ansi red)❌ Validation failed:(_ansi reset)"
        $validation_result.errors | each {|error| print $"  • ($error)"}
        exit 1
    }
}

# Helper: Resolve infrastructure path
def resolve_infra_path [infra: string] {
    if ($infra | path exists) {
        return $infra
    }

    # Try workspace/infra path
    let workspace_path = $"workspace/infra/($infra)"
    if ($workspace_path | path exists) {
        return $workspace_path
    }

    # Try absolute workspace path
    let proj_root = ($env.PROVISIONING_ROOT? | default ($env.HOME | path join "project-provisioning"))
    let abs_workspace_path = ($proj_root | path join "workspace" "infra" $infra)
    if ($abs_workspace_path | path exists) {
        return $abs_workspace_path
    }

    return ""
}

# Show providers help
def show_providers_help [] {
    print $"
(_ansi cyan_bold)╔══════════════════════════════════════════════════╗(_ansi reset)
(_ansi cyan_bold)║(_ansi reset)  📦 PROVIDER MANAGEMENT                         (_ansi cyan_bold)║(_ansi reset)
(_ansi cyan_bold)╚══════════════════════════════════════════════════╝(_ansi reset)

(_ansi green_bold)[Available Providers](_ansi reset)
  (_ansi blue)provisioning providers list [--nickel] [--format <fmt>](_ansi reset)
    List all available providers
    Formats: table (default value), json, yaml

  (_ansi blue)provisioning providers info <provider> [--nickel](_ansi reset)
    Show detailed provider information with optional Nickel details

(_ansi green_bold)[Provider Installation](_ansi reset)
  (_ansi blue)provisioning providers install <provider> <infra> [--version <v>](_ansi reset)
    Install provider for an infrastructure
    Default version: 0.0.1

  (_ansi blue)provisioning providers remove <provider> <infra> [--force](_ansi reset)
    Remove provider from infrastructure
    --force skips confirmation prompt

  (_ansi blue)provisioning providers installed <infra> [--format <fmt>](_ansi reset)
    List installed providers for infrastructure
    Formats: table (default value), json, yaml

  (_ansi blue)provisioning providers validate <infra>(_ansi reset)
    Validate provider installation and configuration

(_ansi green_bold)EXAMPLES(_ansi reset)

  # List all providers
  provisioning providers list

  # Show Nickel module details
  provisioning providers info upcloud --nickel

  # Install provider
  provisioning providers install upcloud myinfra

  # List installed providers
  provisioning providers installed myinfra

  # Validate installation
  provisioning providers validate myinfra

  # Remove provider
  provisioning providers remove aws myinfra --force

(_ansi default_dimmed)💡 Use 'provisioning help providers' for more information(_ansi reset)
"
}
