# Image create — render build template, execute, capture snapshot ID, persist state.

use ./state.nu *
use ../lib_provisioning/utils/nickel_processor.nu [ncl-eval]

# Load the ImageRole definition from the workspace images.ncl for a given role name.
def load-image-role [infra: string, role: string]: nothing -> record {
    let images_ncl = ($infra | path join "images.ncl")
    if not ($images_ncl | path exists) {
        error make { msg: $"images.ncl not found at ($images_ncl)" }
    }
    let data = (ncl-eval $images_ncl [])
    let roles = ($data | get image_roles? | default {})
    let role_def = ($roles | get -o $role)
    if ($role_def | is-empty) {
        error make { msg: $"Role '($role)' not defined in ($images_ncl)" }
    }
    $role_def
}

# Build template context and render via tera plugin.
def render-build-template [role_def: record, infra: string, check: bool]: nothing -> string {
    let tera_loaded = (plugin list | where name == "tera" | length) > 0
    if not $tera_loaded { plugin use tera }

    let provider  = ($role_def | get provider? | default "hetzner")
    let tpl_name  = ($role_def | get template_name? | default "hetzner_build_image.j2")
    let tpl_path  = ($env.PROVISIONING | path join "extensions" | path join "providers"
                     | path join $provider | path join "templates" | path join $tpl_name)

    if not ($tpl_path | path exists) {
        error make { msg: $"Build template not found: ($tpl_path)" }
    }

    # Calculate flake directory: go up 2 levels from infra/wuji to workspace root, then add nixos
    let infra_expanded = ($infra | path expand)
    let workspace_root = ($infra_expanded | path dirname | path dirname)
    let flake_dir = ($workspace_root | path join "nixos")

    let ctx = {
        image_role:            $role_def,
        ssh_key:               ($role_def | get ssh_key? | default ""),
        location:              ($role_def | get location? | default "nbg1"),
        flake_dir:             $flake_dir,
        now:                   ((date now) | format date "%Y-%m-%dT%H:%M:%SZ"),
        provisioning_version:  ($env.PROVISIONING_VERSION? | default "0.0.0"),
        check:                 $check,
    }
    $ctx | tera-render $tpl_path
}

# Parse the SNAPSHOT_ID=<id> line from build script stdout.
def extract-snapshot-id [output: string]: nothing -> string {
    let line = ($output | lines | find "SNAPSHOT_ID=" | first?)
    if ($line | is-empty) {
        error make { msg: "Build script did not emit SNAPSHOT_ID=<id>" }
    }
    $line | str replace "SNAPSHOT_ID=" "" | str trim
}

export def image-create [
    role: string
    --infra: string = ""
    --check
] {
    let infra_path = if ($infra | is-empty) {
        let ws = ($env.PROVISIONING_WORKSPACE? | default "")
        if ($ws | is-empty) {
            error make { msg: "Specify --infra <path> or set PROVISIONING_WORKSPACE" }
        }
        $ws | path join "infra"
    } else {
        let expanded = ($infra | path expand)

        # Detect if we're in a project subdirectory and path was duplicated
        # E.g., ran from /project/workspaces with --infra workspaces/... → /project/workspaces/workspaces/...
        if ($expanded | str contains "workspaces/workspaces") or ($expanded | str contains "infra/infra") {
            let cwd = (pwd)
            let infra_parts = ($infra | split row "/")
            let first_part = ($infra_parts | get 0)

            # If we're in a subdirectory that matches the first part of --infra, strip it
            if ($cwd | str contains $first_part) {
                let adjusted = ($infra_parts | skip 1 | str join "/")
                let adjusted_path = ($adjusted | path expand)

                if ($adjusted_path | path exists) {
                    $adjusted_path
                } else {
                    error make {
                        msg: $"Path duplication detected in: ($expanded)\n\nYou appear to be in a subdirectory. Either:\n  1. Run from project root: cd ($env.HOME)/project-provisioning\n  2. Use absolute path: --infra ($env.HOME)/project-provisioning/workspaces/...\n  3. Use relative from current dir: --infra librecloud_hetzner/infra/wuji"
                    }
                }
            } else {
                $expanded
            }
        } else {
            $expanded
        }
    }

    let role_def = (load-image-role $infra_path $role)
    let provider = ($role_def | get provider? | default "hetzner")

    print $"Building image role '($role)' for provider '($provider)'"

    if $check {
        let script = (render-build-template $role_def $infra_path true)
        print "── [check mode] rendered build script ──"
        print $script
        print "── no snapshot created ──"
        return
    }

    let script   = (render-build-template $role_def $infra_path false)
    let tmp_dir = ($env.TMPDIR? | default "/tmp")
    let tmp_path = ($tmp_dir | path join $"build_image_($provider)_($role).sh")
    $script | save --force $tmp_path
    ^chmod +x $tmp_path

    print $"Executing build script: ($tmp_path)"
    print ""

    # Execute script - redirect output to log file for visibility
    let tmp_log = ($tmp_dir | path join $"build_image_($provider)_($role).log")

    # Run bash script via shell, capturing output to log file
    # Don't use Nushell's external command error handling - let shell handle it
    ^sh -c $"bash -x ($tmp_path) >($tmp_log) 2>&1 || true"

    # ALWAYS print build output, even if bash failed
    if ($tmp_log | path exists) {
        print ""
        print "=== Build Output ==="
        print (open $tmp_log)
        print ""
    }

    # Check if script had any error (look for error: in output)
    if ($tmp_log | path exists) {
        let log_content = (open $tmp_log)
        if ($log_content | str contains "error:") {
            print "❌ BUILD FAILED - see output above for details"
            exit 1
        }
    }

    let snapshot_id = (extract-snapshot-id (open $tmp_log))
    print $"Snapshot created: ($snapshot_id)"

    let os_base = ($role_def | get os_base? | default "debian-12")
    let labels  = ($role_def | get labels?  | default {})

    image-state-write $provider $role {
        provider:    $provider,
        role:        $role,
        snapshot_id: $snapshot_id,
        built_at:    ((date now) | format date "%Y-%m-%dT%H:%M:%SZ"),
        last_used:   null,
        os_base:     $os_base,
        labels:      $labels,
    }

    print $"State saved: (image-state-path $provider $role)"
}
