# Nickel Packaging Library
# Functions for packaging and distributing Nickel modules
# Author: JesusPerezLorenzo
# Date: 2025-09-29

use config/accessor.nu *
use utils *

# Package core provisioning Nickel schemas
export def "pack-core" [
    --output: string = ""    # Output directory (from config if not specified)
    --version: string = ""   # Version override
] {
    _print "📦 Packaging core provisioning schemas..."

    # Get config
    let dist_config = (get-distribution-config)
    let nickel_config = (get-nickel-config)

    # Get pack path from config or use provided output
    let pack_path = if ($output | is-empty) {
        $dist_config.pack_path
    } else {
        $output
    }

    # Get core module path from config
    let base_path = ($env.PROVISIONING_CONFIG? | default ($env.PROVISIONING? | default ""))
    if ($base_path | is-empty) {
        error make {msg: "PROVISIONING_CONFIG or PROVISIONING environment variable must be set"}
    }
    let core_module = ($nickel_config.core_module | str replace --all "{{paths.base}}" $base_path)
    let core_path = $core_module

    # Get version from config or use provided
    let core_version = if ($version | is-empty) {
        $nickel_config.core_version
    } else {
        $version
    }

    # Ensure pack_path exists and get absolute path
    mkdir $pack_path
    let abs_pack_path = ($pack_path | path expand)

    # Change to the Nickel module directory to run packaging from inside
    cd $core_path

    # Check if nickel mod pkg is supported
    let help_result = (^nickel mod --help | complete)
    let has_pkg = ($help_result.stdout | str contains "pkg")

    if not $has_pkg {
        _print $"  ⚠️  Nickel does not support 'nickel mod pkg'"
        _print $"  💡 Please upgrade to Nickel 0.11.3+ for packaging support"
        error make {msg: "Nickel packaging not supported in this version"}
    }

    # Run nickel mod pkg from inside the module directory with --target
    _print $"  Running: nickel mod pkg --target ($abs_pack_path)"
    let result = (^nickel mod pkg --target $abs_pack_path | complete)

    if $result.exit_code != 0 {
        error make {msg: $"Failed to package core: ($result.stderr)"}
    }

    _print $"  ✓ Nickel packaging completed"

    # Find the generated package in the target directory (nickel creates .tar files)
    cd $abs_pack_path
    let package_files = (glob *.tar)

    if ($package_files | is-empty) {
        _print $"  ⚠️  No .tar file created in ($abs_pack_path)"
        _print $"  💡 Check if nickel.mod is properly configured"
        error make {msg: "Nickel packaging did not create output file"}
    }

    let package_file = ($package_files | first)
    _print $"  ✓ Package: ($package_file)"

    # Generate metadata
    generate-package-metadata $pack_path "provisioning_core" $core_version $package_file

    return $package_file
}

# Package a provider module
export def "pack-provider" [
    provider: string,         # Provider name
    --output: string = ""     # Output directory
    --version: string = ""    # Version override
] {
    _print $"📦 Packaging provider: ($provider)..."

    # Get config
    let dist_config = (get-distribution-config)
    let pack_path = if ($output | is-empty) {
        $dist_config.pack_path
    } else {
        $output
    }

    # Get provider path from config
    let config = (get-config)
    let providers_base = ($config | get paths.providers)
    let provider_path = ($providers_base | path join $provider "nickel")

    if not ($provider_path | path exists) {
        error make {msg: $"Provider not found: ($provider) at ($provider_path)"}
    }

    # Ensure pack_path exists and get absolute path
    mkdir $pack_path
    let abs_pack_path = ($pack_path | path expand)

    # Change to the provider Nickel directory to run packaging from inside
    cd $provider_path

    # Run nickel mod pkg with target directory
    _print $"  Running: nickel mod pkg --target ($abs_pack_path)"
    let result = (^nickel mod pkg --target $abs_pack_path | complete)

    if $result.exit_code != 0 {
        error make {msg: $"Failed to package provider: ($result.stderr)"}
    }

    _print $"  ✓ Provider ($provider) packaged"

    # Find the generated package in the target directory
    cd $abs_pack_path
    let package_files = (glob *.tar)

    if ($package_files | is-empty) {
        error make {msg: "No package file found"}
    }

    let package_file = ($package_files | first)
    _print $"  ✓ Package: ($package_file)"

    # Read version from nickel.mod if not provided
    let pkg_version = if ($version | is-empty) {
        let mod_file = ($provider_path | path join "nickel.mod")
        if ($mod_file | path exists) {
            parse-nickel-version $mod_file
        } else {
            "0.0.1"
        }
    } else {
        $version
    }

    # Generate metadata
    generate-package-metadata $pack_path $"($provider)_prov" $pkg_version $package_file

    return $package_file
}

