#!/usr/bin/env bash

set -euo pipefail

COMMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VM_TOOLS_DIR="$(cd "$COMMON_DIR/.." && pwd)"
PROJECT_ROOT="$(cd "$VM_TOOLS_DIR/../.." && pwd)"
SC_OWNER_USER="${SC_OWNER_USER:-${SUDO_USER:-$USER}}"
SC_OWNER_HOME="${SC_OWNER_HOME:-$(getent passwd "$SC_OWNER_USER" | cut -d: -f6)}"
SC_OWNER_HOME="${SC_OWNER_HOME:-$HOME}"
export LIBVIRT_DEFAULT_URI="${LIBVIRT_DEFAULT_URI:-qemu:///system}"
if [ "${LIBVIRT_DEFAULT_URI}" = "qemu:///system" ]; then
    SC_HOME="${SC_HOME:-/var/lib/libvirt/sysadmin-chronicles}"
    SC_IMAGE_ROOT="${SC_IMAGE_ROOT:-/var/lib/libvirt/images/sysadmin-chronicles}"
else
    SC_HOME="${SC_HOME:-$SC_OWNER_HOME/.local/share/sysadmin-chronicles}"
    SC_IMAGE_ROOT="${SC_IMAGE_ROOT:-$SC_HOME/images}"
fi
SC_BASE_DIR="$SC_IMAGE_ROOT/base"
SC_SEED_DIR="$SC_IMAGE_ROOT/seed"
SC_POOL_NAME="${SC_POOL_NAME:-sc-images}"
SC_NETWORK_NAME="${SC_NETWORK_NAME:-sc-internal}"
SC_SSH_KEY="${SC_SSH_KEY:-$SC_OWNER_HOME/.ssh/sc_host_key}"
DRY_RUN="${DRY_RUN:-false}"

_virsh() {
    if [ "${SC_VIRSH_SUDO:-false}" = true ]; then
        sudo virsh "$@"
    else
        virsh "$@"
    fi
}

_virt_install() {
    if [ "${SC_VIRSH_SUDO:-false}" = true ]; then
        sudo virt-install "$@"
    else
        virt-install "$@"
    fi
}

step() { echo ""; echo "── $* ───────────────────────────────────────"; }
ok()   { echo "  ✓ $*"; }
info() { echo "  → $*"; }
fail() { echo "  ✗ $*"; exit 1; }

ssh_login_user() {
    local domain="${1:-}"
    case "$domain" in
        sc-workstation) printf '%s\n' "opsbridge" ;;
        *) printf '%s\n' "player" ;;
    esac
}

run() {
    if [ "$DRY_RUN" = "true" ]; then
        echo "  [DRY-RUN] $*"
    else
        "$@"
    fi
}

require_cmd() {
    local cmd="$1"
    command -v "$cmd" >/dev/null 2>&1 || fail "Required command not found: $cmd"
}

ensure_vm_tooling() {
    require_cmd virsh
    require_cmd qemu-img
    require_cmd curl
    require_cmd virt-install

    if ! command -v cloud-localds >/dev/null 2>&1 \
    && ! command -v genisoimage >/dev/null 2>&1 \
    && ! command -v mkisofs >/dev/null 2>&1 \
    && ! command -v xorriso >/dev/null 2>&1; then
        fail "Need cloud-localds, genisoimage, mkisofs, or xorriso to build NoCloud seed images"
    fi

    [ -f "$SC_SSH_KEY" ] || fail "Missing SSH private key: $SC_SSH_KEY"
    [ -f "${SC_SSH_KEY}.pub" ] || fail "Missing SSH public key: ${SC_SSH_KEY}.pub"

    run mkdir -p "$SC_BASE_DIR" "$SC_SEED_DIR"

    _virsh pool-info "$SC_POOL_NAME" >/dev/null 2>&1 || fail "Missing libvirt pool: $SC_POOL_NAME"
    _virsh net-info "$SC_NETWORK_NAME" >/dev/null 2>&1 || fail "Missing libvirt network: $SC_NETWORK_NAME"
}

