use ../lib_provisioning/workspace *
use ../lib_provisioning/utils/nickel_processor.nu [ncl-eval, ncl-eval-soft, default-ncl-paths]

# Resolve the provisioning root for --import-path resolution.
def comp-prov-root []: nothing -> string {
    $env.PROVISIONING? | default "/usr/local/provisioning"
}

# Export a Nickel file as parsed JSON. Uses default-ncl-paths to match the daemon's
# cache key derivation — otherwise every call misses and re-runs nickel export cold.
def comp-ncl-export [ws_root: string, rel_path: string]: nothing -> record {
    let full_path = ($ws_root | path join $rel_path)
    ncl-eval $full_path (default-ncl-paths $ws_root)
}

# Resolve workspace name: explicit --workspace flag or active workspace.
def comp-resolve-workspace [workspace: string]: nothing -> string {
    if ($workspace | is-not-empty) {
        return $workspace
    }
    let details = (get-active-workspace-details)
    if ($details == null) {
        error make { msg: "No active workspace — pass --workspace or activate one first" }
    }
    $details.name
}

# Validate cluster capabilities against real infrastructure state.
#
# Exports infra/{infra}/capabilities.ncl from the workspace and compares declared
# capabilities (storage_classes, ingress_class) against live kubectl output.
# Returns a table of check / expected / actual / status rows.
#
# Usage:
#   provisioning validate capabilities --workspace libre-daoshi --infra wuji
export def "main validate capabilities" [
    --workspace (-w): string    # Workspace name (default: active)
    --infra (-i): string = "wuji"  # Infra name
]: nothing -> table<check: string, expected: string, actual: string, status: string> {
    let ws_name = (comp-resolve-workspace $workspace)
    let ws_root = (get-workspace-path $ws_name)
    if ($ws_root | is-empty) {
        error make { msg: $"Workspace '($ws_name)' not found in registry." }
    }

    let caps_path = ($ws_root | path join "infra" $infra "capabilities.ncl")
    if not ($caps_path | path exists) {
        error make { msg: $"capabilities.ncl not found at ($caps_path)" }
    }

    let caps = (comp-ncl-export $ws_root ($"infra/($infra)/capabilities.ncl"))
    mut rows: list<record<check: string, expected: string, actual: string, status: string>> = []

    # Check storage classes
    let declared_sc = ($caps | get -o provides | default {} | get -o storage_classes | default [] | each { $in | into string })
    if ($declared_sc | is-not-empty) {
        let sc_result = (do { ^kubectl get sc --no-headers -o custom-columns=NAME:.metadata.name } | complete)
        let actual_sc = if $sc_result.exit_code == 0 {
            $sc_result.stdout | lines | where { $in | is-not-empty }
        } else {
            []
        }
        for sc in $declared_sc {
            let found = ($actual_sc | any { $in == $sc })
            $rows = ($rows | append {
                check: "storage_class",
                expected: $sc,
                actual: (if $found { $sc } else { "<not found>" }),
                status: (if $found { "ok" } else { "MISSING" }),
            })
        }
    }

    # Check ingress class
    let declared_ic = ($caps | get -o provides | default {} | get -o ingress_class | default "")
    if ($declared_ic | is-not-empty) {
        let ic_result = (do { ^kubectl get ingressclass --no-headers -o custom-columns=NAME:.metadata.name } | complete)
        let actual_ic = if $ic_result.exit_code == 0 {
            $ic_result.stdout | lines | where { $in | is-not-empty }
        } else {
            []
        }
        let found = ($actual_ic | any { $in == $declared_ic })
        $rows = ($rows | append {
            check: "ingress_class",
            expected: $declared_ic,
            actual: (if $found { $declared_ic } else { "<not found>" }),
            status: (if $found { "ok" } else { "MISSING" }),
        })
    }

    $rows
}

