#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
LAB_DIR="${REPO_ROOT}/.lab"
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/jeannie-unit.XXXXXX")"
BACKUP_DIR="${TMP_DIR}/backup"
FAILURES=0
LAB_STATE_FILES="pimox-workers.tsv cluster-workers.auto.tfvars.json manual-workers.tsv"

mkdir -p "${BACKUP_DIR}" "${LAB_DIR}" "${TMP_DIR}/bin"

cleanup() {
    for file in ${LAB_STATE_FILES}; do
        rm -f "${LAB_DIR}/${file}"
        if [[ -f "${BACKUP_DIR}/${file}" ]]; then
            cp "${BACKUP_DIR}/${file}" "${LAB_DIR}/${file}"
        fi
    done
    rm -rf "${TMP_DIR}"
}
trap cleanup EXIT

for file in ${LAB_STATE_FILES}; do
    if [[ -f "${LAB_DIR}/${file}" ]]; then
        cp "${LAB_DIR}/${file}" "${BACKUP_DIR}/${file}"
    fi
done

cat >"${TMP_DIR}/bin/tofu" <<'EOF'
#!/usr/bin/env bash
exit 1
EOF
chmod +x "${TMP_DIR}/bin/tofu"
export PATH="${TMP_DIR}/bin:${PATH}"

cat >"${LAB_DIR}/pimox-workers.tsv" <<'EOF'
pimox01	192.168.100.66	jv	pimox-worker-01	/home/jv/.ssh/id_ed25519
pimox02	192.168.100.67	jv	pimox-worker-02	/home/jv/.ssh/id_ed25519
pimox03	192.168.100.76	jv	pimox-worker-03	/home/jv/.ssh/id_ed25519
EOF

cat >"${LAB_DIR}/manual-workers.tsv" <<'EOF'
manual01	192.168.100.99	jv	manual-worker-01	/home/jv/.ssh/id_ed25519
EOF

cat >"${LAB_DIR}/cluster-workers.auto.tfvars.json" <<'EOF'
{
  "worker_nodes": {
    "pimox01": {
      "host": "192.168.100.66",
      "user": "jv",
      "node_name": "pimox-worker-01",
      "ssh_private_key_path": "/home/jv/.ssh/id_ed25519"
    },
    "pimox02": {
      "host": "192.168.100.67",
      "user": "jv",
      "node_name": "pimox-worker-02",
      "ssh_private_key_path": "/home/jv/.ssh/id_ed25519"
    },
    "pimox03": {
      "host": "192.168.100.76",
      "user": "jv",
      "node_name": "pimox-worker-03",
      "ssh_private_key_path": "/home/jv/.ssh/id_ed25519"
    },
    "manual01": {
      "host": "192.168.100.99",
      "user": "jv",
      "node_name": "manual-worker-01",
      "ssh_private_key_path": "/home/jv/.ssh/id_ed25519"
    }
  }
}
EOF

export JEANNIE_LIBRARY_MODE=true
# shellcheck disable=SC1091
source "${REPO_ROOT}/jeannie"

pass() {
    printf 'ok    %s\n' "$1"
}

fail() {
    printf 'fail  %s\n' "$1"
    FAILURES=$((FAILURES + 1))
}

assert_eq() {
    local label="$1"
    local expected="$2"
    local actual="$3"

    if [[ "${actual}" == "${expected}" ]]; then
        pass "${label}"
    else
        fail "${label}: expected '${expected}', got '${actual}'"
    fi
}

assert_contains() {
    local label="$1"
    local needle="$2"
    local haystack="$3"

    if grep -Fq "${needle}" <<<"${haystack}"; then
        pass "${label}"
    else
        fail "${label}: expected output to contain '${needle}'"
    fi
}

assert_command_success() {
    local label="$1"
    shift

    if "$@" >/dev/null 2>&1; then
        pass "${label}"
    else
        fail "${label}: command failed"
    fi
}

assert_command_failure_contains() {
    local label="$1"
    local needle="$2"
    shift 2
    local output
    local status

    set +e
    output="$("$@" 2>&1)"
    status=$?
    set -e
    if [[ "${status}" -eq 0 ]]; then
        fail "${label}: command unexpectedly passed"
        return 0
    fi
    assert_contains "${label}" "${needle}" "${output}"
}

test_pimox_worker_count_detects_generated_topology() {
    unset LAB_PIMOX_WORKER_COUNT
    assert_eq "detects 3 generated Pimox workers" "3" "$(pimox_worker_count_default)"
}