# Package all discovered providers
export def "pack-all-providers" [
    --output: string = ""     # Output directory
] {
    use module_loader.nu *

    let dist_config = (get-distribution-config)
    let pack_path = if ($output | is-empty) {
        $dist_config.pack_path
    } else {
        $output
    }

    _print "📦 Packaging all providers..."

    let providers = (discover-nickel-modules "providers")

    mut packaged = []

    for provider in $providers {
        # Use error handling without complete since pack-provider is internal
        let pack_result = (do -i {
            pack-provider $provider.name --output $pack_path
        })

        if ($pack_result | is-not-empty) and not ($pack_result | describe | str contains "error") {
            $packaged = ($packaged | append {name: $provider.name, path: $pack_result, status: "success"})
        } else {
            _print $"  ❌ Failed to package ($provider.name)"
            $packaged = ($packaged | append {name: $provider.name, path: "", status: "failed"})
        }
    }

    _print ""
    _print $"✅ Packaged ($packaged | where status == success | length)/($packaged | length) providers"

    return $packaged
}

# Generate package metadata
def "generate-package-metadata" [
    pack_path: string,
    module_name: string,
    version: string,
    package_file: string
] {
    let dist_config = (get-distribution-config)
    let registry_path = $dist_config.registry_path
    mkdir $registry_path

    let metadata_file = ($registry_path | path join $"($module_name).json")

    let dist_metadata = ($dist_config | get metadata)

    let metadata = {
        name: $module_name
        version: $version
        package_file: $package_file
        created: (date now | format date "%Y-%m-%d %H:%M:%S")
        maintainer: $dist_metadata.maintainer
        repository: $dist_metadata.repository
        license: $dist_metadata.license
        homepage: $dist_metadata.homepage
    }

    $metadata | to json | save -f $metadata_file

    _print $"  ✓ Metadata: ($metadata_file)"
}

# Parse version from nickel.mod
def "parse-nickel-version" [
    mod_path: string
] {
    let content = (open $mod_path)
    let lines = ($content | lines)

    for line in $lines {
        if ($line | str starts-with "version") {
            let version = ($line | parse 'version = "{version}"' | get version.0? | default "0.0.1")
            return $version
        }
    }

    return "0.0.1"
}

