#!/usr/bin/env nu

# Tarball creation tool - creates distribution archives for different platforms
#
# Creates:
# - Source distributions (.tar.gz, .zip)
# - Binary distributions by platform
# - Complete distributions with all components
# - Customized distributions based on feature sets

use std log

def main [
    --dist-dir: string = "dist"                 # Distribution directory to package
    --output-dir: string = "packages"           # Output directory for archives
    --format: string = "tar.gz"                 # Archive format: tar.gz, zip, both
    --platform: string = "all"                  # Target platforms: linux, macos, windows, all
    --variant: string = "complete"              # Distribution variant: complete, minimal, custom
    --version: string = ""                      # Version string (auto-detected if empty)
    --compression-level: int = 6                # Compression level 1-9
    --exclude: string = ""                      # Comma-separated patterns to exclude
    --verbose                                   # Enable verbose logging
    --checksum                                   # Generate checksums for archives
]
{

    let repo_root = ($env.PWD | path dirname | path dirname | path dirname)
    let dist_root = ($dist_dir | path expand)
    let output_root = ($output_dir | path expand)

    # Detect version if not provided
    let detected_version = if $version == "" {
        detect_version $repo_root
    } else {
        $version
    }

    let package_config = {
        dist_dir: $dist_root
        output_dir: $output_root
        formats: ($format | if $in == "both" { ["tar.gz", "zip"] } else { [$format] })
        platforms: (if $platform == "all" { ["linux", "macos", "windows"] } else { [$platform] })
        variant: $variant
        version: $detected_version
        compression_level: $compression_level
        exclude_patterns: ($exclude | if $in == "" { [] } else { $in | split row "," | each { str trim } })
        verbose: $verbose
        checksum: $checksum
    }

    log info $"Starting tarball creation with config: ($package_config)"

    # Validate distribution directory
    if not ($dist_root | path exists) {
        log error $"Distribution directory does not exist: ($dist_root)"
        exit 1
    }

    # Ensure output directory exists
    mkdir $output_root

    let package_results = []

    # Create packages for each platform and format combination
    for platform in $package_config.platforms {
        for format in $package_config.formats {
            let package_result = create_platform_package $platform $format $package_config $repo_root
            let package_results = ($package_results | append $package_result)
        }
    }

    # Generate checksums if requested
    let checksum_result = if $package_config.checksum {
        generate_checksums $package_results $package_config
    } else {
        { status: "skipped", checksums: [] }
    }

    # Create package manifest
    create_package_manifest $package_results $checksum_result $package_config $repo_root

    let summary = {
        total_packages: ($package_results | length)
        successful_packages: ($package_results | where status == "success" | length)
        failed_packages: ($package_results | where status == "failed" | length)
        total_size: ($package_results | where status == "success" | get archive_size | math sum)
        checksum_result: $checksum_result
        package_config: $package_config
        results: $package_results
    }

    if $summary.failed_packages > 0 {
        log error $"Package creation completed with ($summary.failed_packages) failures"
        exit 1
    } else {
        log info $"Package creation completed successfully - ($summary.successful_packages) packages created"
    }

    return $summary
}

# Detect version from git or other sources
def detect_version [repo_root: string]
{
    try {
        cd $repo_root

        # Try git describe first
        let git_version = (git describe --tags --always --dirty 2>/dev/null | complete)
        if $git_version.exit_code == 0 and ($git_version.stdout | str trim) != "" {
            return ($git_version.stdout | str trim)
        }

        # Try git rev-parse for short hash
        let git_hash = (git rev-parse --short HEAD 2>/dev/null | complete)
        if $git_hash.exit_code == 0 {
            return $"dev-($git_hash.stdout | str trim)"
        }

        # Fallback to date-based version
        return $"dev-(date now | format date "%Y%m%d")"

    } catch {
        return "dev-unknown"
    }
}

