# OCI Packaging Tool
# Package extensions as OCI artifacts for distribution
# Version: 1.0.0

use ../core/nulib/lib_provisioning/config/loader.nu get-config
use ../core/nulib/lib_provisioning/oci/client.nu *
use std log

# Package extension as OCI artifact
export def package-extension [
    extension_path: string    # Path to extension directory
    --output-path: string     # Output path for OCI artifact (optional)
    --validate                # Validate before packaging
]
{
    log info $"Packaging extension from ($extension_path)"

    # Validate extension structure
    if $validate {
        let validation = (validate-extension $extension_path)

        if not $validation.valid {
            error make {
                msg: $"Extension validation failed: ($validation.errors)"
            }
        }
    }

    # Read extension manifest
    let manifest_path = ($extension_path | path join "manifest.yaml")

    if not ($manifest_path | path exists) {
        error make {
            msg: $"Missing manifest.yaml in ($extension_path)"
        }
    }

    let manifest = (open $manifest_path | from yaml)

    # Create temporary directory for artifact
    let temp_dir = (mktemp -d -t "oci-pkg-XXXXXX")
    let artifact_dir = ($temp_dir | path join $manifest.name)

    mkdir $artifact_dir

    # Copy extension files to artifact directory
    log info "Copying extension files..."

    # Required directories
    let required_dirs = ["kcl", "scripts"]
    for dir in $required_dirs {
        let src = ($extension_path | path join $dir)
        if ($src | path exists) {
            cp -r $src ($artifact_dir | path join $dir)
        }
    }

    # Optional directories
    let optional_dirs = ["templates", "docs", "tests"]
    for dir in $optional_dirs {
        let src = ($extension_path | path join $dir)
        if ($src | path exists) {
            cp -r $src ($artifact_dir | path join $dir)
        }
    }

    # Copy manifest
    cp $manifest_path ($artifact_dir | path join "manifest.yaml")

    # Create OCI manifest
    let oci_manifest = (create-oci-manifest $artifact_dir $manifest)

    # Save OCI manifest
    $oci_manifest | to json | save -f ($artifact_dir | path join "oci-manifest.json")

    # Create tarball
    let output_tar = if ($output_path | is-not-empty) {
        $output_path
    } else {
        $"($manifest.name)-($manifest.version).tar.gz"
    }

    log info $"Creating tarball: ($output_tar)"

    cd $temp_dir
    tar czf $output_tar $manifest.name

    # Move tarball to current directory
    let final_path = ($env.PWD | path join ($output_tar | path basename))
    mv $output_tar $final_path

    # Cleanup
    rm -rf $temp_dir

    log info $"✓ Package created: ($final_path)"

    $final_path
}

# Validate extension structure before packaging
export def validate-extension [
    extension_path: string
]
{
    log info $"Validating extension at ($extension_path)"

    mut errors = []
    mut warnings = []

    # Check if directory exists
    if not ($extension_path | path exists) {
        $errors = ($errors | append "Extension directory does not exist")
        return {
            valid: false
            errors: $errors
            warnings: $warnings
        }
    }

    # Check for manifest.yaml
    let manifest_path = ($extension_path | path join "manifest.yaml")
    if not ($manifest_path | path exists) {
        $errors = ($errors | append "Missing manifest.yaml")
    } else {
        # Validate manifest content
        try {
            let manifest = (open $manifest_path | from yaml)

            # Required fields
            let required_fields = ["name", "type", "version"]
            for field in $required_fields {
                if ($manifest | get -i $field | is-empty) {
                    $errors = ($errors | append $"Missing required field in manifest: ($field)")
                }
            }

            # Validate type
            let valid_types = ["provider", "taskserv", "cluster"]
            if not ($manifest.type in $valid_types) {
                $errors = ($errors | append $"Invalid type in manifest: ($manifest.type)")
            }

            # Validate version format (basic semver check)
            if ($manifest.version? | is-not-empty) {
                if ($manifest.version | str contains ".") == false {
                    $warnings = ($warnings | append "Version should follow semver format (x.y.z)")
                }
            }
        } catch { |err|
            $errors = ($errors | append $"Invalid manifest.yaml: ($err.msg)")
        }
    }

    # Check for required directories
    let kcl_dir = ($extension_path | path join "kcl")
    if not ($kcl_dir | path exists) {
        $errors = ($errors | append "Missing kcl/ directory")
    } else {
        # Check for kcl.mod
        let kcl_mod = ($kcl_dir | path join "kcl.mod")
        if not ($kcl_mod | path exists) {
            $warnings = ($warnings | append "Missing kcl/kcl.mod (recommended)")
        }

        # Check for at least one .k file
        let k_files = (ls $kcl_dir | where name =~ "\.k$")
        if ($k_files | is-empty) {
            $errors = ($errors | append "No .k files found in kcl/ directory")
        }
    }

    # Check for scripts directory
    let scripts_dir = ($extension_path | path join "scripts")
    if not ($scripts_dir | path exists) {
        $warnings = ($warnings | append "Missing scripts/ directory (recommended)")
    } else {
        # Check for install.nu
        let install_script = ($scripts_dir | path join "install.nu")
        if not ($install_script | path exists) {
            $warnings = ($warnings | append "Missing scripts/install.nu (recommended)")
        }
    }

    # Check for optional but recommended directories
    let docs_dir = ($extension_path | path join "docs")
    if not ($docs_dir | path exists) {
        $warnings = ($warnings | append "Missing docs/ directory (recommended)")
    }

    let readme = ($extension_path | path join "README.md")
    if not ($readme | path exists) {
        $warnings = ($warnings | append "Missing README.md (recommended)")
    }

    {
        valid: ($errors | is-empty)
        errors: $errors
        warnings: $warnings
    }
}