# List packaged modules
export def "list-packages" [
    --format: string = "table"  # Output format
] {
    let dist_config = (get-distribution-config)
    let pack_path = $dist_config.pack_path

    if not ($pack_path | path exists) {
        _print "No packages found"
        return []
    }

    # Look for .tar files
    cd $pack_path
    let tar_files = (glob *.tar)
    let packages = $tar_files | each {|file|
        let stats = (ls $file | first)
        {
            name: ($file | path basename | str replace ".tar" "")
            path: $file
            size: $stats.size
            modified: $stats.modified
        }
    }

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

# Clean old packages
export def "clean-packages" [
    --keep-latest: int = 3    # Number of latest versions to keep
    --dry-run                 # Show what would be deleted
] {
    let dist_config = (get-distribution-config)
    let pack_path = $dist_config.pack_path

    if not ($pack_path | path exists) {
        _print "No packages directory found"
        return
    }

    _print $"🗑️  Cleaning packages \(keeping latest ($keep_latest)\)..."

    cd $pack_path
    let tar_files = (glob *.tar)
    if ($tar_files | is-empty) {
        _print "  No packages to clean"
        return
    }

    let packages = ($tar_files | each {|file|
        let stats = (ls $file | first)
        {
            name: $file
            modified: $stats.modified
        }
    } | sort-by modified --reverse)

    let to_delete = ($packages | skip $keep_latest)

    if ($to_delete | is-empty) {
        _print "  Nothing to clean"
        return
    }

    # Get registry path for metadata cleanup
    let dist_config = (get-distribution-config)
    let registry_path = $dist_config.registry_path

    for pkg in $to_delete {
        let pkg_basename = ($pkg.name | path basename)

        # Extract package name without version (e.g., "aws_prov_0.0.1.tar" -> "aws_prov")
        let pkg_name = ($pkg_basename | str replace -r '_\d+\.\d+\.\d+\.tar$' '')
        let metadata_file = ($registry_path | path join $"($pkg_name).json")

        if $dry_run {
            _print $"  [DRY RUN] Would delete package: ($pkg_basename)"
            if ($metadata_file | path exists) {
                _print $"  [DRY RUN] Would delete metadata: ($metadata_file | path basename)"
            }
        } else {
            rm $pkg.name
            _print $"  ✓ Deleted package: ($pkg_basename)"

            # Also delete corresponding metadata if it exists
            if ($metadata_file | path exists) {
                rm $metadata_file
                _print $"  ✓ Deleted metadata: ($metadata_file | path basename)"
            }
        }
    }

    let deleted_count = ($to_delete | length)
    _print $"✅ Cleaned ($deleted_count) old packages"
}

# Remove specific package and its metadata
export def "remove-package" [
    package_name: string,     # Package name (e.g., "aws_prov", "upcloud_prov", "provisioning_core")
    --force = false           # Skip confirmation prompt
] {
    _print $"🗑️  Removing package: ($package_name)"

    let dist_config = (get-distribution-config)
    let pack_path = $dist_config.pack_path
    let registry_path = $dist_config.registry_path

    # Find all versions of this package
    cd $pack_path
    let package_pattern = $"($package_name)*.tar"
    let matching_packages = (glob $package_pattern)

    if ($matching_packages | is-empty) {
        _print $"  ❌ No packages found matching: ($package_name)"
        return
    }

    # Show what will be deleted
    _print ""
    _print "  Packages to remove:"
    for pkg in $matching_packages {
        _print $"    • ($pkg | path basename)"
    }

    # Check for metadata
    let metadata_file = ($registry_path | path join $"($package_name).json")
    if ($metadata_file | path exists) {
        _print $"    • ($metadata_file | path basename) (metadata)"
    }

    # Confirmation unless forced
    if not $force {
        _print ""
        let response = (input $"⚠️  Remove ($matching_packages | length) package\(s\) and metadata? \(y/N\): ")

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

    # Delete packages
    _print ""
    for pkg in $matching_packages {
        rm $pkg
        _print $"  ✓ Deleted: ($pkg | path basename)"
    }

    # Delete metadata
    if ($metadata_file | path exists) {
        rm $metadata_file
        _print $"  ✓ Deleted: ($metadata_file | path basename)"
    }

    _print ""
    _print $"✅ Removed package: ($package_name)"
}

# Clean ALL packages and metadata
export def "clean-all-packages" [
    --dry-run = false,        # Show what would be deleted
    --force = false           # Skip confirmation prompt
] {
    _print "🗑️  Cleaning ALL packages..."

    let dist_config = (get-distribution-config)
    let pack_path = $dist_config.pack_path
    let registry_path = $dist_config.registry_path

    if not ($pack_path | path exists) {
        _print "  No packages directory found"
        return
    }

    # Get all packages
    cd $pack_path
    let all_packages = (glob *.tar)

    if ($all_packages | is-empty) {
        _print "  No packages to clean"
        return
    }

    # Get all metadata files
    cd $registry_path
    let all_metadata = (glob *.json)

    # Show what will be deleted
    _print ""
    _print $"  Found ($all_packages | length) package\(s\) and ($all_metadata | length) metadata file\(s\)"

    if not $dry_run and not $force {
        _print ""
        let response = (input $"⚠️  Delete ALL packages and metadata? \(y/N\): ")

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

    # Delete packages
    _print ""
    for pkg in $all_packages {
        if $dry_run {
            _print $"  [DRY RUN] Would delete: ($pkg | path basename)"
        } else {
            cd $pack_path
            rm $pkg
            _print $"  ✓ Deleted: ($pkg | path basename)"
        }
    }

    # Delete metadata
    for meta in $all_metadata {
        if $dry_run {
            _print $"  [DRY RUN] Would delete: ($meta | path basename)"
        } else {
            cd $registry_path
            rm $meta
            _print $"  ✓ Deleted: ($meta | path basename)"
        }
    }

    _print ""
    if $dry_run {
        _print $"[DRY RUN] Would clean ($all_packages | length) packages and ($all_metadata | length) metadata files"
    } else {
        _print $"✅ Cleaned ($all_packages | length) packages and ($all_metadata | length) metadata files"
    }
}
