#!/usr/bin/env bash
#
# Train the assessment + smoking + alcohol extra-information experiment.
#
# Fixed model:
#   TrajMixer + all_future + relative + Weibull + timed disease history + sex
#
# Extra information:
#   - 65 routine assessment/body/laboratory variables
#   - smoking
#   - alcohol
#   - BMI is already included in the assessment variables
#   - continuous values use train-split RobustScaler statistics
#
# A6000 48 GB default:
#   batch_size=256
#
# Each task uses one GPU. Tasks assigned to the same GPU run sequentially;
# different GPUs run in parallel.
#
# Examples:
#   bash train_extra_info_assessment_all_multiseed_linux.sh --gpus 0
#   bash train_extra_info_assessment_all_multiseed_linux.sh --gpus 0,1,2
#   bash train_extra_info_assessment_all_multiseed_linux.sh \
#       --gpus 0 --seeds 42 --dry-run
#

set -uo pipefail

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"

GPU_CSV=""
SEED_CSV="42,43,44"
NUM_WORKERS=4
BATCH_SIZE=256
PYTHON_BIN="${PYTHON_BIN:-python}"
CAMPAIGN_NAME="extra_info_assessment_smoking_alcohol_robust_multiseed"
DRY_RUN=0

ENTRYPOINT="$SCRIPT_DIR/train_all_future.py"
EXTRA_INFO_TYPES_FILE="$SCRIPT_DIR/extra_info_types_assessment_smoking_alcohol.txt"

usage() {
    cat <<'EOF'
Usage:
  bash train_extra_info_assessment_all_multiseed_linux.sh \
      --gpus GPU_LIST [options]

Required:
  --gpus LIST            Comma-separated GPU ids, for example 0,1,2.

Options:
  --seeds LIST           Comma-separated seeds (default: 42,43,44).
  --batch-size N         Batch size per task (default: 256).
  --num-workers N        DataLoader workers per task (default: 4).
  --python PATH          Python executable (default: $PYTHON_BIN or python).
  --campaign NAME        Output campaign name.
  --dry-run              Print commands without creating files or training.
  -h, --help             Show this help message.

Fixed experiment settings:
  architecture           traj_mixer_v5
  target                 all_future
  time mode              relative
  distribution           weibull
  disease history        timed
  sex                    enabled by the model
  extra information      extra_info_types_assessment_smoking_alcohol.txt
  continuous scaling     robust (training-subset median/IQR)

A6000 48 GB default:
  batch_size             256

Outputs:
  runs/<campaign>/seed_<seed>/traj_mixer_v5/...
  batch_logs/<campaign>/seed_<seed>/assessment_smoking_alcohol.log
EOF
}

while (($# > 0)); do
    case "$1" in
        --gpus)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --gpus requires a value." >&2
                exit 2
            }
            GPU_CSV="$2"
            shift 2
            ;;
        --seeds)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --seeds requires a value." >&2
                exit 2
            }
            SEED_CSV="$2"
            shift 2
            ;;
        --batch-size)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --batch-size requires a value." >&2
                exit 2
            }
            BATCH_SIZE="$2"
            shift 2
            ;;
        --num-workers)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --num-workers requires a value." >&2
                exit 2
            }
            NUM_WORKERS="$2"
            shift 2
            ;;
        --python)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --python requires a value." >&2
                exit 2
            }
            PYTHON_BIN="$2"
            shift 2
            ;;
        --campaign)
            [[ $# -ge 2 ]] || {
                echo "ERROR: --campaign requires a value." >&2
                exit 2
            }
            CAMPAIGN_NAME="$2"
            shift 2
            ;;
        --dry-run)
            DRY_RUN=1
            shift
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            echo "ERROR: unknown argument: $1" >&2
            usage >&2
            exit 2
            ;;
    esac
done

[[ -n "$GPU_CSV" ]] || {
    echo "ERROR: --gpus is required." >&2
    usage >&2
    exit 2
}
[[ -n "$SEED_CSV" ]] || {
    echo "ERROR: --seeds must not be empty." >&2
    exit 2
}
[[ "$BATCH_SIZE" =~ ^[1-9][0-9]*$ ]] || {
    echo "ERROR: --batch-size must be a positive integer." >&2
    exit 2
}
[[ "$NUM_WORKERS" =~ ^[0-9]+$ ]] || {
    echo "ERROR: --num-workers must be a non-negative integer." >&2
    exit 2
}
[[ "$CAMPAIGN_NAME" =~ ^[A-Za-z0-9._-]+$ ]] || {
    echo "ERROR: --campaign may contain only letters, numbers, ., _, and -." >&2
    exit 2
}
[[ -f "$ENTRYPOINT" ]] || {
    echo "ERROR: missing training entrypoint: $ENTRYPOINT" >&2
    exit 2
}
[[ -f "$EXTRA_INFO_TYPES_FILE" ]] || {
    echo "ERROR: missing extra-info file: $EXTRA_INFO_TYPES_FILE" >&2
    exit 2
}
command -v "$PYTHON_BIN" >/dev/null 2>&1 || {
    echo "ERROR: Python executable not found: $PYTHON_BIN" >&2
    exit 2
}

