use ../../../../providers/prov_lib/middleware.nu *
use ../context.nu *
use ../sops/mod.nu *

export def find_get_settings [
  --infra (-i): string     # Infra directory
  --settings (-s): string # Settings path
  include_notuse: bool = false
  no_error: bool = false
]: nothing -> record {
  #use utils/settings.nu [ load_settings ]
  if $infra != null {
    if $settings != null {
      (load_settings --infra $infra --settings $settings $include_notuse $no_error)
    } else {
      (load_settings --infra $infra $include_notuse $no_error)
    }
  } else {
    if $settings != null {
      (load_settings --settings $settings $include_notuse $no_error)
    } else {
      (load_settings $include_notuse $no_error)
    }
  }
}
export def check_env [
]: nothing -> bool {
  # TuDO
  true
}
export def get_context_infra_path [
]: nothing -> string {
  let context = (setup_user_context)
  if $context == null or $context.infra == null { return "" }
  if $context.infra_path? != null and ($context.infra_path | path join $context.infra | path exists) {
    return ($context.infra_path| path join $context.infra)
  }
  if ($env.PROVISIONING_INFRA_PATH | path join $context.infra | path exists) {
    return ($env.PROVISIONING_INFRA_PATH | path join $context.infra)
  }
  ""
}
export def get_infra [
  infra?: string
]: nothing -> string {
  if ($infra | is-not-empty) {
    if ($infra | path exists) {
      $infra
    } else if ($infra | path join $env.PROVISIONING_DFLT_SET | path exists) {
      $infra
    } else if ($env.PROVISIONING_INFRA_PATH | path join $infra | path join $env.PROVISIONING_DFLT_SET | path exists) {
      $env.PROVISIONING_INFRA_PATH | path join $infra
    } else {
      let text = $"($infra) on ($env.PROVISIONING_INFRA_PATH | path join $infra)"
      (throw-error "🛑 Path not found " $text "get_infra" --span (metadata $infra).span)
    }
  } else {
    if ($env.PWD | path join $env.PROVISIONING_DFLT_SET | path exists) {
      $env.PWD
    } else if ($env.PROVISIONING_INFRA_PATH | path join ($env.PWD | path basename) |
      path join $env.PROVISIONING_DFLT_SET | path exists) {
      $env.PROVISIONING_INFRA_PATH | path join ($env.PWD | path basename)
    } else {
      let context_path = get_context_infra_path
      if $context_path != "" { return $context_path }
      $env.PROVISIONING_KLOUD_PATH
    }
  }
}
export def parse_kcl_file [
  src: string
  target: string
  append: bool
  msg: string
  err_exit?: bool = false
]: nothing -> bool {
  # Try nu_plugin_kcl first if available
  let format = if $env.PROVISIONING_WK_FORMAT == "json" { "json" } else { "yaml" }
  let result = (process_kcl_file $src $format)
  if ($result | is-empty) {
    let text = $"kcl ($src) failed code ($result.exit_code)"
    (throw-error $msg $text "parse_kcl_file" --span (metadata $result).span)
    if $err_exit { exit $result.exit_code  }
    return false
  }
  if $append {
    $result | save --append $target
  } else {
    $result | save -f $target
  }
  true
}
export def load_from_wk_format [
  src: string
]: nothing -> record {
  if not ( $src | path exists) { return {} }
  let data_raw = (open -r $src)
  if $env.PROVISIONING_WK_FORMAT == "json" {
    $data_raw | from json | default {}
  } else {
    $data_raw | from yaml | default {}
  }
}
export def load_defaults [
  src_path: string
  item_path: string
  target_path: string
]: nothing -> string {
  if ($target_path | path exists) {
    if (is_sops_file $target_path) { decode_sops_file $src_path $target_path true }
    retrurn
  }
  let full_path = if ($item_path | path exists) {
    ($item_path)
  } else if ($"($item_path).k" | path exists) {
    $"($item_path).k"
  } else if ($src_path | path dirname | path join $"($item_path).k" | path exists) {
    $src_path | path dirname | path join $"($item_path).k"
  } else {
    ""
  }
  if $full_path == "" { return true }
  if (is_sops_file $full_path) {
    decode_sops_file $full_path $target_path true
    (parse_kcl_file $target_path $target_path false  $"🛑 load default settings failed ($target_path) ")
  } else {
    (parse_kcl_file $full_path $target_path false  $"🛑 load default settings failed ($full_path)")
  }
}
export def get_provider_env [
  settings: record
  server: record
]: nothing -> record {
  let prov_env_path = if ($server.prov_settings | path exists ) {
    $server.prov_settings
  } else {
    let file_path = ($settings.src_path | path join $server.prov_settings)
    if ($file_path | str ends-with '.k' ) { $file_path } else { $"($file_path).k" }
  }
  if not ($prov_env_path| path exists ) {
    if $env.PROVISIONING_DEBUG { _print  $"🛑 load (_ansi cyan_bold)provider_env(_ansi reset) from ($server.prov_settings) failed at ($prov_env_path)" }
    return {}
  }
  let str_created_taskservs_dirpath = ($settings.data.created_taskservs_dirpath | default "/tmp" |
    str replace "\~" $env.HOME | str replace "NOW" $env.NOW | str replace "./" $"($settings.src_path)/")
  let created_taskservs_dirpath = if ($str_created_taskservs_dirpath | str starts-with "/" ) { $str_created_taskservs_dirpath } else { $settings.src_path | path join $str_created_taskservs_dirpath }
  if not ( $created_taskservs_dirpath | path exists) { ^mkdir -p $created_taskservs_dirpath }
  let source_settings_path = ($created_taskservs_dirpath | path join $"($prov_env_path | path basename)")
  let target_settings_path = ($created_taskservs_dirpath| path join $"($prov_env_path | path basename | str replace '.k' '').($env.PROVISIONING_WK_FORMAT)")
  let res = if (is_sops_file $prov_env_path) {
    decode_sops_file $prov_env_path $source_settings_path true
    (parse_kcl_file  $source_settings_path $target_settings_path false $"🛑 load prov settings failed ($target_settings_path)")
  } else {
    cp $prov_env_path $source_settings_path
    (parse_kcl_file $source_settings_path $target_settings_path false $"🛑 load prov settings failed ($prov_env_path)")
  }
  if not $env.PROVISIONING_DEBUG { rm -f $source_settings_path }
  if $res and ($target_settings_path | path exists) {
    let data = (open $target_settings_path)
    if not $env.PROVISIONING_DEBUG { rm -f $target_settings_path }
    $data
  } else {
    {}
  }
}
export def get_file_format [
  filename: string
]: nothing -> string {
  if ($filename | str ends-with ".json") {
    "json"
  } else if ($filename | str ends-with ".yaml") {
    "yaml"
  } else {
    $env.PROVISIONING_WK_FORMAT
  }
}
export def save_provider_env [
  data: record
  settings: record
  provider_path: string
]: nothing -> nothing {
  if ($provider_path | is-empty) or not ($provider_path | path dirname |path exists) {
    _print $"❗ Can not save provider env for (_ansi blue)($provider_path | path dirname)(_ansi reset) in (_ansi red)($provider_path)(_ansi reset )"
    return
  }
  if (get_file_format $provider_path) == "json" {
      $"data: ($data | to json | encode base64)" | save --force $provider_path
  } else {
      $"data: ($data | to yaml | encode base64)" | save --force $provider_path
  }
  let result = (on_sops "encrypt" $provider_path --quiet)
  if ($result | is-not-empty) {
    ($result | save --force $provider_path)
  }
}
export def get_provider_data_path [
  settings: record
  server: record
]: nothing -> string {
  let data_path = if ($settings.data.prov_data_dirpath | str starts-with "." ) {
    ($settings.src_path | path join $settings.data.prov_data_dirpath)
  } else {
    $settings.data.prov_data_dirpath
  }
  if not ($data_path | path exists) { ^mkdir -p $data_path }
  ($data_path | path join $"($server.provider)_cache.($env.PROVISIONING_WK_FORMAT)")
}
export def load_provider_env [
  settings: record
  server: record
  provider_path: string = ""
]: nothing -> record {
  let data = if ($provider_path | is-not-empty) and ($provider_path |path exists) {
    let file_data = if (is_sops_file $provider_path) {
      on_sops "decrypt" $provider_path --quiet
      let result = (on_sops "decrypt" $provider_path --quiet)
      # --character-set binhex
      if (get_file_format $provider_path) == "json" {
        ($result | from json | get -o data | default "" | decode base64 | decode | from json)
      } else {
        ($result | from yaml | get -o data | default "" | decode base64 | decode | from yaml)
      }
    } else {
      open $provider_path
    }
    if ($file_data | is-empty) or ($file_data | get -o main | get -o vpc) == "?"  {
      # (throw-error $"load provider ($server.provider) settings failed" $"($provider_path) no main data"
      #  "load_provider_env" --span (metadata $data).span)
      if $env.PROVISIONING_DEBUG { _print  $"load provider ($server.provider) settings failed ($provider_path) no main data in load_provider_env" }
      {}
    } else {
      $file_data
    }
  } else {
    {}
  }
  if ($data | is-empty) {
    let new_data = (get_provider_env $settings $server)
    if ($new_data | is-not-empty) and ($provider_path | is-not-empty) { save_provider_env $new_data $settings $provider_path }
    $new_data
  } else {
    $data
  }
}
export def load_provider_settings [
  settings: record
  server: record
]: nothing -> record {
  let data_path = if ($settings.data.prov_data_dirpath | str starts-with "." ) {
      ($settings.src_path | path join $settings.data.prov_data_dirpath)
  } else { $settings.data.prov_data_dirpath }
  if ($data_path | is-empty) {
    (throw-error $"load provider ($server.provider) settings failed" $"($settings.data.prov_data_dirpath)"
      "load_provider_settings" --span (metadata $data_path).span)
  }
  if not ($data_path | path exists) { ^mkdir -p $data_path }
  let provider_path = ($data_path | path join $"($server.provider)_cache.($env.PROVISIONING_WK_FORMAT)")
  let data = (load_provider_env $settings $server $provider_path)
  if ($data | is-empty) or ($data | get -o main | get -o vpc) == "?"  {
    mw_create_cache  $settings $server false
    (load_provider_env $settings $server $provider_path)
  } else {
    $data
  }
}
export def load [
  infra?: string
  in_src?: string
  include_notuse?: bool = false
  --no_error
]: nothing -> record {
  let source = if $in_src == null or ($in_src | str ends-with '.k' ) { $in_src } else { $"($in_src).k" }
  let source_path = if $source != null and ($source | path type) == "dir" { $"($source)/($env.PROVISIONING_DFLT_SET)" } else { $source }
  let src_path =  if $source_path != null and ($source_path | path exists) {
    $"./($source_path)"
  } else if $source_path != null and ($source_path | str ends-with $env.PROVISIONING_DFLT_SET) == false {
    if $no_error {
      return {}
    } else {
      (throw-error "🛑 invalid settings infra / path " $"file ($source) settings in ($infra)" "settings->load" --span (metadata $source).span)
    }
  } else if ($infra | is-empty) and ($env.PROVISIONING_DFLT_SET| is-not-empty ) and ($env.PROVISIONING_DFLT_SET | path exists) {
      $"./($env.PROVISIONING_DFLT_SET)"
  } else if ($infra | path join $env.PROVISIONING_DFLT_SET | path exists) {
      $infra | path join $env.PROVISIONING_DFLT_SET
  } else {
    if $no_error {
      return {}
    } else {
      (throw-error "🛑 invalid settings infra / path " $"file ($source) settings in ($infra)" "settings->load" --span (metadata $source_path).span)
    }
  }
  let src_dir = ($src_path | path dirname)
  let infra_path = if $src_dir == "." {
    $env.PWD
  } else if ($src_dir | is-empty) {
    $env.PWD | path join $infra
  } else if ($src_dir | path exists ) and ( $src_dir | str starts-with "/")  {
    $src_dir
  } else {
    $env.PWD | path join $src_dir
  }
  let wk_settings_path = mktemp -d
  if not (parse_kcl_file  $"($src_path)" $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)" false "🛑 load settings failed ")  { return }
  if $env.PROVISIONING_DEBUG {  _print $"DEBUG source path: ($src_path)" }
  let settings_data = open $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)"
  if $env.PROVISIONING_DEBUG {  _print $"DEBUG work path: ($wk_settings_path)" }
  let servers_paths = ($settings_data | get -o servers_paths | default [])
  # Set full path for provider data
  let data_fullpath = if ($settings_data.prov_data_dirpath | str starts-with "." ) {
    ($src_dir | path join $settings_data.prov_data_dirpath)
  } else { $settings_data.prov_data_dirpath }
  mut list_servers = []
  mut providers_settings = []
  for it in $servers_paths {
    let file_path = if ($it | str ends-with ".k") {
      $it
    } else {
      $"($it).k"
    }
    let server_path = if ($file_path | str starts-with "/") {
      $file_path
    } else {
      ($src_path | path dirname | path join $file_path)
    }
    if not ($server_path | path exists) {
      if $no_error {
        "" | save $server_path
      } else {
        (throw-error "🛑 server path not found " ($server_path) "load each on list_servers" --span (metadata $servers_paths).span)
      }
    }
    let target_settings_path = $"($wk_settings_path)/($it | str replace --all "/" "_").($env.PROVISIONING_WK_FORMAT)"
    if not (parse_kcl_file  ($server_path | path join $server_path) $target_settings_path false "🛑 load settings failed ")  { return }
    #if not (parse_kcl_file  $server_path $target_settings_path false "🛑 load settings failed ")  { return }
    if not ( $target_settings_path | path exists) { continue }
    let servers_defs = (open $target_settings_path | default {})
    for srvr in ($servers_defs | get -o servers | default []) {
      if not $include_notuse and $srvr.not_use { continue }
      let provider = $srvr.provider
      if not ($"($wk_settings_path)/($provider)($settings_data.defaults_provs_suffix).($env.PROVISIONING_WK_FORMAT)" | path exists ) {
        let dflt_item = ($settings_data.defaults_provs_dirpath | path join $"($provider)($settings_data.defaults_provs_suffix)")
        let dflt_item_fullpath = if ($dflt_item | str starts-with "." ) {
          ($src_dir | path join $dflt_item)
        } else { $dflt_item }
        load_defaults $src_path $dflt_item_fullpath ($wk_settings_path | path join $"($provider)($settings_data.defaults_provs_suffix).($env.PROVISIONING_WK_FORMAT)")
      }
      # Loading defaults provider ...
      let server_with_dflts = if ($"($wk_settings_path)/($provider)($settings_data.defaults_provs_suffix).($env.PROVISIONING_WK_FORMAT)" | path exists ) {
        open ($"($wk_settings_path)/($provider)($settings_data.defaults_provs_suffix).($env.PROVISIONING_WK_FORMAT)") | merge $srvr
      } else { $srvr }
      # Loading provider data settings
      let server_prov_data = if ($data_fullpath | path join $"($provider)($settings_data.prov_data_suffix)" | path exists) {
        (load_defaults $src_dir ($data_fullpath | path join $"($provider)($settings_data.prov_data_suffix)")
          ($wk_settings_path |  path join $"($provider)($settings_data.prov_data_suffix)")
        )
        if (($wk_settings_path |  path join $"($provider)($settings_data.prov_data_suffix)") | path exists) {
          $server_with_dflts | merge (load_from_wk_format ($wk_settings_path |  path join $"($provider)($settings_data.prov_data_suffix)"))
        } else { $server_with_dflts }
      } else { $server_with_dflts }
      # Loading provider data settings
      let server_with_data = if ($data_fullpath | path join $"($srvr.hostname)_($provider)($settings_data.prov_data_suffix)" | path exists) {
        (load_defaults $src_dir ($data_fullpath | path join $"($srvr.hostname)_($provider)($settings_data.prov_data_suffix)")
          ($wk_settings_path | path join $"($srvr.hostname)_($provider)($settings_data.prov_data_suffix)")
        )
        if ($wk_settings_path | path join $"($srvr.hostname)_($provider)($settings_data.prov_data_suffix)" | path exists) {
          $server_prov_data | merge (load_from_wk_format ($wk_settings_path | path join $"($srvr.hostname)_($provider)($settings_data.prov_data_suffix)"))
        }  else { $server_prov_data }
      } else { $server_prov_data }
      $list_servers = ($list_servers | append $server_with_data)
      if ($providers_settings | where {|it| $it.provider == $provider} | length) == 0 {
        $providers_settings = ($providers_settings | append {
          provider: $provider,
          settings: (load_provider_settings {
              data: $settings_data,
              providers: $providers_settings,
              src: ($src_path | path basename),
              src_path: ($src_path | path dirname),
              infra: ($infra_path | path basename),
              infra_path: ($infra_path |path dirname),
              wk_path: $wk_settings_path
            }
            $server_with_data)
          }
        )
      }
    }
  }
  #{ settings: $settings_data, servers: ($list_servers | flatten) }
  # | to ($env.PROVISIONING_WK_FORMAT) | save --append $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)"
  #  let servers_settings = { servers: ($list_servers | flatten) }
  let servers_settings = { servers: $list_servers }
  if $env.PROVISIONING_WK_FORMAT == "json" {
    #$servers_settings | to json | save --append $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)"
    $servers_settings | to json | save --force $"($wk_settings_path)/servers.($env.PROVISIONING_WK_FORMAT)"
  } else {
    #$servers_settings | to yaml | save --append $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)"
    $servers_settings | to yaml | save --force $"($wk_settings_path)/servers.($env.PROVISIONING_WK_FORMAT)"
  }
  #let $settings_data = (open $"($wk_settings_path)/settings.($env.PROVISIONING_WK_FORMAT)")
  let $settings_data = ($settings_data | merge $servers_settings )
  {
    data: $settings_data,
    providers: $providers_settings,
    src: ($src_path | path basename),
    src_path: ($src_path | path dirname),
    infra: ($infra_path | path basename),
    infra_path: ($infra_path |path dirname),
    wk_path: $wk_settings_path
  }
}
export def load_settings [
  --infra (-i): string
  --settings (-s): string # Settings path
  include_notuse: bool = false
  no_error: bool = false
]: nothing -> record {
  let kld = get_infra (if $infra == null { "" } else { $infra })
  if $no_error {
    (load $kld $settings $include_notuse --no_error)
  } else {
    (load $kld $settings $include_notuse)
  }
  # let settings = (load $kld $settings $exclude_not_use)
  # if $env.PROVISIONING_USE_SOPS? != "" {
  #   use sops/lib.nu check_sops
  #   check_sops $settings.src_path
  # }
  # $settings
}
export def save_settings_file [
  settings:    record
  target_file: string
  match_text:  string
  new_text:    string
  mark_changes: bool = false
]: nothing -> nothing {
  let it_path = if ($target_file | path exists) {
    $target_file
  } else if ($settings.src_path | path join $"($target_file).k" | path exists) {
      ($settings.src_path | path join $"($target_file).k")
  } else if ($settings.src_path | path join $"($target_file).($env.PROVISIONING_WK_FORMAT)" | path exists) {
    ($settings.src_path | path join $"($target_file).($env.PROVISIONING_WK_FORMAT)")
  } else {
    _print $"($target_file) not found in ($settings.src_path)"
    return false
  }
  if (is_sops_file $it_path) {
      let result = (on_sops "decrypt" $it_path --quiet)
      if ($result | is-empty) {
        (throw-error $"🛑 saving settings to ($it_path)"
            $"from ($match_text) to ($new_text)"
              $"in ($target_file)" --span (metadata $it_path).span)
        return false
      } else {
        $result | str replace $match_text $new_text| save --force $it_path
        let en_result = (on_sops "encrypt" $it_path --quiet)
        if ($en_result | is-not-empty) {
            ($en_result | save --force $it_path)
        }
      }
  } else {
      open $it_path --raw | str replace $match_text $new_text | save --force $it_path
  }
  #if $it_path != "" and (^grep -q $match_text $it_path | complete).exit_code == 0 {
  #   if (^sed -i $"s/($match_text)/($match_text)\"($new_text)\"/g" $it_path | complete).exit_code == 0 {
  _print $"($target_file) saved with new value "
  if $mark_changes {
    if ($settings.wk_path | path join "changes" | path exists) == false {
      $"($it_path) has been changed" | save ($settings.wk_path | path join "changes") --append
    }
  } else if ($env.PROVISIONING_MODULE | is-not-empty) {
    ^($env.PROVISIONING_NAME) "-mod" $env.PROVISIONING_MODULE $env.PROVISIONING_ARGS
    exit
  }
  #   }
  #}
}
export def save_servers_settings [
  settings:    record
  match_text:  string
  new_text:    string
]: nothing -> nothing {
  $settings.data.servers_paths | each { | it |
    save_settings_file $settings $it $match_text $new_text
  }
}
export def settings_with_env [
  settings:    record
] {
  mut $servers_with_ips = []
  for srv in ($settings.data.servers) {
    let pub_ip = (mw_ip_from_cache $settings $srv false)
    if ($pub_ip | is-empty) {
      $servers_with_ips = ($servers_with_ips | append ($srv))
    }  else {
      $servers_with_ips = ($servers_with_ips | append ($srv | merge { network_public_ip: $pub_ip }))
    }
  }
  ($settings | merge { data: ($settings.data | merge { servers: $servers_with_ips}) })
}