test_configured_worker_count_cannot_hide_existing_workers() {
    LAB_PIMOX_WORKER_COUNT=1
    assert_eq "effective count keeps detected 3 workers" "3" "$(pimox_worker_count_effective)"
    unset LAB_PIMOX_WORKER_COUNT
}

test_configured_worker_count_can_expand_target_set() {
    LAB_PIMOX_WORKER_COUNT=4
    assert_eq "effective count allows explicit expansion" "4" "$(pimox_worker_count_effective)"
    unset LAB_PIMOX_WORKER_COUNT
}

test_cluster_worker_targets_include_pimox_and_manual_workers() {
    CLUSTER_WORKER_TARGETS=()
    cluster_worker_targets
    local targets
    targets="$(printf '%s\n' "${CLUSTER_WORKER_TARGETS[@]}" | sort | paste -sd ',' -)"
    assert_eq "cluster targets include generated Pimox workers" "jv@192.168.100.66,jv@192.168.100.67,jv@192.168.100.76,jv@192.168.100.99" "${targets}"
}

test_prepare_cluster_worker_var_file_merges_manual_and_pimox_workers() {
    unset LAB_INCLUDE_RASPBERRY_WORKER
    LAB_INCLUDE_MANUAL_WORKERS=true
    LAB_PIMOX_WORKER_NODE_LABELS_JSON='{"homelab.dev/node-role":"app","homelab.dev/storage":"ssd"}'
    LAB_MANUAL_WORKER_NODE_LABELS_JSON='{"homelab.dev/node-role":"manual","homelab.dev/storage":"custom"}'
    prepare_cluster_worker_var_file false
    python3 - "${LAB_DIR}/cluster-workers.auto.tfvars.json" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as handle:
    document = json.load(handle)
nodes = document["worker_nodes"]
labels = document["worker_node_labels"]
expected = {"pimox01", "pimox02", "pimox03", "manual01"}
if set(nodes) != expected:
    raise SystemExit(f"worker keys mismatch: {sorted(nodes)}")
if nodes["manual01"]["node_name"] != "manual-worker-01":
    raise SystemExit("manual worker node name was not preserved")
if labels["pimox01"]["homelab.dev/storage"] != "ssd":
    raise SystemExit("pimox labels were not applied")
if labels["manual01"]["homelab.dev/node-role"] != "manual":
    raise SystemExit("manual labels were not applied")
PY
    pass "worker var file merges manual and Pimox workers"
    unset LAB_INCLUDE_MANUAL_WORKERS LAB_PIMOX_WORKER_NODE_LABELS_JSON LAB_MANUAL_WORKER_NODE_LABELS_JSON LAB_CLUSTER_VAR_FILE LAB_INCLUDE_RASPBERRY_WORKER
}

test_prepare_cluster_worker_var_file_excludes_manual_by_default() {
    unset LAB_INCLUDE_RASPBERRY_WORKER LAB_INCLUDE_MANUAL_WORKERS
    prepare_cluster_worker_var_file false
    python3 - "${LAB_DIR}/cluster-workers.auto.tfvars.json" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as handle:
    document = json.load(handle)
nodes = document["worker_nodes"]
if "manual01" in nodes:
    raise SystemExit("manual worker was included without LAB_INCLUDE_MANUAL_WORKERS=true")
if set(nodes) != {"pimox01", "pimox02", "pimox03"}:
    raise SystemExit(f"unexpected worker keys: {sorted(nodes)}")
PY
    pass "worker var file excludes manual workers by default"
    unset LAB_CLUSTER_VAR_FILE LAB_INCLUDE_RASPBERRY_WORKER LAB_INCLUDE_MANUAL_WORKERS
}

test_prepare_cluster_worker_var_file_can_include_raspberry_worker() {
    LAB_INCLUDE_RASPBERRY_WORKER=true
    LAB_RASPBERRY_HOST=192.168.100.89
    LAB_RASPBERRY_NODE_NAME=raspberry
    prepare_cluster_worker_var_file false
    python3 - "${LAB_DIR}/cluster-workers.auto.tfvars.json" <<'PY'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as handle:
    document = json.load(handle)
node = document["worker_nodes"].get("raspberrypi")
if not node:
    raise SystemExit("raspberrypi worker was not included")
if node["host"] != "192.168.100.89" or node["node_name"] != "raspberry":
    raise SystemExit(f"unexpected raspberry worker: {node}")
PY
    pass "worker var file can include Raspberry Pi worker"
    unset LAB_INCLUDE_RASPBERRY_WORKER LAB_RASPBERRY_HOST LAB_RASPBERRY_NODE_NAME LAB_CLUSTER_VAR_FILE
}