pool_path() {
    local path
    path="$(_virsh pool-dumpxml "$SC_POOL_NAME" | sed -n 's:.*<path>\(.*\)</path>.*:\1:p' | head -n1)"
    [ -n "$path" ] || fail "Could not determine pool path for $SC_POOL_NAME"
    printf '%s\n' "$path"
}

domain_exists() {
    local domain="$1"
    _virsh dominfo "$domain" >/dev/null 2>&1
}

download_if_missing() {
    local url="$1"
    local dest="$2"
    if [ -f "$dest" ]; then
        ok "Using cached base image: $(basename "$dest")"
        return
    fi
    info "Downloading $(basename "$dest")"
    run curl -L --fail --output "$dest" "$url"
}

create_backing_disk() {
    local base_image="$1"
    local target_disk="$2"
    local disk_size="${3:-}"
    run mkdir -p "$(dirname "$target_disk")"
    run rm -f "$target_disk"
    if [ -n "$disk_size" ]; then
        run qemu-img create -f qcow2 -F qcow2 -b "$base_image" "$target_disk" "$disk_size"
    else
        run qemu-img create -f qcow2 -F qcow2 -b "$base_image" "$target_disk"
    fi
}

create_seed_iso() {
    local user_data="$1"
    local meta_data="$2"
    local output_iso="$3"
    local seed_dir
    seed_dir="$(mktemp -d)"
    cp "$user_data" "$seed_dir/user-data"
    cp "$meta_data" "$seed_dir/meta-data"
    run rm -f "$output_iso"

    if command -v cloud-localds >/dev/null 2>&1; then
        run cloud-localds "$output_iso" "$seed_dir/user-data" "$seed_dir/meta-data"
    elif command -v genisoimage >/dev/null 2>&1; then
        run genisoimage -quiet -output "$output_iso" -volid cidata -joliet -rock "$seed_dir/user-data" "$seed_dir/meta-data"
    elif command -v mkisofs >/dev/null 2>&1; then
        run mkisofs -quiet -output "$output_iso" -volid cidata -joliet -rock "$seed_dir/user-data" "$seed_dir/meta-data"
    else
        run xorriso -as mkisofs -quiet -output "$output_iso" -volid cidata -joliet -rock "$seed_dir/user-data" "$seed_dir/meta-data"
    fi

    rm -rf "$seed_dir"
}

destroy_domain() {
    local domain="$1"
    if ! domain_exists "$domain"; then
        return
    fi
    info "Removing existing domain definition: $domain"
    run _virsh destroy "$domain" >/dev/null 2>&1 || true
    run _virsh undefine "$domain" --nvram --snapshots-metadata >/dev/null 2>&1 \
        || run _virsh undefine "$domain" --snapshots-metadata >/dev/null 2>&1 \
        || run _virsh undefine "$domain" --nvram >/dev/null 2>&1 \
        || run _virsh undefine "$domain" >/dev/null 2>&1 \
        || true
}

build_import_domain() {
    local domain="$1"
    local disk_path="$2"
    local seed_iso="$3"
    local ram_mb="$4"
    local vcpus="$5"
    local graphics_mode="$6"

    local args=(
        --name "$domain"
        --memory "$ram_mb"
        --vcpus "$vcpus"
        --import
        --disk "path=$disk_path,format=qcow2,bus=virtio"
        --disk "path=$seed_iso,device=cdrom"
        --network "network=$SC_NETWORK_NAME,model=virtio"
        --channel "unix,target_type=virtio,name=org.qemu.guest_agent.0"
        --rng /dev/urandom
        --osinfo detect=on,require=off
        --noautoconsole
    )

    case "$graphics_mode" in
        none)
            args+=(--graphics none --console pty,target_type=serial)
            ;;
        vnc)
            args+=(--graphics vnc,listen=127.0.0.1)
            ;;
        spice)
            args+=(
                --graphics spice,listen=127.0.0.1
                --video virtio
                --channel "spicevmc,target_type=virtio,name=com.redhat.spice.0"
            )
            ;;
        spice-qxl)
            args+=(
                --graphics spice,listen=127.0.0.1
                --video qxl
                --channel "spicevmc,target_type=virtio,name=com.redhat.spice.0"
            )
            ;;
        *)
            fail "Unknown graphics mode: $graphics_mode"
            ;;
    esac

    run _virt_install "${args[@]}"
    run _virsh autostart "$domain"
}

