use ../lib_provisioning/workspace *
use ../lib_provisioning/user/config.nu [get-workspace-path, get-active-workspace-details]
use ../../../extensions/providers/hetzner/nulib/hetzner/api.nu *
use ../lib_provisioning/utils/nickel_processor.nu [ncl-eval, ncl-eval-soft]

# Export a Nickel file relative to the workspace root, with provisioning import path.
def bootstrap-ncl-export [ws_root: string, rel_path: string]: nothing -> record {
    let prov_root = ($env.PROVISIONING? | default "/usr/local/provisioning")
    let full_path = ($ws_root | path join $rel_path)
    let result = (ncl-eval $full_path [$ws_root $prov_root])
    $result
}

# Ensure the private network exists and all declared subnets are present.
# Creates the network if absent; reconciles subnets for existing networks.
def bootstrap-network [cfg: record]: nothing -> record {
    let existing = (hetzner_api_list_networks | where name == $cfg.name)
    let network = if ($existing | is-not-empty) {
        print $"  network ($cfg.name) already exists — skip"
        ($existing | first)
    } else {
        print $"  creating network ($cfg.name) ..."
        let payload = { name: $cfg.name, ip_range: $cfg.ip_range, subnets: ($cfg.subnets? | default []) }
        let payload = if ("labels" in ($cfg | columns)) {
            $payload | insert labels $cfg.labels
        } else {
            $payload
        }
        let created = (hetzner_api_create_network $payload)

        let delete_protected = ($cfg | get -o protection.delete | default false)
        if $delete_protected {
            print $"  enabling delete protection on ($cfg.name) ..."
            let _action = (hetzner_api_network_change_protection ($created.id | into string) true)
        }
        $created
    }

    # Reconcile subnets: add any declared subnets that are missing from the network.
    let declared = ($cfg.subnets? | default [])
    if ($declared | is-not-empty) {
        let network_detail = (hetzner_api_network_info ($network.id | into string))
        let existing_ranges = ($network_detail.subnets? | default [] | each { |s| $s.ip_range })
        for sn in $declared {
            if not ($existing_ranges | any { |r| $r == $sn.ip_range }) {
                print $"  adding subnet ($sn.ip_range) to ($cfg.name) ..."
                let _action = (hetzner_api_network_add_subnet ($network.id | into string) $sn)
                print $"  ✓ subnet ($sn.ip_range) added"
            } else {
                print $"  subnet ($sn.ip_range) already present — skip"
            }
        }
    }

    $network
}

# Ensure the SSH key exists in Hetzner Cloud, importing it if absent. Returns the ssh_key record.
def bootstrap-ssh-key [cfg: record]: nothing -> record {
    let existing = (hetzner_api_list_ssh_keys | where name == $cfg.name)
    if ($existing | is-not-empty) {
        print $"  ssh_key ($cfg.name) already exists — skip"
        return ($existing | first)
    }
    let key_path = ($cfg.public_key_path | str replace "~" $nu.home-dir)
    if (($key_path | path exists) == false) {
        error make { msg: $"SSH public key not found at ($key_path)" }
    }
    let public_key = (open $key_path | str trim)
    print $"  importing ssh_key ($cfg.name) ..."
    hetzner_api_create_ssh_key $cfg.name $public_key
}

# Ensure the firewall exists, creating it if absent. Returns the firewall record.
def bootstrap-firewall [cfg: record]: nothing -> record {
    let existing = (hetzner_api_list_firewalls | where name == $cfg.name)
    if ($existing | is-not-empty) {
        print $"  firewall ($cfg.name) already exists — skip"
        return ($existing | first)
    }
    print $"  creating firewall ($cfg.name) ..."
    let payload = { name: $cfg.name, rules: $cfg.rules }
    let payload = if ("labels" in ($cfg | columns)) {
        $payload | insert labels $cfg.labels
    } else {
        $payload
    }
    hetzner_api_create_firewall $payload
}

# Ensure a Floating IP exists, creating it if absent. Returns {id, record}.
def bootstrap-floating-ip [fip: record]: nothing -> record {
    let existing = (hetzner_api_list_floating_ips | where name == $fip.name)
    if ($existing | is-not-empty) {
        let found = ($existing | first)
        print $"  floating_ip ($fip.name) already exists \(id: ($found.id)\) — skip"
        return { id: ($found.id | into string), record: $found }
    }
    print $"  creating floating_ip ($fip.name) ..."
    let description = ($fip | get -o description | default "")
    let labels = ($fip | get -o labels | default {})
    let payload = {
        type:          $fip.type,
        home_location: ($fip.location? | default ($fip.home_location? | default "")),
        name:          $fip.name,
        description:   $description,
        labels:        $labels,
    }
    let created = (hetzner_api_create_floating_ip $payload)
    let fip_id = ($created.id | into string)

    let has_ptr = ("dns_ptr" in ($fip | columns)) and (($fip.dns_ptr | is-empty) == false)
    if $has_ptr {
        print $"  setting PTR ($fip.dns_ptr) for ($created.ip) ..."
        let _action = (hetzner_api_floating_ip_set_rdns $fip_id $created.ip $fip.dns_ptr)
    }

    let delete_protected = ($fip | get -o protection.delete | default false)
    if $delete_protected {
        print $"  enabling delete protection on ($fip.name) ..."
        let _action = (hetzner_api_floating_ip_change_protection $fip_id true)
    }

    { id: $fip_id, record: $created }
}

# Persist bootstrap resource IDs to .provisioning-state.json in the workspace root.
def bootstrap-persist-state [ws_root: string, state: record]: nothing -> nothing {
    let state_path = ($ws_root | path join ".provisioning-state.json")
    let existing = if ($state_path | path exists) {
        open --raw $state_path | from json
    } else {
        {}
    }
    ($existing | merge $state) | to json --indent 2 | save --force $state_path
    print $"  state written to .provisioning-state.json"
}