test_prepare_cluster_worker_var_file_rebuilds_static_pimox_spec() {
    rm -f "${LAB_DIR}/pimox-workers.tsv" "${LAB_DIR}/manual-workers.tsv" "${LAB_DIR}/cluster-workers.auto.tfvars.json"
    LAB_PIMOX_WORKER_NETWORK_MODE=static
    LAB_PIMOX_WORKER_COUNT=2
    LAB_PIMOX_WORKER_STATIC_IPS="192.168.100.66 192.168.100.67"
    LAB_PIMOX_WORKER_USER=jv
    LAB_PIMOX_WORKER_SSH_KEY_PATH=/home/jv/.ssh/id_ed25519
    unset LAB_INCLUDE_RASPBERRY_WORKER
    prepare_cluster_worker_var_file false
    python3 - "${LAB_DIR}/pimox-workers.tsv" "${LAB_DIR}/cluster-workers.auto.tfvars.json" <<'PY'
import json
import sys

spec_file, var_file = sys.argv[1:3]
with open(spec_file, encoding="utf-8") as handle:
    specs = [line.rstrip("\n").split("\t") for line in handle if line.strip()]
if specs != [
    ["pimox01", "192.168.100.66", "jv", "pimox-worker-01", "/home/jv/.ssh/id_ed25519"],
    ["pimox02", "192.168.100.67", "jv", "pimox-worker-02", "/home/jv/.ssh/id_ed25519"],
]:
    raise SystemExit(f"unexpected static worker specs: {specs}")
with open(var_file, encoding="utf-8") as handle:
    document = json.load(handle)
nodes = document.get("worker_nodes") or {}
if set(nodes) != {"pimox01", "pimox02"}:
    raise SystemExit(f"worker_nodes was not rebuilt from static spec: {nodes}")
PY
    pass "worker var file rebuilds missing static Pimox spec"
    unset LAB_PIMOX_WORKER_NETWORK_MODE LAB_PIMOX_WORKER_COUNT LAB_PIMOX_WORKER_STATIC_IPS LAB_PIMOX_WORKER_USER LAB_PIMOX_WORKER_SSH_KEY_PATH LAB_CLUSTER_VAR_FILE LAB_INCLUDE_RASPBERRY_WORKER
}

test_report_renderer_hides_ok_links() {
    local output
    output="$(
        printf 'ok\tArea\tHealthy\tok\tdetail\tfix\tgraph\tquery\nwarn\tArea\tWarning\tneeds attention\tdetail\tfix\tgraph\tquery\n' |
            "${REPO_ROOT}/scripts/report-render" --title "Unit Report" --only all
    )"
    if grep -A4 'Healthy' <<<"${output}" | grep -Eq 'graph:|query:'; then
        fail "report renderer hides graph/query for ok rows"
    else
        pass "report renderer hides graph/query for ok rows"
    fi
}

test_report_renderer_problems_only_hides_ok_rows() {
    local output
    output="$(
        printf 'ok\tArea\tHealthy\tok\t\t\t\t\nwarn\tArea\tWarning\tneeds attention\tdetail\tfix\tgraph\tquery\n' |
            "${REPO_ROOT}/scripts/report-render" --title "Unit Report" --only problems
    )"
    if grep -Fq 'Healthy' <<<"${output}"; then
        fail "report renderer problems-only hides ok rows"
    else
        pass "report renderer problems-only hides ok rows"
    fi
    assert_contains "report renderer problems-only keeps warning" "Warning" "${output}"
    assert_contains "report renderer problems-only prints next fix" "Next Fix" "${output}"
}

test_report_renderer_empty_problems_message() {
    local output
    output="$(
        printf 'ok\tArea\tHealthy\tok\t\t\t\t\n' |
            "${REPO_ROOT}/scripts/report-render" --title "Unit Report" --only problems
    )"
    assert_contains "report renderer empty problems message" "No failing or warning items." "${output}"
}

test_inventory_validator_accepts_current_inventory() {
    assert_command_success "inventory validator accepts repo inventory" "${REPO_ROOT}/scripts/validate-homelab-inventory" "${REPO_ROOT}/homelab.yml"
}

test_inventory_validator_rejects_ip_drift() {
    local inventory="${TMP_DIR}/invalid-homelab.yml"
    python3 - "${REPO_ROOT}/homelab.yml" "${inventory}" <<'PY'
import sys

source, target = sys.argv[1:3]
text = open(source, encoding="utf-8").read()
text = text.replace("lan_ip: 192.168.100.73", "lan_ip: not-an-ip", 1)
with open(target, "w", encoding="utf-8") as handle:
    handle.write(text)
PY
    assert_command_failure_contains "inventory validator rejects invalid Debian LAN IP" "hosts.debian.lan_ip is not a valid IP" "${REPO_ROOT}/scripts/validate-homelab-inventory" "${inventory}"
}