seed_cdrom_target() {
    local domain="$1"
    local seed_iso="$2"
    _virsh dumpxml "$domain" 2>/dev/null \
        | awk -v seed="$seed_iso" '
            /<disk / { in_disk=1; target="" }
            in_disk && /<source file=/ && index($0, seed) { matched=1 }
            in_disk && /<target dev=/ {
                line=$0
                sub(/.*<target dev=.?/, "", line)
                sub(/[ '\'"'"'"].*/, "", line)
                target=line
            }
            in_disk && /<\/disk>/ {
                if (matched && target != "") {
                    print target
                    exit
                }
                in_disk=0
                matched=0
                target=""
            }
        '
}

detach_seed_iso() {
    local domain="$1"
    local seed_iso="$2"
    local target

    target="$(seed_cdrom_target "$domain" "$seed_iso" || true)"
    if [ -z "$target" ]; then
        info "No cloud-init seed ISO attached to $domain"
        return 0
    fi

    info "Detaching cloud-init seed ISO from $domain ($target)"
    if _virsh domstate "$domain" 2>/dev/null | grep -qi running; then
        run _virsh detach-disk "$domain" "$target" --config >/dev/null 2>&1 || true
        run _virsh detach-disk "$domain" "$target" --live >/dev/null 2>&1 || true
        if seed_cdrom_target "$domain" "$seed_iso" >/dev/null 2>&1; then
            info "Restarting $domain to apply cloud-init seed ISO detach"
            run _virsh shutdown "$domain" >/dev/null 2>&1 || true
            local waited=0
            while [ "$waited" -lt 60 ] && _virsh domstate "$domain" 2>/dev/null | grep -qi running; do
                sleep 2
                waited=$((waited + 2))
            done
            if _virsh domstate "$domain" 2>/dev/null | grep -qi running; then
                run _virsh destroy "$domain" >/dev/null 2>&1 || true
            fi
            run _virsh start "$domain" >/dev/null 2>&1
            wait_for_agent_ip "$domain" 180 >/dev/null || true
        fi
    else
        run _virsh detach-disk "$domain" "$target" --config >/dev/null 2>&1 || true
    fi
}

domain_mac() {
    local domain="$1"
    _virsh dumpxml "$domain" 2>/dev/null | sed -n "s/.*<mac address='\([^']*\)'.*/\1/p" | head -n1
}

dhcp_lease_ip() {
    local domain="$1"
    local mac
    mac="$(domain_mac "$domain")"
    [ -n "$mac" ] || return 1
    _virsh net-dhcp-leases "$SC_NETWORK_NAME" 2>/dev/null \
        | awk -v mac="$mac" '$0 ~ mac {print $5}' \
        | cut -d/ -f1 \
        | head -n1
}

valid_guest_ip() {
    local addr="${1:-}"
    [[ -n "$addr" ]] || return 1
    [[ "$addr" != 127.* ]] || return 1
    [[ "$addr" != "0.0.0.0" ]] || return 1
    return 0
}

