#!/usr/bin/env nu
# Prepare script for fip taskserv.
# Determines which FIPs apply to this server from floating_ips.ncl assignment policy,
# resolves their IP addresses, and writes FLOATING_IPS + IFACE to env-fip.

let vars_path = ($env.PROVISIONING_VARS? | default "")
if ($vars_path | is-empty) or not ($vars_path | path exists) {
    print $"🛑 PROVISIONING_VARS not set or not found: ($vars_path)"
    exit 1
}

let wk_data  = (open $vars_path)
let iface    = ($wk_data | get -o taskserv.interface | default "eth0")
let hostname = ($wk_data | get -o server.hostname | default "")

if ($hostname | is-empty) {
    print "🛑 server.hostname not set in vars"
    exit 1
}

# Resolve workspace root
let ws_root = do {
    let explicit = ($env.PROVISIONING_WORKSPACE_PATH? | default "")
    if ($explicit | is-not-empty) { $explicit } else {
        let wss = ($env.PROVISIONING_WORKSPACES? | default "")
        if ($wss | is-not-empty) { $wss | path dirname } else { "" }
    }
}

# Load assignment policy from floating_ips.ncl
def load-fip-policy [ws_root: string]: nothing -> record {
    let fips_ncl = ($ws_root | path join "infra/lib/floating_ips.ncl")
    if not ($fips_ncl | path exists) { return {} }
    let prov = ($env.PROVISIONING? | default ($ws_root | path join ".." ".." "provisioning" | path expand))
    let import_args = ["--import-path" $prov, "--import-path" $ws_root]
    let r = (do { ^nickel export --format json $fips_ncl ...$import_args } | complete)
    if $r.exit_code != 0 { return {} }
    $r.stdout | from json
}

# Determine which FIPs apply to this hostname based on assignment policy
def applicable-fips [fips: record, hostname: string, node_labels: record]: nothing -> list {
    $fips | transpose key val | get val | where {|fip|
        let mode = ($fip | get -o assignment.mode | default null)
        match $mode {
            "pinned" => {
                ($fip | get -o assignment.node | default "") == $hostname
            },
            "floating" => {
                let short_name = ($fip.name | str replace "librecloud-fip-" "")
                let label_key  = $"can_use_fip.($short_name)"
                let excluded   = ($fip | get -o assignment.exclude | default [])
                let label_ok   = ($node_labels | get -o $label_key | default "") == "true"
                $label_ok and not ($excluded | any {|e| $e == $hostname})
            },
            _ => { false },
        }
    }
}

# Load bootstrap state for IP resolution
let bs_data = do {
    if ($ws_root | is-not-empty) {
        let bs_path = ($ws_root | path join ".provisioning-state.json")
        if ($bs_path | path exists) { open $bs_path } else { {} }
    } else { {} }
}

def resolve-ip [fip_name: string, hostname: string, bs_data: record, ws_root: string]: nothing -> string {
    # Bootstrap state is keyed by FIP name — always correct for multi-FIP scenarios.
    # servers-state.floating_ip_address is only the server's primary assigned FIP, so
    # checking it first gives the wrong IP when multiple FIPs apply to one server.
    let fip_key = ($fip_name | str replace --all "librecloud-fip-" "" | str replace --all "-" "_")
    let bs_rec = do -i { $bs_data.bootstrap.floating_ips | get $fip_key } | default null
    if $bs_rec != null {
        let addr = ($bs_rec | get -o ip | default "")
        if ($addr | is-not-empty) {
            print $"✓ ($fip_name) → ($addr) [bootstrap-state]"
            return $addr
        }
    }
    # Fallback: servers-state (single-FIP legacy, or bootstrap state missing)
    if ($ws_root | is-not-empty) and ($hostname | is-not-empty) {
        let infra_dir = ($ws_root | path join "infra")
        if ($infra_dir | path exists) {
            let candidates = (do -i { ls $infra_dir }
                | where type == "dir"
                | get name
                | each {|d| $d | path join ".servers-state.json"}
                | where {|p| $p | path exists})
            for sc in $candidates {
                let rec = (open $sc | get -o $hostname | default null)
                if $rec != null {
                    let addr = ($rec | get -o floating_ip_address | default "")
                    if ($addr | is-not-empty) {
                        print $"✓ ($fip_name) → ($addr) [servers-state]"
                        return $addr
                    }
                }
            }
        }
    }
    ""
}

let fips = if ($ws_root | is-not-empty) { load-fip-policy $ws_root } else { {} }

let applicable = if ($fips | is-empty) {
    # Fallback: legacy floating_ips array on server config
    let arr = ($wk_data | get -o server.floating_ips | default [])
    if ($arr | is-not-empty) {
        $arr | each {|name| { name: $name }}
    } else {
        let singular = ($wk_data | get -o server.floating_ip | default "")
        if ($singular | is-not-empty) { [{ name: $singular }] } else { [] }
    }
} else {
    let node_labels = ($wk_data | get -o server.node_labels | default {})
    applicable-fips $fips $hostname $node_labels
}

if ($applicable | is-empty) {
    print $"🛑 No FIPs applicable to ($hostname) — check floating_ips.ncl assignment policy"
    exit 1
}

let resolved = ($applicable | each {|fip|
    let addr = (resolve-ip $fip.name $hostname $bs_data $ws_root)
    if ($addr | is-empty) {
        print $"🛑 Cannot resolve IP for FIP '($fip.name)' on ($hostname) — run: provisioning bootstrap"
        exit 1
    }
    $addr
})

let floating_ips_str = ($resolved | str join " ")
let out_dir = ($env.PROVISIONING_WK_ENV_PATH? | default ".")
$"IFACE=($iface)\nFLOATING_IPS=\"($floating_ips_str)\"\n" | save -f ($out_dir | path join "env-fip")
print $"✓ env-fip: IFACE=($iface) FLOATING_IPS=($floating_ips_str)"
