# Multi-Repository Dependency Resolution System
# Handles dependency resolution across multiple repositories with OCI support
# Version: 1.0.0

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

# Dependency resolution cache
let $cache_dir = ($env.HOME | path join ".provisioning" "dep-cache")

# Initialize dependency cache
export def init-cache [] -> nothing {
    mkdir $cache_dir
}

# Load repository configuration
export def load-repositories [] -> list<record> {
    let config = (get-config)

    # Check if dependencies configuration exists
    if ($config.dependencies? | is-empty) {
        return []
    }

    # Build repository list from configuration
    let repos = []

    # Core repository
    if ($config.dependencies.core? | is-not-empty) {
        $repos = ($repos | append {
            name: "core"
            type: "core"
            source: ($config.dependencies.core.source)
            priority: 1000
            enabled: true
        })
    }

    # Extensions repository
    if ($config.dependencies.extensions? | is-not-empty) {
        $repos = ($repos | append {
            name: "extensions"
            type: "extensions"
            source_type: ($config.dependencies.extensions.source_type)
            source: ($config.dependencies.extensions)
            priority: 500
            enabled: true
        })
    }

    # Platform repository
    if ($config.dependencies.platform? | is-not-empty) {
        $repos = ($repos | append {
            name: "platform"
            type: "platform"
            source_type: ($config.dependencies.platform.source_type)
            source: ($config.dependencies.platform)
            priority: 300
            enabled: true
        })
    }

    $repos
}

# Resolve dependency from repository
export def resolve-dependency [
    dep_name: string          # Dependency name (e.g., "kubernetes")
    dep_type: string          # Dependency type (provider, taskserv, cluster)
    version?: string          # Version constraint (e.g., "1.28.0", ">=1.25.0")
    --source-type: string = "oci"  # Source type override
] -> record {
    let config = (get-config)
    let repos = (load-repositories)

    log info $"Resolving dependency: ($dep_type)/($dep_name):($version)"

    # Find repository for this dependency type
    let repo = ($repos | where type == "extensions" | first)

    if ($repo | is-empty) {
        error make {
            msg: $"No repository found for ($dep_type)"
        }
    }

    # Resolve based on source type
    match ($repo.source_type? | default "oci") {
        "oci" => { resolve-oci-dependency $dep_name $dep_type $version $repo }
        "gitea" => { resolve-gitea-dependency $dep_name $dep_type $version $repo }
        "local" => { resolve-local-dependency $dep_name $dep_type $version $repo }
        _ => {
            error make {
                msg: $"Unsupported source type: ($repo.source_type)"
            }
        }
    }
}

# Resolve OCI-based dependency
def resolve-oci-dependency [
    dep_name: string
    dep_type: string
    version?: string
    repo: record
] -> record {
    let oci_config = ($repo.source.oci)
    let registry = $oci_config.registry
    let namespace = $oci_config.namespace

    # Get available versions
    let auth_token = if ($oci_config.auth_token_path? | is-not-empty) {
        open ($oci_config.auth_token_path | path expand)
    } else {
        ""
    }

    let insecure = ($oci_config.tls_enabled == false)

    let available_versions = (get-artifact-tags $registry $namespace $dep_name
                              --auth-token $auth_token
                              --insecure=$insecure)

    if ($available_versions | is-empty) {
        error make {
            msg: $"Dependency not found: ($dep_name)"
        }
    }

    # Select version based on constraint
    let selected_version = if ($version | is-not-empty) {
        # For now, exact match. TODO: Implement semver constraint matching
        if ($version in $available_versions) {
            $version
        } else {
            error make {
                msg: $"Version ($version) not found for ($dep_name)"
            }
        }
    } else {
        # Select latest version (assumes sorted)
        $available_versions | first
    }

    {
        name: $dep_name
        type: $dep_type
        version: $selected_version
        source: "oci"
        registry: $registry
        namespace: $namespace
        reference: $"($registry)/($namespace)/($dep_name):($selected_version)"
        available_versions: $available_versions
    }
}

# Resolve Gitea-based dependency
def resolve-gitea-dependency [
    dep_name: string
    dep_type: string
    version?: string
    repo: record
] -> record {
    let gitea_config = ($repo.source.gitea)

    # For Gitea, we'll use git tags as versions
    # This requires cloning or using Gitea API

    {
        name: $dep_name
        type: $dep_type
        version: ($version | default "main")
        source: "gitea"
        url: $"($gitea_config.url)/($gitea_config.organization)/($dep_name)"
        branch: ($gitea_config.branch? | default "main")
    }
}

