# OCI Registry Client
# Handles OCI artifact operations (pull, push, list, search)

use ../config/accessor.nu *
use ../utils/logging.nu *

# OCI client configuration
export def get-oci-config [] {
    {
        registry: (get-config-value "oci.registry" "localhost:5000")
        namespace: (get-config-value "oci.namespace" "provisioning-extensions")
        auth_token_path: (get-config-value "oci.auth_token_path" ($env.HOME | path join ".provisioning" "oci-token"))
        insecure: (get-config-value "oci.insecure" false)
        timeout: (get-config-value "oci.timeout" 300)
        retry_count: (get-config-value "oci.retry_count" 3)
    }
}

# Load OCI authentication token
export def load-oci-token [token_path: string] {
    if ($token_path | path exists) {
        open $token_path | str trim
    } else {
        ""
    }
}

# Build OCI artifact reference
export def build-artifact-ref [
    registry: string
    namespace: string
    name: string
    version: string
] {
    $"($registry)/($namespace)/($name):($version)"
}

# Helper to download OCI layers
def download-oci-layers [
    layers: list
    registry: string
    namespace: string
    name: string
    dest_path: string
    auth_token: string
] {
    for layer in $layers {
        let blob_url = $"http://($registry)/v2/($namespace)/($name)/blobs/($layer.digest)"
        let layer_file = $"($dest_path)/($layer.digest | str replace ':' '_').tar.gz"

        log-debug $"Downloading layer: ($layer.digest)"

        # Download blob using run-external
        # Build curl args immutably per Rule 3
        let base_args = ["-L" "-o" $layer_file $blob_url]
        let curl_args = if ($auth_token | is-not-empty) {
            ["-H" $"Authorization: Bearer ($auth_token)"] | append $base_args
        } else {
            $base_args
        }

        let result = (do { ^curl ...$curl_args } | complete)

        if $result.exit_code != 0 {
            log-error $"Failed to download layer: ($layer.digest)"
            return false
        }

        # Extract layer
        log-debug $"Extracting layer: ($layer.digest)"
        tar -xzf $layer_file -C $dest_path
        rm $layer_file
    }
    true
}