# Create OCI manifest for extension
export def create-oci-manifest [
    artifact_path: string
    extension_manifest: record
]
{
    log info "Creating OCI manifest..."

    # Calculate layer sizes
    let layers = (ls -a $artifact_path | each { |item|
        {
            mediaType: "application/vnd.oci.image.layer.v1.tar+gzip"
            size: $item.size
            digest: $"sha256:(echo $item.name | hash sha256)"
            annotations: {
                "org.opencontainers.image.title": $item.name
            }
        }
    })

    # Create config
    let config = {
        mediaType: "application/vnd.kcl.package.config.v1+json"
        size: 0  # Will be calculated
        digest: ""  # Will be calculated
        data: $extension_manifest
    }

    # Create manifest
    {
        schemaVersion: 2
        mediaType: "application/vnd.oci.image.manifest.v1+json"
        config: $config
        layers: $layers
        annotations: {
            "org.opencontainers.image.created": (date now | format date "%Y-%m-%dT%H:%M:%SZ")
            "org.opencontainers.image.title": $extension_manifest.name
            "org.opencontainers.image.description": ($extension_manifest.description? | default "")
            "org.opencontainers.image.version": $extension_manifest.version
            "org.opencontainers.image.authors": ($extension_manifest.author? | default "")
            "org.opencontainers.image.licenses": ($extension_manifest.license? | default "MIT")
            "provisioning.extension.type": $extension_manifest.type
        }
    }
}

# Build and push extension to OCI registry
export def publish-extension [
    extension_path: string
    registry: string
    namespace: string
    --version: string         # Override version from manifest
    --insecure
    --validate
]
{
    log info $"Publishing extension from ($extension_path)"

    # Validate extension
    if $validate {
        let validation = (validate-extension $extension_path)

        if not $validation.valid {
            log error $"Validation failed: ($validation.errors)"
            return false
        }

        if not ($validation.warnings | is-empty) {
            log warning $"Validation warnings:"
            for warn in $validation.warnings {
                log warning $"  - ($warn)"
            }
        }
    }

    # Read manifest
    let manifest_path = ($extension_path | path join "manifest.yaml")
    let manifest = (open $manifest_path | from yaml)

    let ext_version = if ($version | is-not-empty) {
        $version
    } else {
        $manifest.version
    }

    log info $"Publishing ($manifest.name):($ext_version) to ($registry)/($namespace)"

    # Push to OCI registry
    let result = (push-artifact $extension_path $registry $namespace $manifest.name $ext_version --insecure=$insecure)

    if $result {
        log info $"✓ Successfully published ($manifest.name):($ext_version)"
        log info $"  Reference: ($registry)/($namespace)/($manifest.name):($ext_version)"
        true
    } else {
        log error $"Failed to publish ($manifest.name):($ext_version)"
        false
    }
}

# Unpack OCI artifact to local directory
export def unpack-extension [
    artifact_path: string     # Path to .tar.gz OCI artifact
    destination: string       # Destination directory
]
{
    log info $"Unpacking OCI artifact from ($artifact_path)"

    if not ($artifact_path | path exists) {
        error make {
            msg: $"Artifact not found: ($artifact_path)"
        }
    }

    # Create destination directory
    mkdir $destination

    # Extract tarball
    log info $"Extracting to ($destination)"

    tar xzf $artifact_path -C $destination

    # Find the extension directory (should be only one)
    let ext_dirs = (ls $destination | where type == "dir")

    if ($ext_dirs | length) != 1 {
        error make {
            msg: "Artifact should contain exactly one directory"
        }
    }

    let ext_path = ($ext_dirs | first | get name)

    log info $"✓ Extension unpacked to ($ext_path)"

    $ext_path
}

# List contents of OCI artifact without extracting
export def inspect-artifact [
    artifact_path: string
]
{
    log info $"Inspecting OCI artifact: ($artifact_path)"

    if not ($artifact_path | path exists) {
        error make {
            msg: $"Artifact not found: ($artifact_path)"
        }
    }

    # List tarball contents
    let contents = (tar tzf $artifact_path | lines)

    # Try to extract and read manifest
    let temp_dir = (mktemp -d -t "oci-inspect-XXXXXX")

    tar xzf $artifact_path -C $temp_dir

    # Find manifest.yaml
    let manifest_files = (ls $temp_dir/**/manifest.yaml -r | where type == "file")

    let manifest = if not ($manifest_files | is-empty) {
        let manifest_path = ($manifest_files | first | get name)
        open $manifest_path | from yaml
    } else {
        {}
    }

    # Cleanup
    rm -rf $temp_dir

    {
        artifact: ($artifact_path | path basename)
        size: (ls $artifact_path | first | get size)
        files: $contents
        manifest: $manifest
    }
}

# Generate manifest template for extension
export def generate-manifest [
    extension_name: string
    extension_type: string    # provider, taskserv, cluster
    --version: string = "0.1.0"
    --description: string
    --author: string
]
{
    {
        name: $extension_name
        type: $extension_type
        version: $version
        description: ($description | default $"($extension_type) extension for ($extension_name)")
        author: ($author | default $env.USER)
        license: "MIT"
        platforms: ["linux/amd64"]
        dependencies: {}
        tags: []
    }
}

# Save manifest template to file
export def "generate-manifest save" [
    extension_name: string
    extension_type: string
    output_path: string
    --version: string = "0.1.0"
    --description: string
    --author: string
] -> nothing {
    let manifest = (generate-manifest $extension_name $extension_type
                    --version $version
                    --description $description
                    --author $author)

    $manifest | to yaml | save -f $output_path

    log info $"✓ Manifest template saved to ($output_path)"
}