# Resolve local-based dependency
def resolve-local-dependency [
    dep_name: string
    dep_type: string
    version?: string
    repo: record
] -> record {
    let local_config = ($repo.source.local)
    let dep_path = ($local_config.path | path join $dep_type | path join $dep_name)

    if not ($dep_path | path exists) {
        error make {
            msg: $"Local dependency not found: ($dep_path)"
        }
    }

    {
        name: $dep_name
        type: $dep_type
        version: ($version | default "local")
        source: "local"
        path: $dep_path
    }
}

# Install resolved dependency
export def install-dependency [
    dep: record               # Resolved dependency
    --destination: string     # Override destination path
] -> string {
    let config = (get-config)

    # Determine installation path
    let install_path = if ($destination | is-not-empty) {
        $destination
    } else {
        match $dep.type {
            "taskserv" => ($config.paths.extensions | path join "taskservs" $dep.name)
            "provider" => ($config.paths.extensions | path join "providers" $dep.name)
            "cluster" => ($config.paths.extensions | path join "clusters" $dep.name)
            _ => {
                error make {
                    msg: $"Unknown dependency type: ($dep.type)"
                }
            }
        }
    }

    log info $"Installing ($dep.name):($dep.version) to ($install_path)"

    # Install based on source
    match $dep.source {
        "oci" => { install-oci-dependency $dep $install_path }
        "gitea" => { install-gitea-dependency $dep $install_path }
        "local" => { install-local-dependency $dep $install_path }
        _ => {
            error make {
                msg: $"Unsupported source: ($dep.source)"
            }
        }
    }
}

# Install OCI-based dependency
def install-oci-dependency [
    dep: record
    install_path: string
] -> string {
    let config = (get-config)

    # Get OCI configuration
    let repos = (load-repositories)
    let repo = ($repos | where type == "extensions" | first)
    let oci_config = ($repo.source.oci)

    let auth_token = if ($oci_config.auth_token_path? | is-not-empty) {
        open ($oci_config.auth_token_path | path expand)
    } else {
        ""
    }

    let insecure = ($oci_config.tls_enabled == false)

    # Pull artifact
    let result = (pull-artifact $dep.registry $dep.namespace $dep.name $dep.version $install_path
                  --auth-token $auth_token
                  --insecure=$insecure)

    if not $result {
        error make {
            msg: $"Failed to install ($dep.name):($dep.version)"
        }
    }

    $install_path
}