# Validate component configuration against workspace capabilities and server inventory.
#
# Exports infra/{infra}/settings.ncl and checks each component:
#   - taskserv mode: verifies the target server exists in the servers map.
#   - cluster mode:  verifies the storage_class (if declared) is in capabilities.storage_classes.
# Returns a table of component / check / status / detail rows.
#
# Usage:
#   provisioning validate components --workspace libre-daoshi --infra wuji
export def "main validate components" [
    --workspace (-w): string    # Workspace name (default: active)
    --infra (-i): string = "wuji"  # Infra name
]: nothing -> table<component: string, check: string, status: string, detail: string> {
    let ws_name = (comp-resolve-workspace $workspace)
    let ws_root = (get-workspace-path $ws_name)
    if ($ws_root | is-empty) {
        error make { msg: $"Workspace '($ws_name)' not found in registry." }
    }

    let settings = (comp-ncl-export $ws_root ($"infra/($infra)/settings.ncl"))
    let components = ($settings | get -o components | default {})

    # Load capabilities for storage_class cross-check (best-effort: skip if absent).
    let caps_path = ($ws_root | path join "infra" $infra "capabilities.ncl")
    let caps_sc: list<string> = if ($caps_path | path exists) {
        let c = (comp-ncl-export $ws_root ($"infra/($infra)/capabilities.ncl"))
        $c | get -o provides | default {} | get -o storage_classes | default [] | each { $in | into string }
    } else {
        []
    }

    # Load servers for taskserv target validation (best-effort).
    let servers_path = ($ws_root | path join "infra" $infra "servers.ncl")
    let server_names: list<string> = if ($servers_path | path exists) {
        ncl-eval-soft $servers_path (default-ncl-paths $ws_root) {} | get -o servers | default {} | columns
    } else {
        []
    }

    mut rows: list<record<component: string, check: string, status: string, detail: string>> = []

    let comp_names = ($components | columns)
    for comp_name in $comp_names {
        let comp = ($components | get $comp_name)
        let mode = ($comp | get -o mode | default "cluster")

        if $mode == "taskserv" {
            let target = ($comp | get -o target | default "")
            if ($target | is-empty) {
                $rows = ($rows | append { component: $comp_name, check: "target_server", status: "WARN", detail: "mode=taskserv but no target specified" })
            } else if ($server_names | is-empty) {
                $rows = ($rows | append { component: $comp_name, check: "target_server", status: "SKIP", detail: $"servers.ncl not available — cannot verify '($target)'" })
            } else {
                let found = ($server_names | any { $in == $target })
                $rows = ($rows | append {
                    component: $comp_name,
                    check: "target_server",
                    status: (if $found { "ok" } else { "MISSING" }),
                    detail: (if $found { $"target '($target)' exists" } else { $"target '($target)' not found in servers" }),
                })
            }
        } else if $mode == "cluster" {
            let sc = ($comp | get -o storage_class | default "")
            if ($sc | is-not-empty) {
                if ($caps_sc | is-empty) {
                    $rows = ($rows | append { component: $comp_name, check: "storage_class", status: "SKIP", detail: "capabilities.ncl not available" })
                } else {
                    let found = ($caps_sc | any { $in == $sc })
                    $rows = ($rows | append {
                        component: $comp_name,
                        check: "storage_class",
                        status: (if $found { "ok" } else { "MISSING" }),
                        detail: (if $found { $"storage_class '($sc)' available" } else { $"storage_class '($sc)' not in capabilities" }),
                    })
                }
            }
        }

        # Always emit a baseline row even when no sub-checks apply.
        if ($rows | where component == $comp_name | is-empty) {
            $rows = ($rows | append { component: $comp_name, check: "declared", status: "ok", detail: $"mode=($mode)" })
        }
    }

    $rows
}

# List all components declared in the workspace infra settings.
#
# Reads infra/{infra}/settings.ncl and renders each component with its name,
# mode, target or namespace, and version (if available in the component config).
# Returns a table of name / mode / target / namespace / version rows.
#
# Usage:
#   provisioning component list --workspace libre-daoshi --infra wuji
export def "main component list" [
    --workspace (-w): string    # Workspace name (default: active)
    --infra (-i): string = "wuji"  # Infra name
]: nothing -> table<name: string, mode: string, target: string, namespace: string, version: string> {
    let ws_name = (comp-resolve-workspace $workspace)
    let ws_root = (get-workspace-path $ws_name)
    if ($ws_root | is-empty) {
        error make { msg: $"Workspace '($ws_name)' not found in registry." }
    }

    let settings = (comp-ncl-export $ws_root ($"infra/($infra)/settings.ncl"))
    let components = ($settings | get -o components | default {})

    $components | columns | each { |comp_name|
        let comp = ($components | get $comp_name)
        {
            name:      $comp_name,
            mode:      ($comp | get -o mode      | default "cluster"),
            target:    ($comp | get -o target    | default ""),
            namespace: ($comp | get -o namespace | default ""),
            version:   ($comp | get -o version   | default ""),
        }
    }
}

# Show the full unified view of a single component declaration.
#
# Exports infra/{infra}/components/{name}.ncl from the workspace. If that file
# does not exist, falls back to the component entry in settings.ncl.
# Returns a record with mode, target, namespace, requires, provides, and operations.
#
# Usage:
#   provisioning component info postgresql --workspace libre-daoshi --infra wuji
export def "main component info" [
    name: string                # Component name
    --workspace (-w): string    # Workspace name (default: active)
    --infra (-i): string = "wuji"  # Infra name
]: nothing -> record {
    let ws_name = (comp-resolve-workspace $workspace)
    let ws_root = (get-workspace-path $ws_name)
    if ($ws_root | is-empty) {
        error make { msg: $"Workspace '($ws_name)' not found in registry." }
    }

    # Prefer the per-component NCL file; fall back to settings.ncl entry.
    let comp_ncl_path = ($ws_root | path join "infra" $infra "components" $"($name).ncl")
    let comp = if ($comp_ncl_path | path exists) {
        comp-ncl-export $ws_root ($"infra/($infra)/components/($name).ncl")
    } else {
        let settings = (comp-ncl-export $ws_root ($"infra/($infra)/settings.ncl"))
        let components = ($settings | get -o components | default {})
        if not ($name in ($components | columns)) {
            error make { msg: $"Component '($name)' not declared in infra/($infra)/settings.ncl and no per-component NCL found at ($comp_ncl_path)" }
        }
        $components | get $name
    }

    {
        mode:       ($comp | get -o mode       | default "cluster"),
        target:     ($comp | get -o target     | default ""),
        namespace:  ($comp | get -o namespace  | default ""),
        version:    ($comp | get -o version    | default ""),
        requires:   ($comp | get -o requires   | default []),
        provides:   ($comp | get -o provides   | default {}),
        operations: ($comp | get -o operations | default []),
    }
}
