#!/usr/bin/env nu
# Info: Script to run Provisioning
# Author: Jesus Perez Lorenzo
# Release: 2.0.4
# Date: 6-2-2024

# CRITICAL: Must be in export-env block so it runs DURING PARSING,
# not after. This sets up NU_LIB_DIRS before modules are loaded.
export-env {
    # Initialize NU_LIB_DIRS, handling both string (from bash) and list (from Nushell)
    let lib_dirs_raw = ($env.NU_LIB_DIRS? | default "")
    let current_lib_dirs = if ($lib_dirs_raw | type) == "string" {
        if ($lib_dirs_raw | is-empty) {
            []
        } else {
            ($lib_dirs_raw | split row ":")
        }
    } else {
        $lib_dirs_raw
    }

    # Ensure known provisioning paths are in NU_LIB_DIRS
    let default_paths = [
        "/opt/provisioning/core/nulib"
        "/usr/local/provisioning/core/nulib"
    ]

    # Combine paths: use default paths first, then add any from current
    $env.NU_LIB_DIRS = ($default_paths | append $current_lib_dirs)

    # Auto-load tera plugin BEFORE loading any modules
    # This ensures tera-render is available throughout the script
    if ( (version).installed_plugins | str contains "tera" ) {
        (plugin use tera)
    }
}

use std log
use lib_provisioning *
use env.nu *

#Load all main defs
use main_provisioning *

#module srv { use instances.nu * }

use servers/ssh.nu *
use servers/utils.nu *
use taskservs/utils.nu find_taskserv
# Bootstrap will be loaded on-demand only when needed for real operations
# use lib_provisioning/platform/bootstrap.nu *

# Helper: Reorder arguments to put flags before positional args
# This allows: provisioning workspace update --yes
# Instead of requiring: provisioning --yes workspace update
# NOTE: Nushell's parameter parsing handles interleaved flags well, so we just return args as-is
# This avoids breaking flag:value pairs
def reorder_args [args: list]: nothing -> list {
  $args
}

# Help on provisioning commands
export def "main help" [
  ...args: string  # Optional category: infrastructure, orchestration, development, workspace, concepts
  --notitles       # not titles
  --out: string    # Print Output format: json, yaml, text (default)
] {
  if $notitles == null or not $notitles { show_titles }
  if ($out | is-not-empty) { $env.PROVISIONING_NO_TERMINAL = false }
  # Use only the first argument, ignore any extras (e.g., "orch status" -> "orch")
  let category = if ($args | length) > 0 { ($args | get 0) } else { "" }
  print (provisioning_options $category)
  if not $env.PROVISIONING_DEBUG { end_run "" }
}