# Install Gitea-based dependency
def install-gitea-dependency [
    dep: record
    install_path: string
] -> string {
    # Clone repository
    log info $"Cloning from ($dep.url)"

    let result = (do {
        git clone --branch $dep.branch --depth 1 $dep.url $install_path
        $install_path
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        error make {
            msg: $"Failed to clone [$dep.url]: ($result.stderr)"
        }
    }
}

# Install local-based dependency
def install-local-dependency [
    dep: record
    install_path: string
] -> string {
    # Copy local dependency
    log info $"Copying from ($dep.path)"

    let result = (do {
        cp -r $dep.path $install_path
        $install_path
    } | complete)

    if $result.exit_code == 0 {
        $result.stdout
    } else {
        error make {
            msg: $"Failed to copy [$dep.path]: ($result.stderr)"
        }
    }
}

# Resolve and install all dependencies for an extension
export def resolve-extension-deps [
    extension_name: string
    extension_type: string
    --recursive               # Recursively resolve dependencies
] -> list<record> {
    log info $"Resolving dependencies for ($extension_type)/($extension_name)"

    # Load extension manifest
    let manifest = (load-extension-manifest $extension_name $extension_type)

    if ($manifest | is-empty) {
        log warning $"No manifest found for ($extension_name)"
        return []
    }

    if ($manifest.dependencies? | is-empty) {
        log info $"No dependencies for ($extension_name)"
        return []
    }

    # Resolve each dependency
    let resolved = ($manifest.dependencies | items | each { |dep|
        let dep_name = ($dep | get 0)
        let dep_version = ($dep | get 1)

        log info $"  Resolving ($dep_name):($dep_version)"

        let result = (do {
            let resolved_dep = (resolve-dependency $dep_name "taskserv" $dep_version)

            # Install dependency
            let install_path = (install-dependency $resolved_dep)

            # Recursive resolution if enabled
            if $recursive {
                let sub_deps = (resolve-extension-deps $dep_name "taskserv" --recursive)
                [$resolved_dep] | append $sub_deps
            } else {
                [$resolved_dep]
            }
        } | complete)

        if $result.exit_code == 0 {
            $result.stdout
        } else {
            log error $"  Failed to resolve [$dep_name]: ($result.stderr)"
            []
        }
    } | flatten)

    $resolved
}

# Load extension manifest
def load-extension-manifest [
    extension_name: string
    extension_type: string
] -> record {
    let config = (get-config)

    # Try to find manifest.yaml in extension directory
    let ext_path = match $extension_type {
        "taskserv" => ($config.paths.extensions | path join "taskservs" $extension_name)
        "provider" => ($config.paths.extensions | path join "providers" $extension_name)
        "cluster" => ($config.paths.extensions | path join "clusters" $extension_name)
        _ => ""
    }

    if ($ext_path | is-empty) or not ($ext_path | path exists) {
        return {}
    }

    let manifest_path = ($ext_path | path join "manifest.yaml")

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

    open $manifest_path | from yaml
}

# Check for dependency updates
export def check-dependency-updates [
    extension_name: string
    extension_type: string
] -> list<record> {
    log info $"Checking updates for ($extension_type)/($extension_name)"

    # Load current manifest
    let manifest = (load-extension-manifest $extension_name $extension_type)

    if ($manifest | is-empty) or ($manifest.dependencies? | is-empty) {
        return []
    }

    # Check each dependency for updates
    let updates = ($manifest.dependencies | items | each { |dep|
        let dep_name = ($dep | get 0)
        let current_version = ($dep | get 1)

        let result = (do {
            let resolved = (resolve-dependency $dep_name "taskserv")
            let latest_version = $resolved.version

            if $current_version != $latest_version {
                {
                    name: $dep_name
                    current: $current_version
                    latest: $latest_version
                    update_available: true
                }
            } else {
                {
                    name: $dep_name
                    current: $current_version
                    latest: $latest_version
                    update_available: false
                }
            }
        } | complete)

        if $result.exit_code == 0 {
            $result.stdout
        } else {
            {
                name: $dep_name
                current: $current_version
                latest: "unknown"
                update_available: false
            }
        }
    })

    $updates
}

# Validate dependency graph (detect cycles, conflicts)
export def validate-dependency-graph [
    extension_name: string
    extension_type: string
] -> record {
    log info $"Validating dependency graph for ($extension_type)/($extension_name)"

    # Build dependency graph
    let graph = (build-dependency-graph $extension_name $extension_type)

    # Check for cycles
    let has_cycles = (detect-cycles $graph)

    # Check for conflicts
    let conflicts = (detect-conflicts $graph)

    {
        valid: (not $has_cycles and ($conflicts | is-empty))
        has_cycles: $has_cycles
        conflicts: $conflicts
        graph: $graph
    }
}

# Build dependency graph recursively
def build-dependency-graph [
    extension_name: string
    extension_type: string
    --visited: list<string> = []
] -> record {
    # Prevent infinite recursion
    if ($extension_name in $visited) {
        return {
            name: $extension_name
            type: $extension_type
            dependencies: []
            circular: true
        }
    }

    let new_visited = ($visited | append $extension_name)

    # Load manifest
    let manifest = (load-extension-manifest $extension_name $extension_type)

    if ($manifest | is-empty) or ($manifest.dependencies? | is-empty) {
        return {
            name: $extension_name
            type: $extension_type
            dependencies: []
            circular: false
        }
    }

    # Build graph for each dependency
    let deps = ($manifest.dependencies | items | each { |dep|
        let dep_name = ($dep | get 0)
        let dep_version = ($dep | get 1)

        build-dependency-graph $dep_name "taskserv" --visited $new_visited
    })

    {
        name: $extension_name
        type: $extension_type
        dependencies: $deps
        circular: false
    }
}

# Detect cycles in dependency graph
def detect-cycles [
    graph: record
] -> bool {
    if $graph.circular {
        return true
    }

    if ($graph.dependencies | is-empty) {
        return false
    }

    ($graph.dependencies | any { |dep| detect-cycles $dep })
}

# Detect conflicts in dependency graph
def detect-conflicts [
    graph: record
] -> list<record> {
    # TODO: Implement conflict detection
    # - Check for version conflicts
    # - Check for conflicting dependencies
    []
}
