#!/usr/bin/env nu
use std
use ../../../../../core/nulib/lib_provisioning/config/accessor.nu *

export def local_query_servers [
   find: string
   cols: string
] {
   # TODO FIX
   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 "🛑 local server list " $"($res.exit_code) ($res.stdout)" "local query server" --span (metadata $res).span)
      } else {
         print $"🛑 Error local server list: ($res.exit_code) ($res.stdout | ^grep 'error')"
      }
   }
}
export def local_server_info [
   server: record
   check: bool
] {
   let hostname = $server.hostname
   # TODO FIX
   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 "🛑 local server show" $"($res.exit_code) ($res.stdout)" $"local server info ($hostname)" --span (metadata $res).span)
      } else {
         print $"🛑 local server show ($hostname):($res.stdout | ^grep 'error')"
      }
   }
}
export def local_on_prov_server [
   server?: record
   infra?: string
] {
   let info = if ( $env.CURRENT_FILE? | into string ) != "" { (^grep "^# Info:" $env.CURRENT_FILE ) | str replace "# Info: " "" } else { "" }
   print $env.CURRENT_FILE
   $" From LOCAL ($info) "
}
# infrastructure and services
export def local [
  args: list<string>     # Args for create command
  --server(-s): record
  #hostname?: string       # Server hostname in settings
  --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
] {
   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 local help"
         if not (is-debug-enabled) { end_run "" }
         exit
      },
      _ => {
         if ($args | find "help" | length) > 0 {
            match $task {
               "server"  => {
                  print "SERVER "
                  local_server ($args | drop nth ..0)
                  #local_server ($args | drop nth ..1) --server $server
               },
               "inventory" => {
                  local_server ($args | drop nth ..0)
               },
               "ssh"  => {
                  local_server ($args | drop nth ..0)
               },
               "delete"  => {
                  local_server ($args | drop nth ..0)
                  # ($args | drop nth ..1) --server $server
               },
               _ => {
                  option_undefined "local" ""
                  print "TODO local 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" => {
         local_get_ip $curr_settings $server ($cmd_args | get -o 0 | default "")
      },
      "server"  => {
         print (
            local_server $cmd_args --server $server --settings $curr_settings --error_exit
         )
      },
      "inventory"  => {
      },
      "ssh"  => {
      },
      "delete"  => {
         # ($args | drop nth ..1) --server $server
      },
      _ => {
         option_undefined "local" ""
         if not (is-debug-enabled) { end_run "" }
         exit
      }
   }
}
export def local_get_ip [
   settings: record
   server: record
   ip_type: string
] {
   match $ip_type {
      "private" | "prv" | "priv" =>  {
         return $"($server.network_private_ip)"
      },
      _ => {
         let ip = ($server.network_public_ip | default "")
         # TODO FIX add NOT FOUND ERRORS
         return $ip
         #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?
         #   return $"(($ip_addresses |  get -o 0).address? | get -o 0 | default '')"
         #} else { "" }
      }
   }
}
# To create infrastructure and services
export def local_server [
   args: list<string>     # Args for create command
  --server(-s): record
  --error_exit
   --status
  #hostname?: string       # Server hostname in settings
  --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
] {
   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 local server help"
         if not (is-debug-enabled) { end_run "" }
         exit
      },
      _ => {
         if $target == "" or ($args | find "help" | length) > 0  {
            match $task {
               "server"  => {
                  local_server $cmd_args
               },
               "status"  => {
                  print $server
                  print $error_exit
               }
               "inventory"  => {
                  print "TODO local server inventory help"
               },
               "ssh"  => {
                  print "TODO local server ssh help"
               },
               "delete"  => {
                  # ($args | drop nth ..1) --server $server
                  #local_delete_server $cmd_args true
               },
               _ => {
                  option_undefined "local" "server"
                  print "TODO local 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 "🛑 local server" $text "" --span (metadata $server_target).span)
      }
      return ""
   }
   if $status or $task == "status" {
      print "local server status "
      return true
   }
   match $task {
      "get_ip" => {
         local_get_ip $settings $server_target ($cmd_args | get -o 0 | default "")
      },
      "stop" => {
         print "TODO local server stop"
      },
      "start" => {
         print "TODO local server start"
      },
      "restart" => {
         print "TODO local server restart"
      },
      _ => {
         option_undefined "local" "server"
         if not (is-debug-enabled) { end_run "" }
         exit
      }
   }
}
export def local_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 ""
    }
   # 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)
   let private_net_id = if $result.exit_code == 0 {
      let data = ($result.stdout | from json )
      ($data.networks? | find $priv_cidr_block | get -o 0 | get -o uuid | default "")
   }  else {
      ""
   }
   if $check and $private_net_id == "" {
      print $"❗private_network will be register in a real creation request not in check state"
      return  ""
   } else if $private_net_id == "" {
      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 0).uuid? | default "")
      }  else { "" }
      if $new_net_id  == "" {
         (throw-error $"🛑 no private network ($network_private_name) found"
            $"for server ($server.hostname) ip ($server.network_private_ip)"
               $"local_check_requirements" --span (metadata $new_net_id.span))
         return false
      }
      # Save changes ...
      #use utils/settings.nu [ save_servers_settings save_settings_file ]
      let match_text = "    network_private_id = "
      let defs_provider_path = $"($settings.data.server_path | get -o 0 | path dirname)/local_defaults"
      save_servers_setings $settings $match_text $new_net_id
      save_settings_file $settings $"($settings.src_path)/($settings.src)" $match_text $new_net_id
      save_settings_file $settings $"($defs_provider_path)" $match_text $new_net_id
   }
   return true
}
export def local_check_server_requirements [
  settings: record
  server: record
  check: bool
] {
   if $server.provider != "local" { return false }
   print ($"✅ (_ansi blue_bold)($server.hostname)(_ansi reset) with provider " +
      $"(_ansi green_bold)($server.provider)(_ansi reset) ($server.zone) does not require creation !" )
   true
}