# Pull OCI artifact using curl and tar
export def oci-pull-artifact [
    registry: string
    namespace: string
    name: string
    version: string
    dest_path: string
    --auth-token: string = ""
] {
    let result = (do {
        log-info $"Pulling OCI artifact: ($name):($version) from ($registry)/($namespace)"

        # Create destination directory
        mkdir $dest_path

        # Build manifest URL
        let manifest_url = $"http://($registry)/v2/($namespace)/($name)/manifests/($version)"

        # Build auth header
        let auth_header = if ($auth_token | is-not-empty) {
            ["Authorization" $"Bearer ($auth_token)"]
        } else {
            []
        }

        # Fetch manifest
        log-debug $"Fetching manifest from ($manifest_url)"
        let manifest_result = (http get --headers $auth_header $manifest_url)

        if ($manifest_result | is-empty) {
            log-error "Failed to fetch OCI manifest"
            error make {msg: "Failed to fetch OCI manifest"}
        }

        # Parse manifest
        let manifest = ($manifest_result | from json)

        # Save manifest
        $manifest | to json | save $"($dest_path)/oci-manifest.json"

        # Download each layer
        let layers = ($manifest | get layers)
        let download_result = (download-oci-layers $layers $registry $namespace $name $dest_path $auth_token)

        if not $download_result {
            error make {msg: "Failed to download layers"}
        }

        log-info $"Successfully pulled ($name):($version)"
        true
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        log-error $"Failed to pull OCI artifact: ($result.stderr)"
        false
    }
}

# Push OCI artifact using curl
export def oci-push-artifact [
    artifact_path: string
    registry: string
    namespace: string
    name: string
    version: string
    --auth-token: string = ""
] {
    let result = (do {
        log-info $"Pushing OCI artifact: ($name):($version) to ($registry)/($namespace)"

        # Create tarball of artifact
        let temp_tarball = (mktemp --suffix .tar.gz)

        log-debug $"Creating artifact tarball: ($temp_tarball)"
        tar -czf $temp_tarball -C $artifact_path .

        # Calculate digest
        let digest = (open $temp_tarball | hash sha256)
        let blob_digest = $"sha256:($digest)"

        # Upload blob
        let blob_url = $"http://($registry)/v2/($namespace)/($name)/blobs/uploads/"

        log-debug $"Uploading blob to ($blob_url)"

        # Start upload using run-external - build args immutably per Rule 3
        let base_start_args = ["-X" "POST" $blob_url]
        let upload_start_args = if ($auth_token | is-not-empty) {
            ["-H" $"Authorization: Bearer ($auth_token)"] | append $base_start_args
        } else {
            $base_start_args
        }

        let start_upload = (do {
            ^curl ...$upload_start_args
        } | complete)

        if $start_upload.exit_code != 0 {
            log-error "Failed to start blob upload"
            rm $temp_tarball
            error make {msg: "Failed to start blob upload"}
        }

        # Extract upload URL from Location header
        let upload_url = ($start_upload.stdout | str trim)

        # Upload blob using run-external - build args immutably per Rule 3
        let auth_headers = if ($auth_token | is-not-empty) {
            ["-H" $"Authorization: Bearer ($auth_token)"]
        } else {
            []
        }

        let upload_args = [
            "-X" "PUT"
        ] | append $auth_headers | append [
            "-H" "Content-Type: application/octet-stream"
            "--data-binary" $"@($temp_tarball)"
            $"($upload_url)?digest=($blob_digest)"
        ]

        let upload_result = (do { ^curl ...$upload_args } | complete)

        if $upload_result.exit_code != 0 {
            log-error "Failed to upload blob"
            rm $temp_tarball
            error make {msg: "Failed to upload blob"}
        }

        # Create manifest
        let config = if ($"($artifact_path)/oci-config.json" | path exists) {
            open $"($artifact_path)/oci-config.json" | from json
        } else {
            {
                created: (date now | format date "%Y-%m-%dT%H:%M:%SZ")
                architecture: "any"
                os: "any"
            }
        }

        let manifest = {
            schemaVersion: 2
            mediaType: "application/vnd.oci.image.manifest.v1+json"
            config: {
                mediaType: "application/vnd.oci.image.config.v1+json"
                size: (ls $temp_tarball | get size | get 0)
                digest: $blob_digest
            }
            layers: [
                {
                    mediaType: "application/vnd.oci.image.layer.v1.tar+gzip"
                    size: (ls $temp_tarball | get size | get 0)
                    digest: $blob_digest
                }
            ]
        }

        # Upload manifest
        let manifest_url = $"http://($registry)/v2/($namespace)/($name)/manifests/($version)"
        let manifest_json = ($manifest | to json)

        log-debug $"Uploading manifest to ($manifest_url)"

        # Upload manifest using run-external - build args immutably per Rule 3
        let auth_headers = if ($auth_token | is-not-empty) {
            ["-H" $"Authorization: Bearer ($auth_token)"]
        } else {
            []
        }

        let manifest_args = [
            "-X" "PUT"
        ] | append $auth_headers | append [
            "-H" "Content-Type: application/vnd.oci.image.manifest.v1+json"
            "-d" $manifest_json
            $manifest_url
        ]

        let manifest_result = (do { ^curl ...$manifest_args } | complete)

        if $manifest_result.exit_code != 0 {
            log-error "Failed to upload manifest"
            rm $temp_tarball
            error make {msg: "Failed to upload manifest"}
        }

        rm $temp_tarball
        log-info $"Successfully pushed ($name):($version)"
        true
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        log-error $"Failed to push OCI artifact: ($result.stderr)"
        false
    }
}

# List artifacts in OCI registry
export def oci-list-artifacts [
    registry: string
    namespace: string
    --auth-token: string = ""
] {
    let result = (do {
        let catalog_url = $"http://($registry)/v2/($namespace)/_catalog"

        let auth_header = if ($auth_token | is-not-empty) {
            ["Authorization" $"Bearer ($auth_token)"]
        } else {
            []
        }

        let http_result = (http get --headers $auth_header $catalog_url)

        if ($http_result | is-empty) {
            []
        } else {
            let catalog = ($http_result | from json)
            $catalog.repositories? | default []
        }
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        log-error $"Failed to list OCI artifacts: ($result.stderr)"
        []
    }
}

# Get artifact tags from OCI registry
export def oci-get-artifact-tags [
    registry: string
    namespace: string
    name: string
    --auth-token: string = ""
] {
    let result = (do {
        let tags_url = $"http://($registry)/v2/($namespace)/($name)/tags/list"

        let auth_header = if ($auth_token | is-not-empty) {
            ["Authorization" $"Bearer ($auth_token)"]
        } else {
            []
        }

        let http_result = (http get --headers $auth_header $tags_url)

        if ($http_result | is-empty) {
            []
        } else {
            let tags_data = ($http_result | from json)
            $tags_data.tags? | default []
        }
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        log-error $"Failed to get artifact tags: ($result.stderr)"
        []
    }
}

# Get artifact manifest from OCI registry
export def oci-get-artifact-manifest [
    registry: string
    namespace: string
    name: string
    version: string
    --auth-token: string = ""
] {
    let result = (do {
        let manifest_url = $"http://($registry)/v2/($namespace)/($name)/manifests/($version)"

        let auth_header = if ($auth_token | is-not-empty) {
            ["Authorization" $"Bearer ($auth_token)"]
        } else {
            []
        }

        let http_result = (http get --headers $auth_header $manifest_url)

        if ($http_result | is-empty) {
            {}
        } else {
            $http_result | from json
        }
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        log-error $"Failed to get artifact manifest: ($result.stderr)"
        {}
    }
}

# Check if artifact exists in OCI registry
export def oci-artifact-exists [
    registry: string
    namespace: string
    name: string
    version?: string
] {
    let result = (do {
        let artifacts = (oci-list-artifacts $registry $namespace)

        if ($version | is-empty) {
            # Just check if artifact name exists
            $name in $artifacts
        } else {
            # Check specific version
            if $name not-in $artifacts {
                false
            } else {
                let tags = (oci-get-artifact-tags $registry $namespace $name)
                $version in $tags
            }
        }
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        false
    }
}

# Delete artifact from OCI registry
export def oci-delete-artifact [
    registry: string
    namespace: string
    name: string
    version: string
    --auth-token: string = ""
] {
    let result = (do {
        log-warn $"Deleting OCI artifact: ($name):($version)"

        # Get manifest to get digest
        let manifest = (oci-get-artifact-manifest $registry $namespace $name $version --auth-token $auth_token)

        if ($manifest | is-empty) {
            log-error "Manifest not found"
            error make {msg: "Manifest not found"}
        }

        let digest = ($manifest | get config.digest)

        # Delete manifest
        let manifest_url = $"http://($registry)/v2/($namespace)/($name)/manifests/($digest)"

        # Delete using run-external - build args immutably per Rule 3
        let auth_headers = if ($auth_token | is-not-empty) {
            ["-H" $"Authorization: Bearer ($auth_token)"]
        } else {
            []
        }

        let delete_args = ["-X" "DELETE"] | append $auth_headers | append $manifest_url

        let delete_result = (do { ^curl ...$delete_args } | complete)

        if $delete_result.exit_code == 0 {
            log-info $"Successfully deleted ($name):($version)"
            true
        } else {
            log-error $"Failed to delete artifact: ($delete_result.stderr)"
            false
        }
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        log-error $"Failed to delete OCI artifact: ($result.stderr)"
        false
    }
}

# Check if OCI registry is available
export def is-oci-available [] {
    let result = (do {
        let config = (get-oci-config)
        let health_url = $"http://($config.registry)/v2/"

        let health_result = (do { http get $health_url } | complete)
        $health_result.exit_code == 0
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        false
    }
}

# Test OCI connectivity and authentication
export def test-oci-connection [] {
    let config = (get-oci-config)
    let token = (load-oci-token $config.auth_token_path)

    mut results = {
        registry_reachable: false
        authentication_valid: false
        catalog_accessible: false
        errors: []
    }

    # Test registry reachability
    let health_url = $"http://($config.registry)/v2/"
    let health_result = (do { http get $health_url } | complete)

    if $health_result.exit_code == 0 {
        $results.registry_reachable = true
    } else {
        $results.errors = ($results.errors | append "Registry unreachable")
    }

    # Test authentication (if token provided)
    if ($token | is-not-empty) {
        let catalog = (oci-list-artifacts $config.registry $config.namespace --auth-token $token)

        if ($catalog | is-not-empty) {
            $results.authentication_valid = true
            $results.catalog_accessible = true
        } else {
            $results.errors = ($results.errors | append "Authentication failed or catalog empty")
        }
    }

    $results
}
