# OCI-Aware Extension Loader
# Loads extensions from multiple sources: OCI, Gitea, Local

# Selective imports (ADR-025 Phase 3 Layer 2).
# config/accessor and extensions/cache star-imports were dead — dropped.
use lib_provisioning/utils/logging.nu [log-debug log-error log-info]
use lib_provisioning/oci/client.nu [
    get-oci-config is-oci-available load-oci-token oci-artifact-exists
    oci-get-artifact-manifest oci-get-artifact-tags oci-pull-artifact
]
use lib_provisioning/extensions/loader.nu [load-manifest is-extension-allowed check-requirements load-hooks]

# Check if extension is already loaded (in memory)
def is-loaded [extension_type: string, extension_name: string] {
    let registry = ($env.EXTENSION_REGISTRY? | default {providers: {}, taskservs: {}})

    match $extension_type {
        "provider" => {
            let providers = ($registry.providers? | default {})
            ($extension_name in ($providers | columns))
        }
        "taskserv" => {
            let taskservs = ($registry.taskservs? | default {})
            ($extension_name in ($taskservs | columns))
        }
        _ => false
    }
}

# Load extension with OCI support
export def load-extension [
    extension_type: string
    extension_name: string
    version?: string
    --source-type: string = "auto"  # auto, oci, gitea, local
    --force (-f)
] {
    let result = (do {
        log-info $"Loading extension: ($extension_name) \(type: ($extension_type), version: ($version | default 'latest'), source: ($source_type))"

        # 1. Check if already loaded
        if (is-loaded $extension_type $extension_name) and not $force {
            log-debug $"Extension ($extension_name) already loaded"
            return {success: true, cached: true, source: "memory"}
        }

        # 2. Check cache
        let cached = (get-from-cache $extension_type $extension_name $version)

        if $cached.found and not $force {
            log-debug $"Extension ($extension_name) found in cache"
            let loaded = (load-from-path $extension_type $extension_name $cached.path)
            return ($loaded | insert cached true | insert source "cache")
        }

        # 3. Determine source type
        let resolved_source = if $source_type == "auto" {
            determine-source-type $extension_type $extension_name
        } else {
            $source_type
        }

        log-debug $"Resolved source type: ($resolved_source)"

        # 4. Download based on source type
        let downloaded = match $resolved_source {
            "oci" => (download-from-oci $extension_type $extension_name $version)
            "gitea" => (download-from-gitea $extension_type $extension_name $version)
            "local" => (resolve-local-path $extension_type $extension_name)
            _ => (error make {msg: $"Unknown source type: ($resolved_source)"})
        }

        if not $downloaded.success {
            return {success: false, error: $downloaded.error}
        }

        # 5. Save to cache (if not local)
        if $resolved_source != "local" {
            save-to-cache $extension_type $extension_name $downloaded.version $downloaded.path $resolved_source $downloaded.metadata
        }

        # 6. Load extension
        let loaded = (load-from-path $extension_type $extension_name $downloaded.path)

        $loaded | insert source $resolved_source | insert version $downloaded.version
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        log-error $"Failed to load extension ($extension_name): ($result.stderr)"
        {success: false, error: $result.stderr}
    }
}

# Determine source type automatically
def determine-source-type [
    extension_type: string
    extension_name: string
] {
    # Check workspace config for preferred source
    let preferred = (get-config-value "extensions.source_type" "auto")

    if $preferred != "auto" {
        log-debug $"Using preferred source type from config: ($preferred)"
        return $preferred
    }

    # Check if OCI registry is configured and artifact exists
    if (is-oci-available) {
        let config = (get-oci-config)

        if (oci-artifact-exists $config.registry $config.namespace $extension_name) {
            log-debug $"Extension ($extension_name) found in OCI registry"
            return "oci"
        }
    }

    # Check if Gitea is configured
    if (is-gitea-available) {
        log-debug "Gitea is available, using gitea source"
        return "gitea"
    }

    # Check local paths
    let local_path = (try-resolve-local-path $extension_type $extension_name)

    if ($local_path | is-not-empty) {
        log-debug $"Extension ($extension_name) found locally at ($local_path)"
        return "local"
    }

    # Default to OCI if available, otherwise error
    if (is-oci-available) {
        log-debug "Defaulting to OCI source"
        return "oci"
    }

    error make {msg: $"No valid source found for extension ($extension_name)"}
}

# Download extension from OCI registry
def download-from-oci [
    extension_type: string
    extension_name: string
    version?: string
] {
    let result = (do {
        let config = (get-oci-config)
        let token = (load-oci-token $config.auth_token_path)

        # Resolve version if not specified
        let resolved_version = if ($version | is-empty) or $version == "latest" {
            let tags = (oci-get-artifact-tags $config.registry $config.namespace $extension_name --auth-token $token)

            if ($tags | is-empty) {
                error make {msg: $"No versions found for ($extension_name) in OCI registry"}
            }

            # Get latest semver version
            $tags | where ($it =~ '^v?\d+\.\d+\.\d+') | sort-by-semver | last
        } else {
            $version
        }

        log-info $"Downloading ($extension_name):($resolved_version) from OCI registry"

        # Get temp extraction path
        let dest = (get-temp-extraction-path $extension_type $extension_name $resolved_version)
        mkdir $dest

        # Pull artifact
        let pull_result = (oci-pull-artifact
            $config.registry
            $config.namespace
            $extension_name
            $resolved_version
            $dest
            --auth-token $token
        )

        if not $pull_result {
            error make {msg: $"Failed to pull OCI artifact ($extension_name):($resolved_version)"}
        }

        # Get manifest
        let manifest = (oci-get-artifact-manifest
            $config.registry
            $config.namespace
            $extension_name
            $resolved_version
            --auth-token $token
        )

        {
            success: true
            path: $dest
            version: $resolved_version
            metadata: {manifest: $manifest}
        }
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        log-error $"OCI download failed: ($result.stderr)"
        {success: false, error: $result.stderr}
    }
}

# Download extension from Gitea
def download-from-gitea [
    extension_type: string
    extension_name: string
    version?: string
] {
    let result = (do {
        # TODO: Implement Gitea download
        # This is a placeholder for future implementation
        log-warn "Gitea source not yet implemented"

        {
            success: false
            error: "Gitea source not yet implemented"
        }
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        {success: false, error: $result.stderr}
    }
}

# Resolve local path for extension
def resolve-local-path [
    extension_type: string
    extension_name: string
] {
    let local_path = (try-resolve-local-path $extension_type $extension_name)

    if ($local_path | is-empty) {
        return {
            success: false
            error: $"Extension ($extension_name) not found locally"
        }
    }

    {
        success: true
        path: $local_path
        version: "local"
        metadata: {}
    }
}

# Try to resolve local path
def try-resolve-local-path [
    extension_type: string
    extension_name: string
] {
    # Check extension paths from loader.nu
    let extension_paths = [
        ($env.PWD | path join ".provisioning" "extensions")
        ($env.HOME | path join ".provisioning-extensions")
        "/opt/provisioning-extensions"
        (get-config-value "paths.extensions" "")
    ] | where ($it | is-not-empty) | where ($it | path exists)

    # Search for extension
    for base_path in $extension_paths {
        let ext_path = match $extension_type {
            "provider" => ($base_path | path join "providers" $extension_name)
            "taskserv" => ($base_path | path join "taskservs" $extension_name)
            "cluster" => ($base_path | path join "clusters" $extension_name)
            _ => ($base_path | path join $extension_name)
        }

        if ($ext_path | path exists) {
            return $ext_path
        }
    }

    ""
}

# Load extension from filesystem path
def load-from-path [
    extension_type: string
    extension_name: string
    path: string
] {
    let result = (do {
        log-debug $"Loading extension from path: ($path)"

        # Validate extension structure
        let validation = (validate-extension-structure $path)

        if not $validation.valid {
            error make {msg: $"Invalid extension structure: ($validation.errors)"}
        }

        # Load manifest
        let manifest_file = ($path | path join "extension.yaml")
        let manifest = if ($manifest_file | path exists) {
            open $manifest_file | from yaml
        } else {
            # Fallback to old manifest.yaml
            load-manifest $path
        }

        # Check if extension is allowed
        if not (is-extension-allowed $manifest) {
            error make {msg: $"Extension ($extension_name) is not allowed by policy"}
        }

        # Check requirements
        if not (check-requirements $manifest) {
            error make {msg: $"Extension ($extension_name) requirements not met"}
        }

        # Load hooks
        let hooks = (load-hooks $path $manifest)

        log-info $"Successfully loaded ($extension_name) from ($path)"

        {
            success: true
            name: $extension_name
            type: $extension_type
            path: $path
            manifest: $manifest
            hooks: $hooks
        }
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        log-error $"Failed to load from path: ($result.stderr)"
        {success: false, error: $result.stderr}
    }
}

# Validate extension directory structure
def validate-extension-structure [path: string] {
    let required_files = ["extension.yaml"]
    let required_dirs = []  # Optional: ["nickel", "scripts"]

    mut errors = []

    # Check required files
    for file in $required_files {
        let file_path = ($path | path join $file)
        if not ($file_path | path exists) {
            $errors = ($errors | append $"Missing required file: ($file)")
        }
    }

    # Check required directories (if any)
    for dir in $required_dirs {
        let dir_path = ($path | path join $dir)
        if not ($dir_path | path exists) {
            $errors = ($errors | append $"Missing required directory: ($dir)")
        }
    }

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

# Save downloaded extension to cache
def save-to-cache [
    extension_type: string
    extension_name: string
    version: string
    path: string
    source_type: string
    metadata: record
] {
    match $source_type {
        "oci" => {
            let manifest = ($metadata.manifest? | default {})
            save-oci-to-cache $extension_type $extension_name $version $path $manifest
        }
        "gitea" => {
            save-gitea-to-cache $extension_type $extension_name $version $path $metadata
        }
        _ => {
            log-debug $"Not caching extension from source: ($source_type)"
        }
    }
}

# Check if Gitea is available
def is-gitea-available [] {
    # TODO: Implement Gitea availability check
    false
}

# Helper: Sort by semver
def sort-by-semver [] {
    $in | sort-by --custom {|a, b|
        compare-semver-versions $a $b
    }
}

# Helper: Compare semver versions
def compare-semver-versions [a: string, b: string] {
    let a_parts = ($a | str replace 'v' '' | split row '.')
    let b_parts = ($b | str replace 'v' '' | split row '.')

    for i in 0..2 {
        let a_num = if ($a_parts | length) > $i { $a_parts | get $i | into int } else { 0 }
        let b_num = if ($b_parts | length) > $i { $b_parts | get $i | into int } else { 0 }

        if $a_num < $b_num {
            return (-1)
        } else if $a_num > $b_num {
            return 1
        }
    }

    0
}