export def local_make_settings [
  settings: record
  server: record
] {

#  #  _delete_settings
   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 ""
#   echo "settings:" > "$out_settings"

#   for server in $(_settings_hosts)
#   do
   if $uuid == "" {
      return false
   }
   let ip_pub = (local_get_ip $settings $server "public")
   let ip_priv = (local_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 {
      ## create data record
      {
         servers: [ $server_settings ]
      }
   }
   $new_data | to yaml | save --force $out_settings_path
   print $"✅ local settings ($task) -> ($out_settings_path)"
   true
}
export def local_delete_settings [
   settings: record
   server: record
] {
}
export def local_post_create_server [
   settings: record
   server: record
   check: bool
] {
   if $server != null {
      return (local_storage_fix_size $settings $server 0)
   }
   true
}
export def local_modify_server [
   settings: record
   server:   record
   new_values: list
   error_exit: bool
] {
   # TODO LOCAL
   return
   let res = (^upctl server $server.hostname  modify ...($new_values)  | complete)
   if $res.exit_code != 0 {
      print $"❗ Server ($server.hostname)  modify ($new_values | str join ' ') errors ($res.stdout ) "
      if $error_exit {
         exit 1
      } else {
         return "error"
      }
   }
}
export def local_storage_fix_size [
   settings: record
   server:   record
   storage_pos: int
] {
   # TODO LOCAL
   return
   let total_size = ($server | get -o storages | get $storage_pos | get -o total | default 0)
   if $total_size == 0 { return 0 }
   let storage = (^upctl server show $server.hostname "-o" "json" | from json).storage_devices | (get -o $storage_pos)
   if $storage == null { return 0 }
   let curr_size = $storage.storage_size? | default 0
   if $curr_size == 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 (local_change_server_state $settings $server "stop" "") == false {
         print $"❗ Stop ($server.hostname) errors "
         return "error"
      }
      let res = (^upctl storage modify --size $total_size $storage.storage  | 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).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) ... "
      if (local_change_server_state $settings $server "start" "") == false {
         print $"❗ Errors to start ($server.hostname): ($res.stdout ) "
         return "error"
      }
      return "storage"
   }
   ""
}
export def local_status_server [
   hostname: string
] {
   let res = (^upctl server show $hostname "-o" "json" | complete)
   if $res.exit_code != 0 {
      print $"❗ status ($hostname) errors ($res.stdout ) "
      return ""
   }
   return (($res.stdout | from json).state | default "")
}
export def local_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 $"❗ status ($server.hostname) errors ($res.stdout ) "
         exit 1
      } else {
         return false
      }
   }
   true
}
export def local_server_state [
   server: record
   new_state: string
   error_exit: bool
   wait: bool
   settings: record
] {
}
export def local_server_is_running [
   server: record
   error_exit: bool
] {
   true
   #TODO FIX
   # 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 $"❗ status ($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 local_change_server_state [
   settings: record
   server: record
   new_state: string
   ops: string
] {
   let state = (local_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)
   mut num = 0
   while true {
      let status = (local_status_server $server.hostname)
      if $status == ""  {
         return false
      } else if ($status | str contains "maintenance") == false {
         print " "
         break
      } 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
      }
   }
   let res = if ($ops | str contains "--type" ) {
     (^upctl server $new_state --type ($ops | str replace "--type " "") $server.hostname | complete)
   } else if $ops != "" {
     (^upctl server $new_state $ops $server.hostname | complete)
   } else {
     (^upctl server $new_state $server.hostname | complete)
   }
   if $res.exit_code != 0 {
      print $"❗Errors ($server.hostname) to ($new_state)  ($res.stdout ) "
      return false
   }
   $num = 0
   while true {
      let status = (local_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 local_delete_server_storage [
   settings: record
   server: record
   error_exit: bool
] {
  print ($"✅ (_ansi blue_bold)($server.hostname)(_ansi reset) with provider " +
    $"(_ansi green_bold)($server.provider)(_ansi reset) ($server.zone) does not require delete storage !" )
   true
}
export def local_delete_server [
   settings: record
   server: record
   keep_storage: bool
   error_exit: bool
] {
  print ($"✅ (_ansi blue_bold)($server.hostname)(_ansi reset) with provider " +
     $"(_ansi green_bold)($server.provider)(_ansi reset) ($server.zone) does not require delete !" )
  true
}