def main [
  ...args: string  # Other options, use help to get info
  --infra (-i): string     # Cloud directory
  --settings (-s): string # Settings path
  --serverpos (-p): int   # Server position in settings
  --outfile (-o): string  # Output file
  --template(-t): string  # Template path or name in PROVISION_KLOUDS_PATH
  --check (-c)     # Only check mode no servers will be created
  --upload (-u)    # Upload scripts to server for inspection without executing (use with --check)
  --yes (-y)       # confirm task
  --wait           # Wait servers to be created
  --keepstorage    # keep storage
  --select: string # Select with task as option
  --onsel: string  # On selection: e (edit) | v (view) | l (list) | t (tree)
  --infras: string # Infra list names separated by commas
  --new (-n): string    # New infrastructure name
  --debug (-x)     # Use Debug mode
  --xm             # Debug with PROVISIONING_METADATA
  --xc             # Debug for task and services locally PROVISIONING_DEBUG_CHECK
  --xr             # Debug for remote servers PROVISIONING_DEBUG_REMOTE
  --xld            # Log level with DEBUG PROVISIONING_LOG_LEVEL=debug
  --nc             # Not clean working settings
  --metadata       # Error with metadata (-xm)
  --notitles       # not tittles
  --environment: string    # Environment override (dev/test/prod)
  --dep-option: string     # Workspace dependency option: workspace-home, home-package, git-package, publish-repo
  --dep-url: string        # Dependency URL for git-package or publish-repo
  --dry-run                # Show what would be done without doing it (pack command)
  --force (-f)             # Skip confirmation prompts (pack/delete commands)
  --all                    # Process all items (pack clean command)
  --keep-latest: int       # Keep N latest versions (pack clean command)
  --workspace (-w): string # Workspace name (for bootstrap, cluster deploy, etc.)
  --activate                # Activate workspace as default (workspace commands)
  --interactive             # Interactive workspace creation wizard
  --org: string            # Organization name (for detect/complete commands)
  --apply                  # Apply changes (for complete command)
  --verbose                # Verbose output (for detect/complete/workflow commands)
  --pretty                 # Pretty-print JSON/YAML output (for detect/complete commands)
  -v               # Show version
  --version (-V)   # Show version with title
  --info                   # Show Info with title
  --about                  # Show About
  --helpinfo (-h)  # For more details use options "help" (no dashes)
  --out: string    # Print Output format: json, yaml, text (default)
   --view           # Print with highlight
   --inputfile: string  # Input format: json, yaml, text (default)
   --include_notuse # Include servers not use
   --services: string   # Platform services set: core, all, custom (for platform start)
]: nothing -> nothing {
  # Reorder arguments: move flags to the beginning
  # This allows: provisioning workspace update --yes
  let reordered_args = (reorder_args $args)

  # Extract flags from reordered args (for flags that came after positional args)
  let has_yes_in_args = ($reordered_args | any {|x| $x == "--yes" or $x == "-y"})
  let has_check_in_args = ($reordered_args | any {|x| $x == "--check" or $x == "-c"})
  let has_upload_in_args = ($reordered_args | any {|x| $x == "--upload" or $x == "-u"})
  let has_force_in_args = ($reordered_args | any {|x| $x == "--force" or $x == "-f"})
  let has_verbose_in_args = ($reordered_args | any {|x| $x == "--verbose" or $x == "-v"})
  let has_wait_in_args = ($reordered_args | any {|x| $x == "--wait"})

  # Combine with already-parsed flags (take OR - if either parsed or in args, then true)
  let final_yes = ($yes or $has_yes_in_args)
  let final_check = ($check or $has_check_in_args)
  let final_upload = ($upload or $has_upload_in_args)
  let final_force = ($force or $has_force_in_args)
  let final_verbose = ($verbose or $has_verbose_in_args)
  let final_wait = ($wait or $has_wait_in_args)

  # Initialize provisioning system
  provisioning_init $helpinfo "" $reordered_args

  # Parse all flags into normalized structure
   let parsed_flags = (parse_common_flags {
     version: $version, v: $v, info: $info, about: $about,
     debug: $debug, metadata: $metadata, xc: $xc, xr: $xr, xld: $xld,
     check: $final_check, upload: $final_upload, yes: $final_yes, wait: $final_wait, keepstorage: $keepstorage,
     nc: $nc, include_notuse: $include_notuse,
     out: $out, notitles: $notitles, view: $view,
     infra: $infra, infras: $infras, settings: $settings, outfile: $outfile,
     template: $template, select: $select, onsel: $onsel, serverpos: $serverpos,
     new: $new, environment: $environment,
     dep_option: $dep_option, dep_url: $dep_url,
     dry_run: $dry_run, force: $final_force, all: $all, keep_latest: $keep_latest,
     activate: $activate, interactive: $interactive,
     org: $org, apply: $apply, verbose: $final_verbose, pretty: $pretty,
     services: $services, workspace: $workspace
   })

  # Handle version, info, about flags
  if $parsed_flags.show_version { ^$env.PROVISIONING_NAME -v ; exit }
  if $parsed_flags.show_info { ^$env.PROVISIONING_NAME -i ; exit }
  if $parsed_flags.show_about { _print (get_about_info) ; exit }

  # Bootstrap platform services (only if running actual commands, not help/info)
  # Skip bootstrap for help-like, guide, setup, discovery/info, and utility commands
  # Updated for Phase 1: Fast-Path Expansion - Include read-only workspace commands
  let is_help_command = (
    ($reordered_args | length) == 0 or
    ($reordered_args | get 0) in [
      # Help and guides
      "help", "-h", "--help",
      "sc", "shortcuts", "quickstart", "quick",
      "from-scratch", "scratch",
      "customize", "custom",
      "guide", "guides", "howto",
      # Setup
      "setup", "st",
      # Workspace commands (read-only, fast-path)
      "workspace", "ws",
      # Discovery and module commands
      "mod", "module", "discover", "disc",
      "dt", "dp", "dc",
      "discover-taskservs", "disc-t",
      "discover-providers", "disc-p",
      "discover-clusters", "disc-c",
      # Development info
      "lyr", "layer", "version", "pack",
      # Utilities and info
      "nuinfo", "env", "allenv",
      "validate", "val", "show", "config-template",
      "cache",
      "list", "l", "ls",
      "plugin", "plugins",
      "qr", "ssh", "sops",
      "providers",
      # Diagnostics commands (workspace-agnostic)
      "status", "health", "diagnostics", "next", "phase"
    ]
  )

  # Check if this is a command that doesn't need platform bootstrap
  # VM commands and infrastructure commands can work without bootstrap
  # Also skip bootstrap if --check flag is present (validation mode, no execution needed)
  let skip_bootstrap = (
    (($reordered_args | length) > 0 and
    ($reordered_args | get 0) in [
      # Interactive Nushell session (no bootstrap needed)
      "nu",
      # Platform commands (don't need bootstrap)
      "platform", "plat", "p",
      # VM commands (info/list only, no bootstrap needed)
      "vm", "vmi", "vmh", "vml",
      # Infrastructure commands can work offline
      "server", "s",
      "taskserv", "task", "t",
      "cluster", "cl",
      "bootstrap",
      # Create command (with various targets)
      "create", "c",
      # Delete command
      "delete", "d",
      # Update command
      "update", "u",
      # Build commands (image management, doesn't need orchestrator)
      "build", "b", "bi", "build-image"
    ]) or
    # Skip bootstrap if in check mode (validation/dry-run, no execution needed)
    $final_check
  )

  if (not $is_help_command) and (not $skip_bootstrap) {
    # Load bootstrap module dynamically when needed
    use lib_provisioning/platform/bootstrap.nu *
    let bootstrap_result = (bootstrap-platform --auto-start --timeout=60 --verbose=($final_verbose))
    if not $bootstrap_result.all_healthy {
      _print ""
      _print $"(_ansi red)❌ Platform services not healthy(_ansi reset)"
      _print ""
      _print "Failed services:"
      for service in ($bootstrap_result.services | where {|s| $s.status != "healthy"}) {
        _print $"  - ($service.name): ($service.action)"
      }
      _print ""
      _print "To start services manually:"
      _print "  cd provisioning/platform && docker-compose up -d"
      _print ""
      exit 1
    }
  }

  # DEBUG
  if ($env.PROVISIONING_DEBUG? | default false) {
    print $"DEBUG provisioning: reordered_args = ($reordered_args)" >&2
    print $"DEBUG provisioning: parsed_flags.infra = (($parsed_flags | get -o infra | default 'MISSING'))" >&2
  }

  # Handle help command BEFORE dispatcher to avoid infinite loop
  # The dispatcher used to call "exec provisioning help" which created infinite recursion
  if (($reordered_args | length) > 0) and (($reordered_args | get 0) in ["help", "h"]) {
    if ($env.PROVISIONING_DEBUG? | default false) {
      print $"DEBUG: Help command detected, args=($reordered_args)" >&2
    }
    let category = if ($reordered_args | length) > 1 { ($reordered_args | get 1) } else { "" }
    print (provisioning_options $category)
    if not ($env.PROVISIONING_DEBUG? | default false) { end_run "" }
    return
  }

  # For info/discovery/utility commands, dispatch directly without going through workspace enforcement
  # These commands don't need workspace context
  if (($reordered_args | length) > 0) and (($reordered_args | get 0) in [
    # Guide commands
    "guide", "guides", "sc", "howto", "shortcuts", "quickstart", "quick",
    "from-scratch", "scratch", "customize", "custom",
    # Discovery/info commands
    "mod", "module", "discover", "disc",
    "dt", "dp", "dc",
    "discover-taskservs", "disc-t",
    "discover-providers", "disc-p",
    "discover-clusters", "disc-c",
    "lyr", "layer", "version",
    "nuinfo", "env", "allenv",
    "validate", "val", "show", "cache",
    # Utility commands (these are informational)
    "plugin", "plugins",
    "qr", "nuinfo",
    # Diagnostics commands (workspace-agnostic)
    "status", "health", "diagnostics", "next", "phase"
  ]) {
    dispatch_command $reordered_args $parsed_flags
    if not $env.PROVISIONING_DEBUG { end_run "" }
    return
  }

  # Check if we're in module mode (invoked with -mod flag from bash wrapper)
  # If so, bypass dispatcher and call the module directly
  if ($env.PROVISIONING_MODULE? | default "" | is-not-empty) {
    let module = $env.PROVISIONING_MODULE
    # At this point, $reordered_args contains [create, ...] or whatever the user provided after -mod
    # We need to invoke the module's main function

    match $module {
      "server" => {
        use servers/create.nu *
        # Ensure tera plugin is loaded for template rendering
        let tera_available = ((plugin list | where name == "tera" | length) > 0)
        if $tera_available {
          if ($env.PROVISIONING_DEBUG? | default false) {
            _print "DEBUG: Loading tera plugin (-mod server)..." >&2
          }
          (plugin use tera)
          if ($env.PROVISIONING_DEBUG? | default false) {
            _print "DEBUG: Tera plugin loaded for -mod server" >&2
          }
        }
        # Call server create module main function
        # $reordered_args now has ["create"] or ["delete"] or ["list"] etc.
        main ...$reordered_args --check=$final_check --wait=$final_wait --infra=($infra | default "") --settings=($settings | default "") --outfile=($outfile | default "") --debug=$debug --xm=$xm --xc=$xc --xr=$xr --xld=$xld --metadata=$metadata --notitles=$notitles --out=($out | default "")
      }
      "taskserv" | "task" => {
        use taskservs/create.nu *
        main ...$reordered_args --check=$final_check --upload=$final_upload --wait=$final_wait --debug=$debug
      }
      "cluster" => {
        use clusters/create.nu *
        main ...$reordered_args --check=$final_check --debug=$debug
      }
      "images" => {
        use images/create.nu *
        use images/list.nu *
        use images/update.nu *
        use images/delete.nu *
        use images/state.nu *
        use images/watch.nu *
        # $reordered_args now has ["create", "cp", "--infra", "..."] or similar
        let subcommand = if ($reordered_args | length) > 0 { $reordered_args | get 0 } else { "help" }
        match $subcommand {
          "create" | "c" => {
            let role = if ($reordered_args | length) > 1 { $reordered_args | get 1 } else { "" }
            let infra_arg = if ($infra | is-not-empty) { $infra } else { "" }
            image-create $role --infra=$infra_arg --check=$final_check
          }
          "list" | "l" => {
            let provider = if ($infra | is-not-empty) { $infra } else { "" }
            image-list --provider=$provider
          }
          "update" | "u" => {
            let role = if ($reordered_args | length) > 1 { $reordered_args | get 1 } else { "" }
            let infra_arg = if ($infra | is-not-empty) { $infra } else { "" }
            image-update $role --infra=$infra_arg --check=$final_check
          }
          "delete" | "d" => {
            let role = if ($reordered_args | length) > 1 { $reordered_args | get 1 } else { "" }
            image-delete $role --yes=$final_yes
          }
          "state" | "s" => {
            image-state-list --provider=$infra
          }
          "watch" | "w" => {
            let interval = if ($reordered_args | length) > 1 { $reordered_args | get 1 } else { "30" }
            image-watch --interval=($interval | into int)
          }
          "help" | "h" | _ => {
            print "Image Management Commands"
            print "======================="
            print ""
            print "Usage: provisioning build image <command> [options]"
            print ""
            print "Commands:"
            print "  create <role>     - Build snapshot for role"
            print "  list              - Show all role states"
            print "  update <role>     - Rebuild stale snapshot"
            print "  delete <role>     - Remove snapshot + state"
            print "  state             - List all state files"
            print "  watch             - Monitor role freshness"
            print ""
            print "Options:"
            print "  --infra <path>    - Infrastructure directory"
            print "  --check           - Validate without executing"
            print "  --yes             - Skip confirmation"
            print ""
          }
        }
      }
      _ => {
        print $"Unknown module: ($module)"
        exit 1
      }
    }
  } else {
    # Normal command dispatch through dispatcher
    dispatch_command $reordered_args $parsed_flags
  }

  # End run if not in debug mode
  if not ($env.PROVISIONING_DEBUG? | default false) { end_run "" }
}