# Create package for a specific platform and format
def create_platform_package [
    platform: string
    format: string
    package_config: record
    repo_root: string
]
{
    log info $"Creating ($format) package for ($platform)..."

    let start_time = (date now)
    let package_name = $"provisioning-($package_config.version)-($platform)-($package_config.variant)"

    try {
        # Prepare package directory
        let package_dir = prepare_package_directory $platform $package_config $package_name

        # Create archive
        let archive_result = match $format {
            "tar.gz" => { create_tar_archive $package_dir $package_config $package_name }
            "zip" => { create_zip_archive $package_dir $package_config $package_name }
            _ => {
                log error $"Unsupported format: ($format)"
                { status: "failed", reason: $"unsupported format: ($format)" }
            }
        }

        if $archive_result.status == "success" {
            log info $"Successfully created ($format) package for ($platform): ($archive_result.archive_path)"

            {
                platform: $platform
                format: $format
                package_name: $package_name
                status: "success"
                archive_path: $archive_result.archive_path
                archive_size: $archive_result.archive_size
                original_size: $archive_result.original_size
                compression_ratio: $archive_result.compression_ratio
                duration: ((date now) - $start_time)
            }
        } else {
            {
                platform: $platform
                format: $format
                package_name: $package_name
                status: "failed"
                reason: $archive_result.reason
                duration: ((date now) - $start_time)
            }
        }

    } catch {|err|
        log error $"Failed to create package for ($platform) ($format): ($err.msg)"
        {
            platform: $platform
            format: $format
            package_name: $package_name
            status: "failed"
            reason: $err.msg
            duration: ((date now) - $start_time)
        }
    }
}

# Prepare package directory structure
def prepare_package_directory [
    platform: string
    package_config: record
    package_name: string
]
{
    let temp_package_dir = ($package_config.output_dir | path join "tmp" $package_name)

    # Clean and create package directory
    if ($temp_package_dir | path exists) {
        rm -rf $temp_package_dir
    }
    mkdir $temp_package_dir

    # Copy distribution files based on variant and platform
    match $package_config.variant {
        "complete" => {
            copy_complete_distribution $package_config.dist_dir $temp_package_dir $platform $package_config
        }
        "minimal" => {
            copy_minimal_distribution $package_config.dist_dir $temp_package_dir $platform $package_config
        }
        "custom" => {
            copy_custom_distribution $package_config.dist_dir $temp_package_dir $platform $package_config
        }
        _ => {
            log error $"Unknown variant: ($package_config.variant)"
            exit 1
        }
    }

    return $temp_package_dir
}

# Copy complete distribution
def copy_complete_distribution [
    dist_dir: string
    package_dir: string
    platform: string
    package_config: record
] {
    # Copy all distribution components
    let components = ["platform", "core", "config", "kcl", "extensions", "templates", "docs"]

    for component in $components {
        let source_path = ($dist_dir | path join $component)
        let target_path = ($package_dir | path join $component)

        if ($source_path | path exists) {
            copy_filtered $source_path $target_path $package_config.exclude_patterns
        }
    }

    # Filter platform-specific binaries
    filter_platform_binaries $package_dir $platform

    # Add installation scripts
    add_installation_scripts $package_dir $platform $package_config
}

# Copy minimal distribution
def copy_minimal_distribution [
    dist_dir: string
    package_dir: string
    platform: string
    package_config: record
] {
    # Copy only essential components
    let essential_components = ["platform", "core", "config"]

    for component in $essential_components {
        let source_path = ($dist_dir | path join $component)
        let target_path = ($package_dir | path join $component)

        if ($source_path | path exists) {
            copy_filtered $source_path $target_path $package_config.exclude_patterns
        }
    }

    # Filter platform-specific binaries
    filter_platform_binaries $package_dir $platform

    # Add minimal installation scripts
    add_minimal_installation_scripts $package_dir $platform $package_config
}

# Copy custom distribution
def copy_custom_distribution [
    dist_dir: string
    package_dir: string
    platform: string
    package_config: record
] {
    # Custom distribution would be configurable - for now, same as complete
    copy_complete_distribution $dist_dir $package_dir $platform $package_config
}

# Copy with exclusion filtering
def copy_filtered [source: string, target: string, exclude_patterns: list] {
    if ($source | path type) == "file" {
        if not (should_exclude $source $exclude_patterns) {
            mkdir ($target | path dirname)
            cp $source $target
        }
    } else {
        mkdir $target

        let items = (ls $source | where type == file)
        for item in $items {
            if not (should_exclude $item.name $exclude_patterns) {
                cp $item.name ($target | path join ($item.name | path basename))
            }
        }

        let dirs = (ls $source | where type == dir)
        for dir in $dirs {
            if not (should_exclude $dir.name $exclude_patterns) {
                copy_filtered $dir.name ($target | path join ($dir.name | path basename)) $exclude_patterns
            }
        }
    }
}

# Filter platform-specific binaries
def filter_platform_binaries [package_dir: string, platform: string] {
    let platform_dir = ($package_dir | path join "platform")

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

    # Get all binaries
    let binaries = (ls $platform_dir | where type == file | get name)

    # Filter based on platform
    for binary in $binaries {
        let binary_name = ($binary | path basename)
        let should_keep = match $platform {
            "linux" => { $binary_name =~ "linux" or not ($binary_name =~ "(macos|windows|darwin|win|exe)") }
            "macos" => { $binary_name =~ "(macos|darwin)" or not ($binary_name =~ "(linux|windows|win|exe)") }
            "windows" => { $binary_name =~ "(windows|win|exe)" or not ($binary_name =~ "(linux|macos|darwin)") }
            _ => true
        }

        if not $should_keep {
            rm $binary
        }
    }
}