test_heal_cooldown_escalates_to_diagnostics() {
    local status_json="${TMP_DIR}/heal-status.json"
    local state_json="${TMP_DIR}/heal-state.json"
    local output

    cat >"${status_json}" <<'EOF'
{
  "rows": [
    {
      "area": "Pimox And Cluster",
      "check": "Kubernetes nodes Ready",
      "status": "fail",
      "summary": "pimox-worker-01 NotReady",
      "detail": "pimox-worker-01 NotReady"
    },
    {
      "area": "Platform And Apps",
      "check": "Traefik deployment",
      "status": "fail",
      "summary": "deployment unavailable"
    }
  ]
}
EOF
    python3 - "${state_json}" <<'PY'
import json
import sys
import time

with open(sys.argv[1], "w", encoding="utf-8") as handle:
    json.dump(
        {
            "failed_actions": {
                "./jeannie workers restart 1": {
                    "failed_at": int(time.time()),
                    "finding": "Pimox And Cluster / Kubernetes nodes Ready",
                }
            }
        },
        handle,
    )
PY

    output="$(
        LAB_HEAL_STATE_FILE="${state_json}" LAB_HEAL_COOLDOWN_SECONDS=3600 \
            "${REPO_ROOT}/scripts/heal" plan --status-json "${status_json}"
    )"
    assert_contains "heal cooldown removes auto target" "Next heal target: none" "${output}"
    assert_contains "heal cooldown escalates diagnostics" "next diagnostic command: ./jeannie doctor-cluster --details" "${output}"
}

test_pimox_worker_static_ip_selection() {
    assert_eq "pimox worker static IP index 1" "192.168.100.66" "$(pimox_worker_static_ip 1 "192.168.100.66 192.168.100.67")"
    assert_eq "pimox worker static IP index 2" "192.168.100.67" "$(pimox_worker_static_ip 2 "192.168.100.66,192.168.100.67")"
    if pimox_worker_static_ip 3 "192.168.100.66 192.168.100.67" >/dev/null; then
        fail "pimox worker static IP missing index should fail"
    else
        pass "pimox worker static IP missing index should fail"
    fi
}

test_pimox_guest_exec_exitcode_parser() {
    assert_eq "pimox guest exec parses success exitcode" "0" "$(pimox_guest_exec_exitcode '{"exitcode":0,"exited":1}')"
    assert_eq "pimox guest exec parses failed exitcode" "1" "$(pimox_guest_exec_exitcode '{"exitcode":1,"exited":1}')"
    assert_command_failure_contains "pimox guest exec rejects invalid JSON" "" pimox_guest_exec_exitcode 'not json'
}

test_pimox_worker_net0_config_defaults_to_deterministic_mac() {
    unset LAB_PIMOX_WORKER_MAC_MODE
    assert_eq "pimox worker net0 defaults to deterministic MAC" "virtio=02:68:10:00:23:32,bridge=vmbr0" "$(pimox_worker_net0_config 9010 vmbr0)"
    LAB_PIMOX_WORKER_MAC_MODE=auto
    assert_eq "pimox worker net0 auto MAC opt-in" "virtio,bridge=vmbr0" "$(pimox_worker_net0_config 9010 vmbr0)"
    unset LAB_PIMOX_WORKER_MAC_MODE
}

test_pimox_worker_count_detects_generated_topology
test_configured_worker_count_cannot_hide_existing_workers
test_configured_worker_count_can_expand_target_set
test_cluster_worker_targets_include_pimox_and_manual_workers
test_prepare_cluster_worker_var_file_merges_manual_and_pimox_workers
test_prepare_cluster_worker_var_file_excludes_manual_by_default
test_prepare_cluster_worker_var_file_can_include_raspberry_worker
test_prepare_cluster_worker_var_file_rebuilds_static_pimox_spec
test_report_renderer_hides_ok_links
test_report_renderer_problems_only_hides_ok_rows
test_report_renderer_empty_problems_message
test_inventory_validator_accepts_current_inventory
test_inventory_validator_rejects_ip_drift
test_heal_cooldown_escalates_to_diagnostics
test_pimox_worker_static_ip_selection
test_pimox_guest_exec_exitcode_parser
test_pimox_worker_net0_config_defaults_to_deterministic_mac

if ((FAILURES > 0)); then
    printf '\n%s unit test(s) failed.\n' "${FAILURES}" >&2
    exit 1
fi

printf '\nAll jeannie unit tests passed.\n'