wait_for_agent_ip() {
    local domain="$1"
    local timeout_sec="${2:-300}"
    local waited=0

    while [ "$waited" -lt "$timeout_sec" ]; do
        local addr
        addr="$(_virsh domifaddr "$domain" --source agent 2>/dev/null | awk '/ipv4/ {print $4}' | cut -d/ -f1 | head -n1 || true)"
        if ! valid_guest_ip "$addr"; then
            addr=""
        fi
        if [ -z "$addr" ]; then
            addr="$(dhcp_lease_ip "$domain" || true)"
        fi
        if valid_guest_ip "$addr"; then
            printf '%s\n' "$addr"
            return 0
        fi
        sleep 5
        waited=$((waited + 5))
    done

    return 1
}

wait_for_ssh() {
    local domain="$1"
    local timeout_sec="${2:-180}"
    local waited=0
    local login_user
    login_user="$(ssh_login_user "$domain")"

    while [ "$waited" -lt "$timeout_sec" ]; do
        local addr
        addr="$(wait_for_agent_ip "$domain" 10 || true)"
        if [ -n "$addr" ]; then
            if ssh_base_args "$addr" "$login_user" true >/dev/null 2>&1; then
                return 0
            fi
        fi
        sleep 5
        waited=$((waited + 5))
    done

    return 1
}

wait_for_guest_command() {
    local domain="$1"
    local timeout_sec="$2"
    local command="$3"
    local progress_command="${4:-}"
    local progress_every_sec="${5:-30}"
    local waited=0
    local last_progress=-9999
    local login_user
    login_user="$(ssh_login_user "$domain")"

    while [ "$waited" -lt "$timeout_sec" ]; do
        local addr
        addr="$(wait_for_agent_ip "$domain" 10 || true)"
        if [ -n "$addr" ]; then
            if ssh_base_args "$addr" "$login_user" "$command" >/dev/null 2>&1; then
                return 0
            fi
            if [ -n "$progress_command" ] && [ $((waited - last_progress)) -ge "$progress_every_sec" ]; then
                last_progress="$waited"
                info "Guest progress for $domain:"
                ssh_base_args "$addr" "$login_user" "$progress_command" 2>/dev/null || true
            fi
        fi
        sleep 5
        waited=$((waited + 5))
    done

    return 1
}

ssh_base_args() {
    local host="$1"
    local login_user="${2:-player}"
    shift
    shift || true
    ssh \
        -o StrictHostKeyChecking=no \
        -o UserKnownHostsFile=/dev/null \
        -o BatchMode=yes \
        -o ConnectTimeout=5 \
        -o LogLevel=ERROR \
        -i "$SC_SSH_KEY" \
        "${login_user}@${host}" \
        "$@"
}

guest_run() {
    local domain="$1"
    shift
    wait_for_ssh "$domain" 180 >/dev/null || fail "SSH did not become ready for $domain"
    local addr
    addr="$(wait_for_agent_ip "$domain" 120)" || fail "Could not resolve IP for $domain"
    local login_user
    login_user="$(ssh_login_user "$domain")"
    if [ "$DRY_RUN" = "true" ]; then
        echo "  [DRY-RUN][SSH $domain@$addr] $*"
        return 0
    fi
    ssh_base_args "$addr" "$login_user" "$@"
}

guest_run_sudo_script() {
    local domain="$1"
    local script_file="$2"
    wait_for_ssh "$domain" 180 >/dev/null || fail "SSH did not become ready for $domain"
    local addr
    addr="$(wait_for_agent_ip "$domain" 120)" || fail "Could not resolve IP for $domain"
    local login_user
    login_user="$(ssh_login_user "$domain")"
    if [ "$DRY_RUN" = "true" ]; then
        echo "  [DRY-RUN][SSH $domain@$addr] sudo bash -s < $script_file"
        return 0
    fi
    ssh \
        -o StrictHostKeyChecking=no \
        -o UserKnownHostsFile=/dev/null \
        -o BatchMode=yes \
        -o ConnectTimeout=5 \
        -o LogLevel=ERROR \
        -i "$SC_SSH_KEY" \
        "${login_user}@${addr}" "sudo bash -s" < "$script_file"
}