export def get_show_info [
  ops: list
  curr_settings: record
  out: string
]: nothing -> record  {
  match ($ops | get -o 0 | default "") {
    "set" |"setting" | "settings" => $curr_settings,
    "def" | "defs" |"defsetting" | "defsettings" => {
      let src = ($curr_settings | get -o src | default "");
      let src_path = ($curr_settings | get -o src_path | default "");
      let def_settings = if ($src_path | path join $src | path exists) {
        open -r ($src_path | path join $src)
      } else { "" }
      let main_path = ($env.PROVISIONING | path join "kcl" | path join "settings.k")
      let src_main_settings = if ($main_path | path exists) {
        open -r $main_path
      } else { "" }
      {
        def: $src,
        def_path: $src_path,
        infra: ($curr_settings | get -o infra | default ""),
        infra_path: ($curr_settings | get -o infra_path | default ""),
        def_settings: $def_settings,
        main_path: $main_path,
        main_settings: $src_main_settings,
      }
    },
    "server" |"servers" | "s" => {
      let servers = ($curr_settings | get -o data | get -o servers |  default {})
      let item = ($ops | get -o 1 | default "")
      if ($item | is-empty) {
        $servers
      } else {
        let server = (find_server $item $servers ($out | default ""))
        let def_target = ($ops | get -o 2 | default "")
        match $def_target {
          "t" | "task" | "taskserv" => {
            let task = ($ops | get -o 3 | default "")
            (find_taskserv $curr_settings $server $task ($out | default ""))
          },
          _ => $server,
        }
      }
    },
    "serverdefs" |"serversdefs" | "sd" => {
      (find_serversdefs $curr_settings)
    },
    "provgendefs" |"provgendef" | "pgd" => {
      (find_provgendefs)
    },
    "taskservs" |"taskservs" | "ts" => {
      #(list_taskservs $curr_settings)
      let list_taskservs = (taskservs_list)
      if ($list_taskservs | length) == 0 {
          _print $"🛑 no items found for (_ansi cyan)taskservs list(_ansi reset)"
          return
      }
      $list_taskservs
    },
    "taskservsgendefs" |"taskservsgendef" | "tsd" => {
      let defs_path = ($env.PROVISIONING_TASKSERVS_PATH | path join $env.PROVISIONING_GENERATE_DIRPATH | path join $env.PROVISIONING_GENERATE_DEFSFILE)
      if ($defs_path | path exists) {
        open $defs_path
      }
    },
    "cost" | "costs" | "c" | "price" | "prices" | "p" => {
      (servers_walk_by_costs $curr_settings "" false false "stdout")
    },
    "alldata" => ($curr_settings | get -o data | default {}
       | merge { costs: (servers_walk_by_costs $curr_settings "" false false "stdout") }
    ),
    "data" | _ => {
      if ($out | is-not-empty) {
        ($curr_settings | get -o data | default {})
      } else {
          print ($" (_ansi cyan_bold)($curr_settings | get -o data | get -o main_name | default '')"
            + $"(_ansi reset): (_ansi yellow_bold)($curr_settings | get -o data | get -o main_title | default '') (_ansi reset)"
          )
          print ($curr_settings | get -o data | default {} | merge { servers: ''})
          ($curr_settings | get -o data | default {} | get -o servers | each {|item|
              print $"\n server:  (_ansi cyan_bold)($item.hostname | default '') (_ansi reset)"
              print $item
          })
          ""
      }
    },
  }
}
