use ../lib_provisioning/utils/init.nu *
use ../lib_provisioning/utils/interface.nu [_ansi _print end_run set-provisioning-out set-provisioning-no-terminal]
use ../lib_provisioning/utils/undefined.nu [invalid_task]
use ../lib_provisioning/utils/settings.nu *

# Sync .servers-state.json from live hcloud data.
# Called after create, delete, or update so server list always reflects actual state.
export def sync-servers-state-post-op [ws_root: string, infra_name: string] {
    let state_path = ($ws_root | path join "infra" | path join $infra_name | path join ".servers-state.json")

    let hcloud_res = (do { ^hcloud server list -o json } | complete)
    if $hcloud_res.exit_code != 0 or ($hcloud_res.stdout | str trim | is-empty) {
        print "  ⚠ hcloud unavailable — skipping state sync"
        return
    }
    let live = ($hcloud_res.stdout | from json)

    let fip_res = (do { ^hcloud floating-ip list -o json } | complete)
    let fip_map = if $fip_res.exit_code == 0 and ($fip_res.stdout | str trim | is-not-empty) {
        $fip_res.stdout | from json
        | reduce --fold {} {|fip, acc|
            let srv_id = ($fip | get -o server | default 0)
            if $srv_id != 0 {
                $acc | insert ($srv_id | into string) { name: $fip.name, ip: $fip.ip }
            } else { $acc }
        }
    } else { {} }

    let state = ($live | reduce --fold {} {|srv, acc|
        let fip = ($fip_map | get -o ($srv.id | into string) | default null)
        $acc | insert $srv.name {
            provider_id:         ($srv.id | into string),
            public_ip:           ($srv.public_net?.ipv4?.ip? | default ""),
            location:            ($srv.datacenter?.location?.name? | default ""),
            status:              $srv.status,
            floating_ip:         (if $fip != null { $fip.name } else { "" }),
            floating_ip_address: (if $fip != null { $fip.ip } else { "" }),
            protection_delete:   ($srv.protection?.delete? | default false),
            last_sync:           (date now | format date "%Y-%m-%dT%H:%M:%SZ"),
        }
    })

    $state | to json --indent 2 | save --force $state_path
    print $"  ✓ server state synced → ($state_path)"
}

# Delete orphaned volumes declared in the infra config that exist in Hetzner but are unattached.
def delete_orphaned_infra_volumes [settings: record, yes: bool] {
    let declared_vols = (
        $settings.data.servers
        | each {|s| $s.storage?.additional_volumes? | default []}
        | flatten
        | each {|v| $v.name}
        | uniq
    )
    if ($declared_vols | is-empty) { return }

    let live_res = (do { ^hcloud volume list -o json } | complete)
    let live_vols = if $live_res.exit_code == 0 and ($live_res.stdout | str trim | is-not-empty) {
        $live_res.stdout | from json
    } else { [] }

    let orphans = ($live_vols | where {|v|
        ($declared_vols | any {|n| $n == $v.name}) and ($v.server? | default null) == null
    })

    if ($orphans | is-empty) { return }

    _print $"\nOrphaned volumes from infra: ($orphans | each {|v| $v.name} | str join ', ')"
    if not $yes {
        _print "Delete orphaned volumes? Data will be lost. [y/N] "
        let ans = (input "")
        if $ans not-in ["y", "Y", "yes"] { _print "Skipped."; return }
    }

    for vol in $orphans {
        _print $"  Deleting orphaned volume ($vol.name)..."
        if ($vol.protection?.delete? | default false) {
            do { ^hcloud volume disable-protection $vol.name delete } | complete | ignore
        }
        let res = (do { ^hcloud volume delete $vol.name } | complete)
        if $res.exit_code == 0 { _print $"  ✓ ($vol.name) deleted" } else { _print $"  ⚠ Failed: ($res.stderr)" }
    }
}