# Provision L1 Hetzner resources: private network, SSH key, firewall, Floating IPs.
#
# Reads infra/bootstrap.ncl from the workspace root. All operations are idempotent —
# existing resources are detected via API list calls and skipped. Resource IDs are
# persisted to .provisioning-state.json for use by downstream L2 provisioning.
export def "main bootstrap" [
    --workspace (-w): string  # Workspace name (default: active workspace)
    --dry-run (-n)            # Print what would be created without calling the API
] : nothing -> nothing {
    # Resolve workspace: explicit flag > PWD config/provisioning.ncl > convention > active
    let ws_name = if ($workspace | is-not-empty) {
        $workspace
    } else {
        # Priority 1: config/provisioning.ncl in PWD (workspace root detection)
        let pwd_config = ($env.PWD | path join "config" "provisioning.ncl")
        let from_pwd = if ($pwd_config | path exists) {
            let cfg = (ncl-eval-soft $pwd_config [] null)
            if $cfg != null { $cfg | get -o workspace | default "" } else { "" }
        } else { "" }

        if ($from_pwd | is-not-empty) {
            $from_pwd
        } else {
            # Priority 2: convention — directory name = workspace name
            let convention = ($env.PWD | path basename)
            let convention_bootstrap = ($env.PWD | path join "infra" "bootstrap.ncl")
            if ($convention_bootstrap | path exists) {
                $convention
            } else {
                # Priority 3: active workspace
                let details = (get-active-workspace-details)
                if ($details == null) {
                    error make { msg: "No active workspace. Use --workspace or run from a workspace directory." }
                }
                $details.name
            }
        }
    }

    # Resolve workspace root: registered path > PWD (when inferred from PWD)
    let ws_root_registered = do -i { get-workspace-path $ws_name } | default ""
    let ws_root = if ($ws_root_registered | is-not-empty) {
        $ws_root_registered
    } else {
        # If not registered, we must be in the workspace root (PWD detection above)
        $env.PWD
    }
    let bootstrap_path = ($ws_root | path join "infra/bootstrap.ncl")

    if (($bootstrap_path | path exists) == false) {
        error make { msg: $"infra/bootstrap.ncl not found in workspace ($ws_name) at ($ws_root)" }
    }

    print $"Bootstrap L1 resources for workspace: ($ws_name)"
    print $"  config: ($bootstrap_path)"

    let cfg = (bootstrap-ncl-export $ws_root "infra/bootstrap.ncl")

    # Support both singular `network` and plural `networks` in bootstrap.ncl.
    let all_networks = if ("networks" in ($cfg | columns)) {
        $cfg.networks
    } else {
        [$cfg.network]
    }

    if $dry_run {
        print "DRY RUN — resources that would be created:"
        for net in $all_networks {
            print $"  network:     ($net.name) \(($net.ip_range)\)"
            for sn in ($net.subnets? | default []) {
                print $"    subnet:    ($sn.ip_range) \(($sn.type), ($sn.network_zone)\)"
            }
        }
        print $"  ssh_key:     ($cfg.ssh_key.name)"
        print $"  firewall:    ($cfg.firewall.name)"
        for rule in $cfg.firewall.rules {
            let port_str = if ($rule.port | is-empty) or ($rule.port == null) { "any" } else { $rule.port }
            let src = ($rule.source_ips | str join ", ")
            print $"    ($rule.direction) ($rule.protocol)/($port_str)  ← ($src)"
        }
        for fip in $cfg.floating_ips {
            print $"  floating_ip: ($fip.name) \(($fip.type), ($fip.home_location)\)"
        }
        return
    }

    print "\n[networks]"
    let network_results = ($all_networks | each { |net| bootstrap-network $net })
    # Primary network is the first one (used for state persistence)
    let network = ($network_results | first)

    print "\n[ssh_key]"
    let ssh_key = (bootstrap-ssh-key $cfg.ssh_key)

    print "\n[firewall]"
    let firewall = (bootstrap-firewall $cfg.firewall)

    print "\n[floating_ips]"
    let fip_results = ($cfg.floating_ips | each {|fip| bootstrap-floating-ip $fip })

    let fip_state = ($fip_results | reduce --fold {} {|entry, acc|
        let key = ($entry.record.name | str replace --all "librecloud-fip-" "" | str replace --all "-" "_")
        $acc | insert $key { id: $entry.id, ip: $entry.record.ip, name: $entry.record.name }
    })

    bootstrap-persist-state $ws_root {
        bootstrap: {
            network_id:   ($network.id | into string),
            network_name: $network.name,
            ssh_key_id:   ($ssh_key.id | into string),
            firewall_id:  ($firewall.id | into string),
            floating_ips: $fip_state,
        }
    }

    # Trigger reconcile so SurrealDB resource records reflect the just-bootstrapped state.
    # Best-effort: silently skipped if the orchestrator daemon is not running.
    let orchestrator_url = ($env.ORCHESTRATOR_URL? | default "http://localhost:8080")
    do -i { http post $"($orchestrator_url)/api/v1/infra/reconcile" {workspace: $ws_name} | ignore }

    print "\nBootstrap complete."
    print $"  network:     ($network.name) id=($network.id) range=($cfg.network.ip_range)"
    for sn in $cfg.network.subnets {
        print $"    subnet:  ($sn.ip_range) \(($sn.type), ($sn.network_zone)\)"
    }
    print $"  firewall:    ($firewall.name) id=($firewall.id) rules=($cfg.firewall.rules | length)"
    for fip in $fip_results {
        print $"  fip ($fip.record.name): id=($fip.id) ip=($fip.record.ip)"
    }
}
