#!/bin/bash
#
# ansible-runner - Modular Ansible playbook executor
#
# Usage:
#   ansible-runner --category=system
#   ansible-runner --category=user [--user=NAME]
#   ansible-runner --playbook=/path/to/playbook.yaml
#

set -euo pipefail

# Resolve paths
SCRIPT_PATH="$0"
[[ -L "$SCRIPT_PATH" ]] && SCRIPT_PATH=$(readlink -f "$SCRIPT_PATH")
BASE_DIR=$(dirname "$SCRIPT_PATH")
APP_DIR=$(realpath "${BASE_DIR}/..")

# Configuration
CONF_FILE="${APP_DIR}/conf/gen-mdm.conf"
[[ -f "$CONF_FILE" ]] && source "$CONF_FILE"

# Defaults
MDM_REPO_PATH="${MDM_REPO_PATH:-/var/eus/mdm}"
MDM_REPO_URL="${MDM_REPO_URL:-}"
MDM_REPO_BRANCH="${MDM_REPO_BRANCH:-main}"
MDM_DEVICE_REPO_PATH="${MDM_DEVICE_REPO_PATH:-/var/eus/mdm-device}"
MDM_DEVICE_REPO_URL="${MDM_DEVICE_REPO_URL:-}"
ANSIBLE_VENV="${ANSIBLE_VENV:-${APP_DIR}/venv}"
MDM_LOG_PATH="${MDM_LOG_PATH:-/var/log/gen-mdm}"

# Lock files
LOCK_DIR="/run/gen-mdm"
LOG_FILE="${MDM_LOG_PATH}/ansible-runner.log"

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

#------------------------------------------------------------------------------
# Logging
#------------------------------------------------------------------------------

mkdir -p "$MDM_LOG_PATH"

log() {
    local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
    echo "$msg" >> "$LOG_FILE"
    echo -e "$msg"
}

log_error() {
    log "${RED}ERROR:${NC} $1"
}

log_info() {
    log "${GREEN}INFO:${NC} $1"
}

log_warn() {
    log "${YELLOW}WARN:${NC} $1"
}

#------------------------------------------------------------------------------
# Locking
#------------------------------------------------------------------------------

acquire_lock() {
    local lock_name="$1"
    local lock_file="${LOCK_DIR}/${lock_name}.lock"

    mkdir -p "$LOCK_DIR"

    if [[ -f "$lock_file" ]]; then
        local pid=$(cat "$lock_file" 2>/dev/null)
        if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
            log_warn "Another instance is running (PID: $pid)"
            return 1
        fi
        rm -f "$lock_file"
    fi

    echo $$ > "$lock_file"
    trap "rm -f '$lock_file'" EXIT
    return 0
}

#------------------------------------------------------------------------------
# Repository Management
#------------------------------------------------------------------------------

GIT_SSH="ssh -i ${APP_DIR}/conf/gen-mdm -o StrictHostKeyChecking=accept-new"

_git_sync() {
    local url="$1" local_path="$2" branch="$3"

    if [[ ! -d "$local_path/.git" ]]; then
        log_info "Cloning $(basename "$local_path")..."
        mkdir -p "$(dirname "$local_path")"
        GIT_SSH_COMMAND="$GIT_SSH" git clone --branch "$branch" "$url" "$local_path"
    else
        log_info "Updating $(basename "$local_path")..."
        GIT_SSH_COMMAND="$GIT_SSH" git -C "$local_path" fetch origin
        git -C "$local_path" checkout "$branch" 2>/dev/null \
            || git -C "$local_path" checkout -b "$branch" "origin/$branch"
        GIT_SSH_COMMAND="$GIT_SSH" git -C "$local_path" pull origin "$branch"
    fi
}

update_repository() {
    local branch="$MDM_REPO_BRANCH"
    [[ -f "${APP_DIR}/conf/branch" ]] && branch=$(cat "${APP_DIR}/conf/branch")

    if [[ -n "$MDM_REPO_URL" ]]; then
        _git_sync "$MDM_REPO_URL" "$MDM_REPO_PATH" "$branch"
    else
        log_warn "MDM_REPO_URL not configured, skipping common repo update"
    fi

    if [[ -n "$MDM_DEVICE_REPO_URL" ]]; then
        _git_sync "$MDM_DEVICE_REPO_URL" "$MDM_DEVICE_REPO_PATH" "$branch"
    else
        log_warn "MDM_DEVICE_REPO_URL not configured, skipping device repo update"
    fi
}

#------------------------------------------------------------------------------
# Ansible Execution
#------------------------------------------------------------------------------

run_ansible() {
    local playbook="$1"
    shift
    local extra_vars=("$@")

    if [[ ! -f "$playbook" ]]; then
        log_warn "Playbook not found: $playbook"
        return 0
    fi

    log_info "Running playbook: $playbook"

    local ansible_cmd="${ANSIBLE_VENV}/bin/ansible-playbook"
    if [[ ! -x "$ansible_cmd" ]]; then
        ansible_cmd="ansible-playbook"
    fi

    local cmd=("$ansible_cmd" "$playbook")
    for var in "${extra_vars[@]}"; do
        cmd+=("-e" "$var")
    done

    cd "$MDM_REPO_PATH"
    "${cmd[@]}" 2>&1 | tee -a "$LOG_FILE"
}