# Delete one server or all servers in an infra from Hetzner Cloud.
#
# Single server:
#   provisioning server delete <hostname>
#   provisioning server delete <hostname> --yes
#
# All servers in infra (only those that exist in Hetzner):
#   provisioning server delete
#   provisioning server delete --yes
#
# Volume and FIP handling (interactive prompt unless flag given):
#   --del-volume   Delete attached volumes. Default: detach only, data preserved.
#   --del-fip       Delete the floating IP.  Default: unassign only, FIP returns to pool.
#
# Examples:
#   prvng server delete libre-daoshi-0
#   prvng server delete libre-daoshi-0 --yes --del-volume --del-fip
#   prvng server delete --yes                         # delete all, keep volumes + FIPs
#   prvng server delete --yes --del-volume        # delete all + volumes
export def "main delete" [
    name?: string             # Hostname to delete. Omit to delete all servers in infra.
    --infra (-i): string = "" # Infra name (auto-detected from PWD if omitted)
    --all (-a)                # Explicit flag to confirm all-server delete (optional, same as no name)
    --yes (-y)                # Skip all confirmation prompts
    --del-volume          # Delete attached block volumes (default: preserve, detach only)
    --del-fip              # Delete assigned floating IPs (default: unassign only, back to pool)
    --debug (-x)
    --out: string = ""
]: nothing -> nothing {
    if ($out | is-not-empty) {
        set-provisioning-out $out
        set-provisioning-no-terminal true
    }

    let server_name = ($name | default "")

    # --all: intersect declared servers with what actually exists in Hetzner
    if $all and ($server_name | is-empty) {
        let settings = (find_get_settings --infra $infra)
        let declared  = ($settings.data.servers | each {|s| $s.hostname})
        if ($declared | is-empty) {
            error make { msg: "No servers declared in infra" }
        }
        # Query live Hetzner state — only delete what actually exists
        let live_res = (do { ^hcloud server list -o json } | complete)
        let live_names = if $live_res.exit_code == 0 and ($live_res.stdout | str trim | is-not-empty) {
            $live_res.stdout | from json | each {|s| $s.name}
        } else { [] }
        let hostnames = ($declared | where {|h| $live_names | any {|l| $l == $h}})
        let missing   = ($declared | where {|h| not ($live_names | any {|l| $l == $h})})
        for h in $missing { _print $"ℹ️  ($h) not found in Hetzner — skipping" }
        if ($hostnames | is-empty) {
            _print "Nothing to delete — no declared servers exist in Hetzner."
            # Still clean up orphaned infra volumes if --del-volume
            if $del_volume {
                delete_orphaned_infra_volumes $settings $yes
            }
            return
        }
        _print $"Will delete ($hostnames | length) server\(s\): ($hostnames | str join ', ')"
        if not $yes {
            _print "Type 'yes' to confirm deletion of ALL servers: "
            let confirm = (input "")
            if $confirm != "yes" { _print "Aborted."; return }
        }
        for hostname in $hostnames {
            if $del_volume and $del_fip {
                main delete $hostname --infra $infra --yes --del-volume --del-fip
            } else if $del_volume {
                main delete $hostname --infra $infra --yes --del-volume
            } else if $del_fip {
                main delete $hostname --infra $infra --yes --del-fip
            } else {
                main delete $hostname --infra $infra --yes
            }
        }
        # Clean up any remaining orphaned volumes declared in infra
        if $del_volume {
            delete_orphaned_infra_volumes $settings $yes
        }
        return
    }

    if ($server_name | is-empty) {
        error make { msg: "Usage: provisioning server delete <hostname> [--infra <infra>] [--yes]\n       provisioning server delete --all --infra <infra> [--yes]" }
    }

    let infra_name = if ($infra | is-not-empty) { $infra | path basename } else { "" }
    let ws_root = ($env.PROVISIONING_WORKSPACE_PATH? | default "")

    # Fetch server info — skip gracefully if not found
    let describe_res = (do { ^hcloud server describe $server_name -o json } | complete)
    if $describe_res.exit_code != 0 {
        _print $"ℹ️  Server '($server_name)' not found in Hetzner — nothing to delete"
        return
    }
    let srv    = ($describe_res.stdout | from json)
    let srv_id = ($srv.id | into string)
    let prot   = ($srv | get -o protection | default {})
    let locked = ($prot.delete? | default false)

    # Collect attached resources
    let attached_vols = (
        do { ^hcloud volume list -o json } | complete
        | if $in.exit_code == 0 { $in.stdout | from json | where {|v| ($v.server?.id? | default 0 | into string) == $srv_id} }
        else { [] }
    )
    let assigned_fips = (
        do { ^hcloud floating-ip list -o json } | complete
        | if $in.exit_code == 0 { $in.stdout | from json | where {|f| ($f.server?.id? | default 0 | into string) == $srv_id} }
        else { [] }
    )

    # Summary before confirmation
    _print $"\nServer: ($server_name) \(id: ($srv_id), status: ($srv.status), protection: delete=($locked)\)"
    if ($attached_vols | is-not-empty) {
        _print $"  Volumes : ($attached_vols | each {|v| $v.name} | str join ', ')"
    }
    if ($assigned_fips | is-not-empty) {
        let fip_list = ($assigned_fips | each {|f| $"($f.name) ($f.ip)"} | str join ', ')
        _print $"  FIPs    : ($fip_list)"
    }

    # Determine volume/FIP action interactively when not forced
    mut do_delete_vols = $del_volume
    mut do_del_fip  = $del_fip

    if not $yes {
        _print ""
        if ($attached_vols | is-not-empty) and not $del_volume {
            _print $"Delete ($attached_vols | length) volume\(s\)? Data will be lost. [y/N] "
            let ans = (input "")
            $do_delete_vols = ($ans in ["y", "Y", "yes"])
        }
        if ($assigned_fips | is-not-empty) and not $del_fip {
            _print $"Delete ($assigned_fips | length) FIP\(s\)? \(N = unassign only, keeps FIP in pool\) [y/N] "
            let ans = (input "")
            $do_del_fip = ($ans in ["y", "Y", "yes"])
        }
        _print $"\nType '($server_name)' to confirm permanent deletion: "
        let confirm = (input "")
        if $confirm != $server_name { _print "Aborted."; return }
    }

    # Step 1: Disable protection
    if $locked {
        _print $"  Disabling protection on ($server_name)..."
        let res = (do { ^hcloud server disable-protection $server_name delete rebuild } | complete)
        if $res.exit_code != 0 { error make { msg: $"Failed to disable protection: ($res.stderr)" } }
        _print "  ✓ protection disabled"
    }

    # Step 2: Handle FIPs before server deletion
    for fip in $assigned_fips {
        if $do_del_fip {
            _print $"  Deleting FIP ($fip.name)..."
            # Disable FIP protection if set
            if ($fip.protection?.delete? | default false) {
                do { ^hcloud floating-ip disable-protection $fip.name delete } | complete | ignore
            }
            let res = (do { ^hcloud floating-ip delete $fip.name } | complete)
            if $res.exit_code == 0 { _print $"  ✓ FIP ($fip.name) deleted" }
            else { _print $"  ⚠ Failed to delete FIP ($fip.name): ($res.stderr)" }
        } else {
            _print $"  Unassigning FIP ($fip.name)..."
            let res = (do { ^hcloud floating-ip unassign $fip.name } | complete)
            if $res.exit_code == 0 { _print $"  ✓ FIP ($fip.name) unassigned → back to pool" }
            else { _print $"  ⚠ Failed to unassign FIP ($fip.name): ($res.stderr)" }
        }
    }

    # Step 3: Delete server
    _print $"  Deleting ($server_name)..."
    let del_res = (do { ^hcloud server delete $server_name } | complete)
    if $del_res.exit_code != 0 { error make { msg: $"Failed to delete server: ($del_res.stderr)" } }
    _print $"  ✓ ($server_name) deleted"

    # Step 4: Handle volumes after server deletion (auto-detached on server delete)
    for vol in $attached_vols {
        if $do_delete_vols {
            _print $"  Deleting volume ($vol.name)..."
            if ($vol.protection?.delete? | default false) {
                do { ^hcloud volume disable-protection $vol.name delete } | complete | ignore
            }
            let res = (do { ^hcloud volume delete $vol.name } | complete)
            if $res.exit_code == 0 { _print $"  ✓ volume ($vol.name) deleted" }
            else { _print $"  ⚠ Failed to delete volume ($vol.name): ($res.stderr)" }
        } else {
            _print $"  Volume ($vol.name) preserved (detached)"
        }
    }

    # Step 3: Sync state — resolve ws_root from user_config.yaml if env var not propagated
    mut sync_ws = $ws_root
    if ($sync_ws | is-empty) {
        let user_config_path = ($env.HOME | path join "Library" "Application Support" "provisioning" "user_config.yaml")
        if ($user_config_path | path exists) {
            let config = (open $user_config_path)
            let active_name = ($config | get -o active_workspace | default "")
            let ws = ($config | get -o workspaces | default [] | where { $in.name == $active_name } | first | default null)
            if $ws != null { $sync_ws = $ws.path }
        }
    }
    let sync_infra = if ($infra_name | is-not-empty) { $infra_name } else {
        let user_config_path = ($env.HOME | path join "Library" "Application Support" "provisioning" "user_config.yaml")
        if ($user_config_path | path exists) {
            let config = (open $user_config_path)
            let active_name = ($config | get -o active_workspace | default "")
            $config | get -o workspaces | default [] | where { $in.name == $active_name } | first | default {} | get -o default_infra | default ""
        } else { "" }
    }
    if ($sync_ws | is-not-empty) and ($sync_infra | is-not-empty) {
        _print "\n[state sync]"
        sync-servers-state-post-op $sync_ws $sync_infra
    }
}