# Add installation scripts
def add_installation_scripts [package_dir: string, platform: string, package_config: record] {
    let install_script = match $platform {
        "linux" => { create_linux_install_script $package_config }
        "macos" => { create_macos_install_script $package_config }
        "windows" => { create_windows_install_script $package_config }
        _ => { create_generic_install_script $package_config }
    }

    let script_name = match $platform {
        "windows" => "install.bat"
        _ => "install.sh"
    }

    $install_script | save ($package_dir | path join $script_name)

    # Make executable on Unix-like systems
    if $platform in ["linux", "macos"] {
        chmod +x ($package_dir | path join $script_name)
    }

    # Add README
    create_package_readme $package_config | save ($package_dir | path join "README.md")
}

# Add minimal installation scripts
def add_minimal_installation_scripts [package_dir: string, platform: string, package_config: record] {
    # Create simple installation instructions
    let instructions = match $platform {
        "linux" | "macos" => $"#!/bin/bash
# Provisioning System Installation
# Version: ($package_config.version)

echo \"Installing provisioning system...\"

# Copy binaries to /usr/local/bin
sudo cp platform/* /usr/local/bin/
sudo chmod +x /usr/local/bin/provisioning-orchestrator

# Copy core libraries
sudo mkdir -p /usr/local/lib/provisioning
sudo cp -r core/* /usr/local/lib/provisioning/

# Copy configuration
sudo mkdir -p /etc/provisioning
sudo cp config/* /etc/provisioning/

echo \"Installation complete!\"
echo \"Run 'provisioning help' to get started.\"
"
        "windows" => $"@echo off
REM Provisioning System Installation
REM Version: ($package_config.version)

echo Installing provisioning system...

REM Create directories
mkdir \"C:\\Program Files\\Provisioning\" 2>NUL
mkdir \"C:\\Program Files\\Provisioning\\bin\" 2>NUL
mkdir \"C:\\Program Files\\Provisioning\\lib\" 2>NUL
mkdir \"C:\\ProgramData\\Provisioning\" 2>NUL

REM Copy files
xcopy platform\\* \"C:\\Program Files\\Provisioning\\bin\\\" /Y /Q
xcopy core\\* \"C:\\Program Files\\Provisioning\\lib\\\" /Y /Q /S
xcopy config\\* \"C:\\ProgramData\\Provisioning\\\" /Y /Q

echo Installation complete!
echo Add C:\\Program Files\\Provisioning\\bin to your PATH
echo Run 'provisioning-orchestrator --help' to get started.
"
        _ => "# Installation instructions not available for this platform"
    }

    let script_name = match $platform {
        "windows" => "install.bat"
        _ => "install.sh"
    }

    $instructions | save ($package_dir | path join $script_name)
}

# Create tar.gz archive
def create_tar_archive [
    package_dir: string
    package_config: record
    package_name: string
]
{
    let archive_name = $"($package_name).tar.gz"
    let archive_path = ($package_config.output_dir | path join $archive_name)
    let package_parent = ($package_dir | path dirname)
    let package_basename = ($package_dir | path basename)

    try {
        cd $package_parent

        # Create tar with specified compression level
        let compression_flag = $"--gzip --compression-level=($package_config.compression_level)"
        tar --create --file $archive_path $compression_flag $package_basename

        # Clean up temporary directory
        rm -rf $package_dir

        # Get size information
        let archive_size = (ls $archive_path | get 0.size)
        let original_size = get_directory_size $package_dir
        let compression_ratio = if $original_size > 0 {
            (($archive_size | into float) / ($original_size | into float) * 100)
        } else {
            0.0
        }

        {
            status: "success"
            archive_path: $archive_path
            archive_size: $archive_size
            original_size: $original_size
            compression_ratio: $compression_ratio
        }

    } catch {|err|
        {
            status: "failed"
            reason: $err.msg
        }
    }
}

# Create zip archive
def create_zip_archive [
    package_dir: string
    package_config: record
    package_name: string
]
{
    let archive_name = $"($package_name).zip"
    let archive_path = ($package_config.output_dir | path join $archive_name)
    let package_parent = ($package_dir | path dirname)
    let package_basename = ($package_dir | path basename)

    try {
        cd $package_parent

        # Create zip archive
        zip -r -$package_config.compression_level $archive_path $package_basename

        # Clean up temporary directory
        rm -rf $package_dir

        # Get size information
        let archive_size = (ls $archive_path | get 0.size)
        let original_size = get_directory_size $package_dir
        let compression_ratio = if $original_size > 0 {
            (($archive_size | into float) / ($original_size | into float) * 100)
        } else {
            0.0
        }

        {
            status: "success"
            archive_path: $archive_path
            archive_size: $archive_size
            original_size: $original_size
            compression_ratio: $compression_ratio
        }

    } catch {|err|
        {
            status: "failed"
            reason: $err.msg
        }
    }
}

# Generate checksums for all packages
def generate_checksums [
    package_results: list
    package_config: record
]
{
    log info "Generating checksums..."

    let successful_packages = ($package_results | where status == "success")
    let checksums = []

    for package in $successful_packages {
        try {
            let sha256_hash = (shasum -a 256 $package.archive_path | awk '{print $1}')
            let md5_hash = (md5sum $package.archive_path | awk '{print $1}')

            $checksums = ($checksums | append {
                file: ($package.archive_path | path basename)
                sha256: $sha256_hash
                md5: $md5_hash
                size: $package.archive_size
            })

        } catch {|err|
            log warning $"Failed to generate checksum for ($package.archive_path): ($err.msg)"
        }
    }

    # Save checksums to file
    let checksum_file = ($package_config.output_dir | path join "checksums.txt")
    let checksum_content = ($checksums | each {|cs|
        $"($cs.sha256)  ($cs.file)\n($cs.md5)  ($cs.file)"
    } | str join "\n")

    $checksum_content | save $checksum_file

    log info $"Generated checksums for ($checksums | length) packages: ($checksum_file)"

    {
        status: "success"
        checksum_file: $checksum_file
        checksums: $checksums
    }
}

# Create package manifest
def create_package_manifest [
    package_results: list
    checksum_result: record
    package_config: record
    repo_root: string
] {
    let manifest = {
        version: $package_config.version
        created_at: (date now | format date "%Y-%m-%d %H:%M:%S")
        created_by: "provisioning-package-system"
        source_commit: (try { cd $repo_root; git rev-parse HEAD } catch { "unknown" })
        source_branch: (try { cd $repo_root; git branch --show-current } catch { "unknown" })
        package_config: $package_config
        packages: $package_results
        checksums: $checksum_result.checksums
        total_packages: ($package_results | where status == "success" | length)
        total_size: ($package_results | where status == "success" | get archive_size | math sum)
    }

    let manifest_file = ($package_config.output_dir | path join "manifest.json")
    $manifest | to json | save $manifest_file

    log info $"Created package manifest: ($manifest_file)"
}

# Utility functions
def should_exclude [path: string, patterns: list]
{
    if ($patterns | length) == 0 { return false }
    return ($patterns | any {|pattern| $path =~ $pattern })
}

def get_directory_size [dir: string] -> int {
    if not ($dir | path exists) { return 0 }
    try {
        find $dir -type f | each {|file| ls $file | get 0.size } | math sum | if $in == null { 0 } else { $in }
    } catch { 0 }
}

# Create installation script templates
def create_linux_install_script [config: record]
{
    $"#!/bin/bash
# Provisioning System Installation Script
# Version: ($config.version)
# Platform: Linux

set -e

INSTALL_DIR=\"/usr/local\"
CONFIG_DIR=\"/etc/provisioning\"
LIB_DIR=\"$INSTALL_DIR/lib/provisioning\"

echo \"Installing Provisioning System ($config.version) for Linux...\"

# Check for root privileges
if [[ $EUID -ne 0 ]]; then
   echo \"This script must be run as root (use sudo)\"
   exit 1
fi

# Install binaries
echo \"Installing binaries...\"
mkdir -p \"$INSTALL_DIR/bin\"
cp platform/* \"$INSTALL_DIR/bin/\"
chmod +x \"$INSTALL_DIR/bin/\"*

# Install libraries
echo \"Installing libraries...\"
mkdir -p \"$LIB_DIR\"
cp -r core/* \"$LIB_DIR/\"

# Install configuration
echo \"Installing configuration...\"
mkdir -p \"$CONFIG_DIR\"
cp config/* \"$CONFIG_DIR/\"

# Install KCL schemas
if [ -d \"kcl\" ]; then
    echo \"Installing KCL schemas...\"
    mkdir -p \"$LIB_DIR/kcl\"
    cp -r kcl/* \"$LIB_DIR/kcl/\"
fi

echo \"Installation complete!\"
echo \"Run 'provisioning help' to get started.\"
"
}

def create_macos_install_script [config: record]
{
    $"#!/bin/bash
# Provisioning System Installation Script
# Version: ($config.version)
# Platform: macOS

set -e

INSTALL_DIR=\"/usr/local\"
CONFIG_DIR=\"/usr/local/etc/provisioning\"
LIB_DIR=\"$INSTALL_DIR/lib/provisioning\"

echo \"Installing Provisioning System ($config.version) for macOS...\"

# Install binaries
echo \"Installing binaries...\"
mkdir -p \"$INSTALL_DIR/bin\"
cp platform/* \"$INSTALL_DIR/bin/\"
chmod +x \"$INSTALL_DIR/bin/\"*

# Install libraries
echo \"Installing libraries...\"
mkdir -p \"$LIB_DIR\"
cp -r core/* \"$LIB_DIR/\"

# Install configuration
echo \"Installing configuration...\"
mkdir -p \"$CONFIG_DIR\"
cp config/* \"$CONFIG_DIR/\"

# Install KCL schemas
if [ -d \"kcl\" ]; then
    echo \"Installing KCL schemas...\"
    mkdir -p \"$LIB_DIR/kcl\"
    cp -r kcl/* \"$LIB_DIR/kcl/\"
fi

echo \"Installation complete!\"
echo \"Run 'provisioning help' to get started.\"
echo \"Note: You may need to add /usr/local/bin to your PATH\"
"
}

def create_windows_install_script [config: record]
{
    $"@echo off
REM Provisioning System Installation Script
REM Version: ($config.version)
REM Platform: Windows

echo Installing Provisioning System ($config.version) for Windows...

REM Create directories
mkdir \"C:\\Program Files\\Provisioning\\bin\" 2>NUL
mkdir \"C:\\Program Files\\Provisioning\\lib\" 2>NUL
mkdir \"C:\\ProgramData\\Provisioning\" 2>NUL

REM Install binaries
echo Installing binaries...
xcopy platform\\* \"C:\\Program Files\\Provisioning\\bin\\\" /Y /Q

REM Install libraries
echo Installing libraries...
xcopy core\\* \"C:\\Program Files\\Provisioning\\lib\\\" /Y /Q /S

REM Install configuration
echo Installing configuration...
xcopy config\\* \"C:\\ProgramData\\Provisioning\\\" /Y /Q

REM Install KCL schemas
if exist kcl\\ (
    echo Installing KCL schemas...
    mkdir \"C:\\Program Files\\Provisioning\\lib\\kcl\" 2>NUL
    xcopy kcl\\* \"C:\\Program Files\\Provisioning\\lib\\kcl\\\" /Y /Q /S
)

echo Installation complete!
echo Add \"C:\\Program Files\\Provisioning\\bin\" to your PATH
echo Run 'provisioning-orchestrator --help' to get started.
pause
"
}

def create_generic_install_script [config: record]
{
    $"#!/bin/sh
# Generic Installation Instructions
# Version: ($config.version)

echo \"Manual installation required for this platform\"
echo \"Please follow the instructions in README.md\"
"
}

def create_package_readme [config: record]
{
    $"# Provisioning System ($config.version)

Cloud-native infrastructure provisioning and management system.

## Installation

### Automated Installation

Run the platform-specific installation script:

- **Linux/macOS**: `sudo ./install.sh`
- **Windows**: Run `install.bat` as Administrator

### Manual Installation

1. Copy platform binaries to your system PATH
2. Copy core libraries to a standard location
3. Copy configuration files to appropriate system directory
4. Set up environment variables if needed

## Getting Started

After installation, run:

```bash
provisioning help
```

For detailed documentation, visit: https://github.com/your-org/provisioning

## Version Information

- **Version**: ($config.version)
- **Variant**: ($config.variant)
- **Build Date**: (date now | format date "%Y-%m-%d")

## Support

For support and issues, please visit the project repository.
"
}

# Show package information
def "main info" [packages_dir: string = "packages"] {
    let packages_root = ($packages_dir | path expand)

    if not ($packages_root | path exists) {
        return { error: "packages directory not found", directory: $packages_root }
    }

    let manifest_file = ($packages_root | path join "manifest.json")
    if ($manifest_file | path exists) {
        open $manifest_file
    } else {
        let packages = (ls $packages_root | where name =~ "\.(tar\.gz|zip)$")
        {
            directory: $packages_root
            packages: ($packages | length)
            total_size: ($packages | get size | math sum)
            files: ($packages | get name | each { path basename })
        }
    }
}