#------------------------------------------------------------------------------
# Category: System
#------------------------------------------------------------------------------

run_system_playbooks() {
    log_info "Running system playbooks..."

    acquire_lock "system" || return 1

    update_repository

    if [[ ! -d "$MDM_REPO_PATH" ]]; then
        log_warn "MDM_REPO_PATH ($MDM_REPO_PATH) does not exist — device not enrolled yet, skipping"
        return 0
    fi

    cd "$MDM_REPO_PATH"

    # Main provision playbook
    if [[ -f "provision.yaml" ]]; then
        run_ansible "provision.yaml"
    fi

    # Host-specific playbook — check device repo first, fall back to common repo
    local hostname=$(hostname -f)
    local device_playbook="${MDM_DEVICE_REPO_PATH}/${hostname}.yaml"
    local common_playbook="${MDM_REPO_PATH}/custom/H/${hostname}.yaml"
    if [[ -f "$device_playbook" ]]; then
        run_ansible "$device_playbook"
    elif [[ -f "$common_playbook" ]]; then
        run_ansible "$common_playbook"
    fi

    log_info "System playbooks complete"
}

#------------------------------------------------------------------------------
# Category: User
#------------------------------------------------------------------------------

get_human_users() {
    # Get users with UID >= 1000 and valid shell
    getent passwd | awk -F: '$3 >= 1000 && $3 < 65534 && $7 !~ /nologin|false/ {print $1}'
}

run_user_playbooks() {
    local target_user="${1:-}"

    log_info "Running user playbooks..."

    acquire_lock "user" || return 1

    update_repository

    if [[ ! -d "$MDM_REPO_PATH" ]]; then
        log_warn "MDM_REPO_PATH ($MDM_REPO_PATH) does not exist — device not enrolled yet, skipping"
        return 0
    fi

    cd "$MDM_REPO_PATH"

    local users=()
    if [[ -n "$target_user" ]]; then
        users=("$target_user")
    else
        mapfile -t users < <(get_human_users)
    fi

    for user in "${users[@]}"; do
        log_info "Processing user: $user"

        # Main user provision playbook
        if [[ -f "provision-user.yaml" ]]; then
            run_ansible "provision-user.yaml" "USER=${user}"
        fi

        # User-specific playbook
        local user_playbook="custom/U/${user}.yaml"
        if [[ -f "$user_playbook" ]]; then
            run_ansible "$user_playbook" "USER=${user}"
        fi

        # User-host specific playbook
        local hostname=$(hostname -f)
        local user_host_playbook="custom/U-H/${user}-${hostname}.yaml"
        if [[ -f "$user_host_playbook" ]]; then
            run_ansible "$user_host_playbook" "USER=${user}"
        fi
    done

    log_info "User playbooks complete"
}

#------------------------------------------------------------------------------
# Single Playbook
#------------------------------------------------------------------------------

run_single_playbook() {
    local playbook="$1"

    if [[ ! -f "$playbook" ]]; then
        log_error "Playbook not found: $playbook"
        return 1
    fi

    # Security: ensure playbook is within allowed paths
    local real_playbook=$(realpath "$playbook")
    if [[ ! "$real_playbook" =~ ^${MDM_REPO_PATH} ]]; then
        log_error "Playbook must be within MDM repository"
        return 1
    fi

    acquire_lock "playbook" || return 1

    log_info "Running single playbook: $playbook"
    run_ansible "$playbook"
}

#------------------------------------------------------------------------------
# Main
#------------------------------------------------------------------------------

show_help() {
    cat << EOF
ansible-runner - Modular Ansible playbook executor

Usage:
  ansible-runner --category=system
  ansible-runner --category=user [--user=NAME]
  ansible-runner --playbook=/path/to/playbook.yaml

Options:
  --category=CATEGORY   Run playbooks by category (system, user)
  --user=NAME           Target specific user (with --category=user)
  --playbook=PATH       Run a single specific playbook
  --help                Show this help

Categories:
  system    Run provision.yaml + host-specific playbooks
  user      Run user-specific playbooks for all or specified user
EOF
}

# Parse arguments
CATEGORY=""
USER=""
PLAYBOOK=""

while [[ $# -gt 0 ]]; do
    case $1 in
        --category=*)
            CATEGORY="${1#*=}"
            shift
            ;;
        --user=*)
            USER="${1#*=}"
            shift
            ;;
        --playbook=*)
            PLAYBOOK="${1#*=}"
            shift
            ;;
        --help|-h)
            show_help
            exit 0
            ;;
        *)
            log_error "Unknown option: $1"
            show_help
            exit 1
            ;;
    esac
done

# Execute
if [[ -n "$PLAYBOOK" ]]; then
    run_single_playbook "$PLAYBOOK"
elif [[ -n "$CATEGORY" ]]; then
    case "$CATEGORY" in
        system)
            run_system_playbooks
            ;;
        user)
            run_user_playbooks "$USER"
            ;;
        *)
            log_error "Unknown category: $CATEGORY"
            show_help
            exit 1
            ;;
    esac
else
    log_error "No category or playbook specified"
    show_help
    exit 1
fi