#!/usr/bin/env nu
# Info: UpCloud
# servers.nu

use std
use api.nu *
use ../../../../../core/nulib/lib_provisioning/config/accessor.nu *

export def upcloud_interface [
]: nothing -> string {
  ($env | get -o UPCLOUD_INTERFACE | default "CLI") # API or CLI
}
export def upcloud_use_api [
]: nothing -> bool {
  (upcloud_interface) == "API"
}
export def upcloud_query_servers [
   find: string
   cols: string
]: nothing -> list {
   if upcloud_use_api {
      upcloud_api_list_servers
   } else {
      let res = (^upctl server list -o json err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
      if $res.exit_code == 0 {
         $res.stdout  | from json | get servers
      } else {
         if (is-debug-enabled) {
            (throw-error "🛑 upctl server list " $"($res.exit_code) ($res.stdout)" "upcloud query server" --span (metadata $res).span)
         } else {
            print $"🛑 Error upctl server list: ($res.exit_code) ($res.stdout | ^grep 'error')"
         }
      }
   }
}
export def upcloud_server_info [
   server: record
   check: bool
]: nothing -> record {
   let hostname = $server.hostname
   if (upcloud_use_api) {
      upcloud_api_server_info $hostname
   } else {
      let res = (^upctl server show $hostname -o json err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
      if $res.exit_code == 0 {
         $res.stdout  | from json
      } else if $check {
         {}
      } else {
         if (is-debug-enabled) {
            (throw-error "🛑 upctl server show" $"($res.exit_code) ($res.stdout)" $"upcloud server info ($hostname)" --span (metadata $res).span)
         } else {
            print $"🛑 upctl server show ($hostname):($res.stdout | ^grep 'error')"
         }
      }
   }
}
export def upcloud_on_prov_server [
   server?: record
] {
   #let info = if ( $env.CURRENT_FILE? | into string ) != "" { (^grep "^# Info:" $env.CURRENT_FILE ) | str replace "# Info: " "" } else { "" }
   #$"From (_ansi purple_bold)UpCloud(_ansi reset)"
}
# infrastructure and services
export def upcloud [
  args: list<string>     # Args for create command
  --server(-s): record
  --serverpos (-p): int         # Server position in settings
  --check (-c)            # Only check mode no servers will be created
  --wait (-w)             # Wait servers to be created
  --infra (-i): string    # Infra path
  --settings (-s): string # Settings path
  --outfile (-o): string  # Output file
  --debug (-x)            # Use Debug mode
]: nothing -> any {
   if $debug { set-debug-enabled true }
   let target = ($args | get -o 0 | default "")
   let task = ($args | get -o 1 | default "")
   let cmd_args = if ($args | length) > 1 { ($args | drop nth ..1) } else { [] }
   match ($task) {
      "help" | "h" => {
         print "TODO upcloud help"
         if not (is-debug-enabled) { end_run "" }
         exit
      },
      _ => {
         if ($args | find "help" | length) > 0 {
            match $task {
               "server"  => {
                  print "SERVER "
                  upcloud_server ($args | drop nth ..0)
               },
               "inventory" => {
                  upcloud_server ($args | drop nth ..0)
               },
               "ssh"  => {
                  upcloud_server ($args | drop nth ..0)
               },
               "delete"  => {
                  upcloud_server ($args | drop nth ..0)
                  # ($args | drop nth ..1) --server $server
               },
               _ => {
                  option_undefined "upcloud" ""
                  print "TODO upcloud help"
               }
            }
            if not (is-debug-enabled) { end_run "" }
            exit
         }
      }
   }
   #use utils/settings.nu [ load_settings ]
   let curr_settings = if $infra != null {
      if $settings != null {
         (load_settings --infra $infra --settings $settings)
      } else {
         (load_settings --infra $infra)
      }
   } else {
      if $settings != null {
         (load_settings --settings $settings)
      } else {
         (load_settings)
      }
   }
   match ($task) {
      "get_ip" => {
         upcloud_get_ip $curr_settings $server ($cmd_args | get -o 0 | default "")
      },
      "server"  => {
         print (
            upcloud_server $cmd_args --server $server --settings $curr_settings --error_exit
         )
      },
      "inventory"  => {
      },
      "ssh"  => {
      },
      "delete"  => {
         # ($args | drop nth ..1) --server $server
      },
      _ => {
         option_undefined "upcloud" ""
         if not (is-debug-enabled) { end_run "" }
         exit
      }
   }
}
export def upcloud_get_ip [
   settings: record
   server: record
   ip_type?: string = "public"
   family?: string = "IPv4"
]: nothing -> string {
   match $ip_type {
      "private" | "prv" | "priv" =>  {
         $"($server.network_private_ip)"
      },
      _ => {
         if (upcloud_use_api) {
            let server = (upcloud_api_server_info $server.hostname)
            if ($server | is-empty) { return "" }
            let uuid = ($server | get -o uuid | default "")
            if ($uuid | is-empty) { return "" }
            (upcloud_api_server_uuid_ip $uuid $ip_type $family)
          } else {
            let result = (^upctl "server" "show" $server.hostname "-o" "json" | complete)
            if $result.exit_code == 0 {
               let data = ($result.stdout | from json)
               #let id =  ($data.id? | default "")
               let ip_addresses = ($data.networking?.interfaces? | where {|it| ($it.type | str contains "public") }).ip_addresses?
               $"(($ip_addresses |  get -o 0).address? | get -o 0 | default '')"
            } else { "" }
         }
      }
   }
}
# To create infrastructure and services
export def upcloud_server [
   args: list<string>     # Args for create command
  --server: record
  --error_exit
  --status
  --serverpos (-p): int         # Server position in settings
  --check (-c)            # Only check mode no servers will be created
  --wait (-w)             # Wait servers to be created
  --infra (-i): string    # Infra path
  --settings (-s): record # Settings path
  --outfile (-o): string  # Output file
  --debug (-x)            # Use Debug mode
]: nothing -> nothing {
   let task = ($args | get -o 0)
   let target = if ($args | length) > 1 { ($args | get -o 1)  } else  { "" }
   let cmd_args = if ($args | length) > 1 { ($args | drop nth ..1) } else { [] }
   match ($task) {
      "help" | "h" | "" => {
         print "TODO upcloud server help"
         if not (is-debug-enabled) { end_run "" }
         exit
      },
      _ => {
         if $target == "" or ($args | find "help" | length) > 0  {
            match $task {
               "server"  => {
                  upcloud_server $cmd_args
               },
               "status"  => {
                  print $server
                  print $error_exit
               }
               "inventory"  => {
                  print "TODO upcloud server inventory help"
               },
               "ssh"  => {
                  print "TODO upcloud server ssh help"
               },
               "delete"  => {
                  # ($args | drop nth ..1) --server $server
                  #upcloud_delete_server $cmd_args true
               },
               _ => {
                  option_undefined "upcloud" "server"
                  print "TODO upcloud server help"
               }
            }
            if not (is-debug-enabled) { end_run "" }
            exit
         }
      }
   }
   let server_target = if $server != null {
      $server
   } else if $settings != null {
      ($settings.data.servers | where {|it| $it.hostname == $target } | get -o 0)
   } else {
      null
   }
   if $server_target == null {
      if $error_exit {
         let text = $"($args | str join ' ')"
         (throw-error "🛑 upcloud server" $text "" --span (metadata $server_target).span)
      }
      return ""
   }
   if $status or $task == "status" {
      print "upcloud server status "
      return true
   }
   match $task {
      "get_ip" => {
         upcloud_get_ip $settings $server_target ($cmd_args | get -o 0 | default "")
      },
      "stop" => {
         print "TODO upcloud server stop"
      },
      "start" => {
         print "TODO upcloud server start"
      },
      "restart" => {
         print "TODO upcloud server restart"
      },
      _ => {
         option_undefined "upcloud" "server"
         if not (is-debug-enabled) { end_run "" }
         exit
      }
   }
}
export def upcloud_create_private_network [
  settings: record
  server: record
  check: bool
] {
   if $server == null {
      print $"❗ No server found in settings "
      return ""
   }
   # new_upctl network list  -o json |
   # let net_id =  ($data.networks | get -o 0 ).uuid)
    let zone = ( $server.zone? | default "")
    if $zone == "" {
      print $"($server.hostname) No zone found to CREATE network_privat_id"
      return ""
    }
   let network_private_name = ($server.network_private_name? | default "")
    if $network_private_name == "" {
      print $"($server.hostname) No network_private_name found to CREATE network_privat_id"
      return ""
   }
   let priv_cidr_block = ($server.priv_cidr_block | default "")
    if $network_private_name == "" {
      print $"($server.hostname) No priv_cidr_block found to CREATE network_privat_id"
      return ""
   }

   let private_net_id = if (upcloud_use_api) {
      # TODO  make it via API
      ""
   } else {
      # EXAMPLE_BASH private_net_id=$(upctl network list -o yaml | $YQ '.networks[] | select(.ip_networks.ip_network[].address == "'"$priv_cidr_block"'") | .uuid' 2>/dev/null | sed 's,",,g')
      let result = (^upctl network list -o json | complete)
      if $result.exit_code == 0 {
         let data = ($result.stdout | from json | get -o networks | find $priv_cidr_block | get -o 0 | get -o uuid | default "" | str trim)
      }  else {
         ""
      }
   }
   if $check and ($private_net_id | is-empty) {
      print $"❗private_network will be register in a real creation request not in check state"
      return  ""
   } else {
      let result = (^upctl network create --name ($network_private_name) --zone $zone --ip-network $"address=($priv_cidr_block),dhcp=true" -o json | complete)
      let new_net_id = if $result.exit_code == 0 {
         ($result.stdout | from json | find $priv_cidr_block | get -o uuid | default "")
      }  else { "" }
      if ($new_net_id | is-empty) {
         (throw-error $"🛑 no private network '($network_private_name)' created"
            $"for server ($server.hostname) ip ($server.network_private_ip)\n($result.stdout)"
               $"upcloud_create_private_network" --span (metadata $new_net_id).span)
         exit
      }
      # Save changes ...
      #use utils/settings.nu [ save_servers_settings save_settings_file ]
      let match_text = "    network_private_id = "
      let default_provider_path = ($settings.data | get -o servers_paths | get -o 0 | default "" | path dirname | path join $"($server.provider)_defaults.k")
      let old_text = 'network_private_id = "CREATE"'
      let new_text = $'network_private_id = "($new_net_id)"'
      save_settings_file $settings $default_provider_path $old_text $new_text
      return $new_net_id
   }
   return ""
}
export def upcloud_check_server_requirements [
  settings: record
  server: record
  check: bool
] {
   if $server.provider == "upcloud" {
      if (^upctl account show "-o" "json" err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete).exit_code != 0 and $check {
         (throw-error $"🛑 no account found"
            $"for server ($server.hostname)"
            "upcloud_check_server_requirements" --span (metadata $server.provider).span)
         exit
      }
      let private_net_id = if ($server.network_private_id? | default "") == "CREATE" {
         print $"❗($server.network_private_id?) found for (_ansi yellow)network_private_id(_ansi reset) will be created for ($server.priv_cidr_block | default '')"
         (upcloud_create_private_network $settings $server $check)
      } else {
         ($server.network_private_id? | default "" )
      }
      if ($private_net_id | is-empty) and $check {
         return true
      }
      let result = (^upctl network show $private_net_id -o json | complete)
      let privavet_net_id = if (not $check) and $result.exit_code != 0 {
         let net_id = (upcloud_create_private_network $settings $server $check)
         let res = (^upctl "network" "show" $private_net_id "-o" "json" | complete)
         if $res.exit_code != 0 {
            print  $"❗Error: no ($private_net_id) found "
            " "
         } else {
            let data = ($result.stdout | from json )
            ($data.networks | get -o 0 | get -o uuid)
         }
      } else if $result.exit_code == 0 {
         let data = ($result.stdout | from json)
         ($data.uuid)
      } else {
         ""
      }
      let server_private_ip = ($server.network_private_ip? | default "")
      if $private_net_id == "" and  $server_private_ip != "" {
         (throw-error $"🛑 no private network ($private_net_id) found"
            $"for server ($server.hostname) ip ($server_private_ip)"
            "upcloud_check_requirements" --span (metadata $server_private_ip).span)
         exit
      }
   }
   true
}
export def upcloud_make_settings [
  settings: record
  server: record
] {
   let out_settings_path = $"($settings.infra_fullpath)/($server.provider)_settings.yaml"
   let data = if ($out_settings_path | path exists ) {
      (open $out_settings_path | from yaml)
   } else {
      null
   }
   let task = if $data != null { "update" } else { "create" }

   let uuid  = (^upctl server show $server.hostname "-o" "json" | from json).uuid? | default ""
   if $uuid == "" {
      return false
   }
   let ip_pub = (upcloud_get_ip $settings  $server "public")
   let ip_priv = (upcloud_get_ip $settings $server "private")

   let server_settings = {
      name: $server.hostname,
      id: $uuid,
      private_net: {
         id: $server.network_private_id
         name: $server.network_private_name
      },
      zone: $server.zone,
      datetime: $env.NOW,
      ip_addresses:  {
         pub: $ip_pub, priv: $ip_priv
      }
   }
   let new_data = if $data != null and $data.servers? != null {
      ( $data.servers | each { |srv|
         where {|it| $it.name != $server.hostname }
      }) | append $server_settings
   }  else {
      {
         servers: [ $server_settings ]
      }
   }
   $new_data | to yaml | save --force $out_settings_path
   print $"✅ upcloud settings ($task) -> ($out_settings_path)"
   true
}
export def upcloud_delete_settings [
   settings: record
   server: record
] {
}
export def upcloud_post_create_server [
   settings: record
   server: record
   check: bool
] {
   mut req_storage = ""
   for storage in ($server | get -o storages | enumerate) {
      let res = (upcloud_storage_fix_size $settings $server $storage.index)
      if ($req_storage | is-empty) and ($res | is-not-empty) {
         $req_storage = $res
      }
   }
   $req_storage
}
export def upcloud_modify_server [
   settings: record
   server:   record
   new_values: list
   error_exit: bool
] {
   mut args = ""
   for item in $new_values {
      if ($item | get -o plan | is-not-empty) { $args = $args + $" --plan ($item.plan)" }
   }
   if ($args | is-empty)  { return }
   print $"Stop (_ansi blue_bold)($server.hostname)(_ansi reset) to modify (_ansi yellow_bold)($args)(_ansi reset)"
   if (upcloud_change_server_state $settings $server "stop" "") == false {
      print $"❗ Stop ($server.hostname) errors "
      if $error_exit {
         exit 1
      } else {
         return "error"
      }
   }
   let res = (^upctl ...($"server modify ($server.hostname) ($args | str trim)" | split row " ") err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
   if $res.exit_code != 0 {
      print $"❗ Server ($server.hostname) modify ($args) errors ($res.stdout ) "
   }
   print $"Start (_ansi blue_bold)($server.hostname)(_ansi reset) with modifications (_ansi green_bold)($args)(_ansi reset) ... "
   if (upcloud_change_server_state $settings $server "start" "") == false {
      print $"❗ Errors to start ($server.hostname)"
      if $error_exit {
         exit 1
      } else {
         return "error"
      }
   }
}
export def upcloud_wait_storage [
   settings: record
   server: record
   new_state: string
   id: string
] {
   print $"Checking storage ($id) state for (_ansi blue_bold)($server.hostname)(_ansi reset) state (_ansi yellow_bold)($new_state)(_ansi reset) ..."
   let state = (^upctl storage show $id -o json e> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | from json | get -o state)
   if ($state | str contains $new_state) { return true }
   let val_timeout = if $server.running_timeout? != null { $server.running_timeout } else { 60 }
   let wait = if $server.running_wait? != null { $server.running_wait } else { 10 }
   let wait_duration = ($"($wait)sec"| into duration)
   mut num = 0
   while true {
      let status = (^upctl storage show $id -o json e> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | from json | get -o state)
      if ($status | str contains $new_state) {
         return true
      } else if $val_timeout > 0 and $num > $val_timeout {
         print ($"\n🛑 (_ansi red)Timeout(_ansi reset) ($val_timeout) volume ($id) state for (_ansi blue)($server.hostname)(_ansi reset) " +
            $"(_ansi blue_bold)($new_state)(_ansi reset)  (_ansi red_bold)failed(_ansi reset) "
         )
         return false
      } else {
         $num = $num + $wait
         if (is-debug-enabled) {
           print ($"(_ansi blue_bold) 🌥 (_ansi reset) storage state for (_ansi yellow)($id)(_ansi reset) " +
               $"for (_ansi green)($server.hostname)(_ansi reset)-> ($status | str trim) "
            )
         } else {
           print -n $"(_ansi blue_bold) 🌥 (_ansi reset)"
         }
         sleep $wait_duration
      }
   }
   false
}
export def upcloud_create_storage [
   settings: record
   server:   record
   server_info: record
   storage: record
   volumes: list
   total_size: int
] {
   if $total_size <= 0 {
      print $"❗Create storage for ($server.hostname) size (_ansi red)($total_size) error(_ansi reset)"
      return {}
   }
   let av_zone = ($storage.item | get -o zone | default ($server | get -o zone))
   if ($av_zone | is-empty) {
      print ($"❗Create storage for (_ansi green_bold)($server.hostname)(_ansi reset) " +
         $"(_ansi cyan_bold)($total_size)(_ansi reset) (_ansi red)Zone  error(_ansi reset)"
      )
      return {}
   }
   let vol_device = ($storage.item | get -o voldevice)
   let op_vol_device = if ($vol_device | is-not-empty) {
      $"--address ($vol_device)"
   } else {
      ""
   }
   let op_encrypted = if ($storage.item | get -o encrypted | default false) {
      "--encrypted"
   } else {
      ""
   }
   let $op_backup = if ($storage.item | get -o backup | is-not-empty)  {
      ( $" --backup-time ($storage.item | get -o backup | get -o time) " +
        $" --backup-interval ($storage.item | get -o backup | get -o interval) " +
        $" --backup-retention ($storage.item | get -o backup | get -o retention)"
      )
   } else {
      ""
   }
   print ($"Create storage for ($server.hostname) (_ansi  cyan_bold)($total_size)(_ansi reset) in " +
     $"(_ansi blue_bold)($av_zone)(_ansi reset) with name (_ansi yellow)($storage.item | get -o name)_($server | get -o hostname)(_ansi reset) ... "
   )
   let res_create = (^upctl storage create --title $"($storage.item | get -o name)_($server.hostname)" --size ($total_size)
      --tier ($storage.item | get -o voltype) --zone $av_zone $op_encrypted $op_backup -o json | complete)
   if $res_create.exit_code != 0 {
      print ($"❗ Create storage for ($server.hostname) (_ansi  cyan_bold)($total_size)(_ansi reset) in " +
         $"(_ansi blue_bold)($av_zone)(_ansi reset) with ($storage.item | get -o name) (_ansi red)error(_ansi reset) ($res_create.stdout)"
      )
      return {}
   }
   let server_id = ($server_info | get -o uuid | default "")
   let vol = ($res_create.stdout | from json)
   let vol_id = ($vol | get -o uuid)
   let new_state = "online"
   if not (upcloud_wait_storage $settings $server $new_state $vol_id) {
      print ($"❗ Wait ($vol_id) storage for ($server.hostname) (_ansi  cyan_bold)($total_size)(_ansi reset) ($storage.item | get -o name) " +
         $"in (_ansi blue_bold)($av_zone)(_ansi reset) errors not in (_ansi red)($new_state)(_ansi reset) state"
      )
      ^upctl storage delete $vol_id
      print $"❗ Attach ($vol_id) deleted"
      return {}
   }
   let vol_device = ($storage.item | get -o voldevice)
   if ($server_id | is-empty) { return $vol }
   print ($"Attach storage for ($server.hostname) (_ansi  cyan_bold)($total_size)(_ansi reset) in " +
     $"(_ansi blue_bold)($av_zone)(_ansi reset) with name (_ansi yellow)($storage.item | get -o name)_($server | get -o hostname)(_ansi reset) ... "
   )
   let res_attach = if ($vol_device | is-not-empty) {
      (^upctl server storage attach $server_id --storage $vol_id --address $vol_device -o "json" | complete)
   } else {
      (^upctl server storage attach $server_id --storage $vol_id -o "json" | complete)
   }
   if $res_attach.exit_code != 0 {
      print $res_attach.exit_code
      print ($"❗Attach ($vol_id) storage for (_ansi green_bold)($server.hostname)(_ansi reset) (_ansi cyan_bold)($total_size)(_ansi reset) " +
         $"($storage.item | get -o name) ($vol_device) in (_ansi blue_bold)($av_zone)(_ansi reset) (_ansi red)errors(_ansi reset) " +
         $"\n($res_attach.stdout)"
      )
      ^upctl storage delete $vol_id
      print $"❗Attach (_ansi red_bold)($vol_id)(_ansi reset) deleted"
      return {}
   }
   let res_vol = (^upctl storage show $vol_id -o json | complete)
   if $res_vol.exit_code == 0 {
      let info_vol = ($res_vol.stdout | from json)
      print $info_vol
      if ($info_vol | get -o servers | get -o server | where {|srv| $srv == $server_id } | length) > 0 {
         print ($"✅ Atached (_ansi yellow)($vol_id)(_ansi reset) storage for ($server.hostname) (_ansi cyan_bold)($total_size)(_ansi reset) " +
            $"($storage.item | get -o name)(if $vol_device != "" { $' ($vol_device)'}) in (_ansi blue_bold)(_ansi blue_bold)($av_zone)(_ansi reset)(_ansi reset)"
         )
      } else {
         print ($"❗ Volume ($vol_id) storage for ($server.hostname) (_ansi  cyan_bold)($total_size)(_ansi reset) " +
            $"device ($vol_device) in (_ansi blue_bold)(_ansi blue_bold)($av_zone)(_ansi reset)(_ansi reset) (_ansi red)error(_ansi reset) not ($server_id)"
         )
      }
      $info_vol
   } else {
      print ($"❗ Volume ($vol_id) storage for ($server.hostname)  (_ansi  cyan_bold)($total_size)(_ansi reset) " +
         $"device ($vol_device) in (_ansi blue_bold)(_ansi blue_bold)($av_zone)(_ansi reset)(_ansi reset) (_ansi red)errors(_ansi reset) ($res_vol.stdout)"
      )
      {}
   }
}

export def upcloud_storage_fix_size [
   settings: record
   server:   record
   storage_pos: int
] {
   let total_size = ($server | get -o storages | get -o $storage_pos | get -o total | default 0)
   if $total_size == 0 { return 0 }
   let storage = (^upctl server show $server.hostname "-o" "json" | from json | get -o storage_devices | get -o $storage_pos)
   if $storage == null {
      let server_info = (^upctl server show $server.hostname "-o" "json" | from json)
      let volumes = ($server_info | get -o storage_devices | default [])
      let storage_data = { item: ($server | get -o storages | get -o $storage_pos), index: $storage_pos }
      upcloud_create_storage $settings $server $server_info $storage_data $volumes $total_size
   }
   let $curr_size = ($storage | get -o storage_size | default 0)
   if $curr_size == 0 { return 0 }
   #let storage_parts = ($server.storages? | get -o $storage_pos | get -o parts | default [])
   #if ($storage_parts | length) == 0 { return 0 }
   if $curr_size != $total_size {
      print (
         $"Stop (_ansi blue_bold)($server.hostname)(_ansi reset) for storage (_ansi yellow_bold)($storage.storage)(_ansi reset)" +
         $" from (_ansi purple_bold)($curr_size)(_ansi reset) to (_ansi green_bold)($total_size)(_ansi reset) ... "
      )
      if (upcloud_change_server_state $settings $server "stop" "") == false {
         print $"❗ Stop ($server.hostname) errors "
         return "error"
      }
      if $storage_pos == 0 {
         let res = (^upctl storage modify --size $total_size $storage.storage err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
         if $res.exit_code != 0 {
            print $"❗ Storage modify errors ($res.stdout ) "
            return "error"
         }
         let new_storage = (^upctl server show $server.hostname "-o" "json" | from json | get -o storage_devices | get -o $storage_pos)
         let new_curr_size = $new_storage.storage_size? | default 0
         print $"Start (_ansi blue_bold)($server.hostname)(_ansi reset) with new size (_ansi green_bold)($new_curr_size)(_ansi reset) ... "
      } else {
         let storage_settings = ($server | get -o storages | get -o $storage_pos)
         let new_storage = (^upctl storage $server.hostname "-o" "json" | from json | get -o storage_devices | get -o $storage_pos)
         let $op_backup = if ($storage_settings | get -o backup | is-not-empty)  {
            ( $" --backup-time ($storage_settings  | get -o backup | get -o time) " +
              $" --backup-interval ($storage_settings | get -o backup | get -o interval) " +
              $" --backup-retention ($storage_settings | get -o backup | get -o retention)"
            )
         } else {
            ""
         }
         let op_encrypted = if ($storage_settings | get -o encrypted | default false) { "--encrypted" } else { "" }
         let res_modify = (^upctl storage modify ($new_storage | get -o uuid) --size $total_size $op_encrypted $op_backup| complete)
         if $res_modify.exit_code != 0 {
            print ($"❗ Modify storage for ($server.hostname) (_ansi  cyan_bold)($total_size)(_ansi reset) in " +
               $"(_ansi blue_bold)($storage.zone)(_ansi reset) with ($storage.item | get -o name) (_ansi red)error(_ansi reset) ($res_modify.stdout)"
            )
            return {}
         }
      }
      if (upcloud_change_server_state $settings $server "start" "") == false {
         print $"❗ Errors to start ($server.hostname) "
         return "error"
      }
      #return "storage"
   }
   "storage"
}
export def upcloud_status_server [
   hostname: string
] {
   let res = (^upctl server show $hostname "-o" "json" err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
   if $res.exit_code != 0 {
      print $"❗ status ($hostname) errors "
      if $env.PROVISIONING_DEBUG { print $res.stdout }
      return ""
   }
   return ($res.stdout | from json | get -o state | default "")
}
export def upcloud_server_exists [
   server: record
   error_exit: bool
] {
   let res = (^upctl server show $server.hostname "-o" "json" err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
   if $res.exit_code != 0 {
      if $error_exit {
         print $"❗ server ($server.hostname) exists errors ($res.stdout ) "
         exit 1
      } else {
         return false
      }
   }
   true
}
export def upcloud_server_state [
   server: record
   new_state: string
   error_exit: bool
   wait: bool
   settings: record
] {
   let res = (^upctl server show $server.hostname "-o" "json" err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
   if $res.exit_code != 0 {
      if $error_exit {
         print $"❗ state ($server.hostname) errors ($res.stdout ) "
         exit 1
      } else {
         return false
      }
   }
   true
}
export def upcloud_server_is_running [
   server: record
   error_exit: bool
] {
   let res = (^upctl server show $server.hostname "-o" "json" err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
   if $res.exit_code != 0 {
      print $"❗ is running ($server.hostname) errors ($res.stdout ) "
      if $error_exit {
         exit 1
      } else {
         return false
      }
   }
   (($res.stdout | from json).state? | str contains "started" | default false)
}
export def upcloud_change_server_state [
   settings: record
   server: record
   new_state: string
   ops: string
] {
   let state = (upcloud_status_server $server.hostname)
   if $state == "" { return false }
   if ($state | str contains $new_state) { return true }
   print $"Checking (_ansi blue_bold)($server.hostname)(_ansi reset) state (_ansi yellow_bold)($new_state)(_ansi reset) ..."
   let val_timeout = if $server.running_timeout? != null { $server.running_timeout } else { 60 }
   let wait = if $server.running_wait? != null { $server.running_wait } else { 10 }
   let wait_duration = ($"($wait)sec"| into duration)
   let res = if ($ops | str contains "--type" ) {
     (^upctl server $new_state --type ($ops | str replace "--type " "") $server.hostname err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
   } else if $ops != "" {
     (^upctl server $new_state $ops $server.hostname err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" })| complete)
   } else {
     (^upctl server $new_state $server.hostname err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
   }
   if $res.exit_code != 0 {
      print $"❗Errors ($server.hostname) to ($new_state) ($res.stdout ) "
      return false
   }
   mut num = 0
   while true {
      let status = (upcloud_status_server $server.hostname)
      if ($status | str contains $new_state) {
         print " "
         return true
      } else if $val_timeout > 0 and $num > $val_timeout {
         print $"\n🛑 (_ansi red)Timeout(_ansi reset) ($val_timeout) (_ansi blue)($server.hostname)(_ansi reset) (_ansi blue_bold)($new_state)(_ansi reset)  (_ansi red_bold)failed(_ansi reset) "
         return false
      } else {
         $num = $num + $wait
         if (is-debug-enabled) {
           print -n $"(_ansi blue_bold) 🌥 (_ansi reset)(_ansi green)($server.hostname)(_ansi reset)->($status) "
         } else {
           print -n $"(_ansi blue_bold) 🌥 (_ansi reset)"
         }
         sleep $wait_duration
      }
    }
    false
}
export def upcloud_delete_server_storage [
   settings: record
   server: record
   error_exit: bool
] {
   let res = (^upctl storage list --normal -o json | complete)
   if $res.exit_code == 0 {
      let data = ($res.stdout | from json)
      $data.storages? | default [] | each {|storage|
         if ($storage.title | str starts-with $server.hostname ) {
            if (upcloud_server_exists $server false) {
               print $"❗ (_ansi blue_bold)($server.hostname)(_ansi reset) (_ansi red_bold)exists(_ansi reset) can not delete storage (_ansi yellow)($storage.uuid)(_ansi reset)"
            } else {
               let del_res = (^upctl storage delete $storage.uuid err> (if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" }) | complete)
               if $del_res.exit_code != 0 {
                  print $"❗ Delete storage (_ansi yellow)($storage.uuid)(_ansi reset) for (_ansi blue_bold)($server.hostname)(_ansi reset) (_ansi red_bold)errors(_ansi reset) ($del_res.stdout ) "
               } else {
                  print $"(_ansi yellow)($storage.uuid)(_ansi reset) for (_ansi blue_bold)($server.hostname)(_ansi reset) (_ansi green_bold)deleted(_ansi reset) ($del_res.stdout ) "
               }
            }
         }
      }
   }
   true
}
export def upcloud_delete_server [
   settings: record
   server: record
   keep_storage: bool
   error_exit: bool
] {
   if not (upcloud_change_server_state $settings $server "stop" "--type hard") {
      if $env.PROVISIONING_DEBUG { print $"❗ Stop (_ansi blue_bold)($server.hostname)(_ansi reset) errors " }
      return false
   }
   let ops = if $keep_storage { "" } else { "--delete-storages" }
   let res =  (^upctl server delete $server.hostname $ops err> (std null-device) | complete)
   if $res.exit_code != 0 {
      print $"❗ Delete (_ansi blue_bold)($server.hostname)(_ansi reset) (_ansi red_bold)errors(_ansi reset) ($res.stdout ) "
      return false
   }
   print $"(_ansi blue_bold)($server.hostname)(_ansi reset) (_ansi green_bold)deleted(_ansi reset)"
   true
}