IFS=',' read -r -a GPU_IDS <<< "$GPU_CSV"
declare -A SEEN_GPUS=()
for gpu in "${GPU_IDS[@]}"; do
    [[ -n "$gpu" && "$gpu" =~ ^[A-Za-z0-9._:-]+$ ]] || {
        echo "ERROR: invalid GPU id: $gpu" >&2
        exit 2
    }
    [[ -z "${SEEN_GPUS[$gpu]+x}" ]] || {
        echo "ERROR: duplicate GPU id: $gpu" >&2
        exit 2
    }
    SEEN_GPUS["$gpu"]=1
done

IFS=',' read -r -a SEEDS <<< "$SEED_CSV"
declare -A SEEN_SEEDS=()
for seed in "${SEEDS[@]}"; do
    [[ "$seed" =~ ^[0-9]+$ ]] || {
        echo "ERROR: invalid seed: $seed" >&2
        exit 2
    }
    [[ -z "${SEEN_SEEDS[$seed]+x}" ]] || {
        echo "ERROR: duplicate seed: $seed" >&2
        exit 2
    }
    SEEN_SEEDS["$seed"]=1
done

RUNS_ROOT="$SCRIPT_DIR/runs/$CAMPAIGN_NAME"
LOG_ROOT="$SCRIPT_DIR/batch_logs/$CAMPAIGN_NAME"
if ((!DRY_RUN)); then
    mkdir -p "$RUNS_ROOT" "$LOG_ROOT"
fi

print_command() {
    printf '%q ' "$@"
    printf '\n'
}

run_job() {
    local seed="$1"
    local gpu="$2"
    local seed_runs_root="$RUNS_ROOT/seed_$seed"
    local seed_log_root="$LOG_ROOT/seed_$seed"
    local log_file="$seed_log_root/assessment_smoking_alcohol.log"
    local -a command=(
        "$PYTHON_BIN"
        -u
        "$ENTRYPOINT"
        --runs_root "$seed_runs_root"
        --seed "$seed"
        --batch_size "$BATCH_SIZE"
        --num_workers "$NUM_WORKERS"
        --device cuda
        --model_architecture traj_mixer_v5
        --time_mode relative
        --dist_mode weibull
        --disease_history_mode timed
        --extra_info_types_file "$EXTRA_INFO_TYPES_FILE"
    )

    if ((!DRY_RUN)); then
        mkdir -p "$seed_runs_root" "$seed_log_root"
    fi

    echo "[$(date '+%F %T')] START seed=$seed gpu=$gpu batch=$BATCH_SIZE"
    echo "  log=$log_file"
    if ((DRY_RUN)); then
        printf '  CUDA_VISIBLE_DEVICES=%q ' "$gpu"
        print_command "${command[@]}"
        return 0
    fi

    if CUDA_VISIBLE_DEVICES="$gpu" PYTHONUNBUFFERED=1 \
        "${command[@]}" >"$log_file" 2>&1; then
        echo "[$(date '+%F %T')] DONE  seed=$seed gpu=$gpu"
        return 0
    else
        local exit_code=$?
        echo "[$(date '+%F %T')] FAIL seed=$seed gpu=$gpu exit=$exit_code" >&2
        echo "  See: $log_file" >&2
        return "$exit_code"
    fi
}

worker() {
    local slot="$1"
    local gpu="${GPU_IDS[$slot]}"
    local seed_index
    local failed=0

    for ((seed_index = slot; seed_index < ${#SEEDS[@]}; seed_index += ${#GPU_IDS[@]})); do
        run_job "${SEEDS[$seed_index]}" "$gpu" || failed=1
    done
    return "$failed"
}

echo "Campaign: $CAMPAIGN_NAME"
echo "Seeds: ${SEEDS[*]}"
echo "GPUs: ${GPU_IDS[*]}"
echo "Batch size: $BATCH_SIZE"
echo "Extra-info file: $EXTRA_INFO_TYPES_FILE"
echo "Total tasks: ${#SEEDS[@]}"
echo "Runs root: $RUNS_ROOT"
echo "Log root: $LOG_ROOT"
if command -v nvidia-smi >/dev/null 2>&1; then
    echo "Selected GPU inventory:"
    for gpu in "${GPU_IDS[@]}"; do
        nvidia-smi \
            --id="$gpu" \
            --query-gpu=index,name,memory.total \
            --format=csv,noheader \
            2>/dev/null || true
    done
fi
echo

declare -a WORKER_PIDS=()
for ((slot = 0; slot < ${#GPU_IDS[@]}; slot++)); do
    worker "$slot" &
    WORKER_PIDS+=("$!")
done

overall_status=0
for pid in "${WORKER_PIDS[@]}"; do
    wait "$pid" || overall_status=1
done

if ((overall_status != 0)); then
    echo "One or more training tasks failed. Inspect logs under: $LOG_ROOT" >&2
    exit 1
fi

if ((DRY_RUN)); then
    echo "Dry run completed successfully."
else
    echo "All assessment + smoking + alcohol tasks completed successfully."
fi
