#!/usr/bin/env bash
# =============================================================================
# onx-snapshot-restore — Restore a cpmove or onoxsoft snapshot into an account
#
# Purpose:
#   Inverse of onx-snapshot-create. Detects the on-disk layout (cpmove-<user>/
#   subtree vs native), maps cpmove paths to onoxsoft conventions, then
#   invokes onx-backup-restore internally for the heavy lifting where possible.
#
# Input (stdin JSON):
#   {
#     "snapshot_path":   "/var/backups/onoxsoft/cpmove-onx_acme01-...tar.gz",
#     "target_username": "onx_acme01",
#     "format":          "cpmove"          -- "cpmove" | "onoxsoft" | "auto"
#   }
#
# Output (stdout JSON):
#   {
#     "restored": true,
#     "format":   "cpmove",
#     "account_created": false,
#     "items": {
#       "home":  N,
#       "dbs":   N,
#       "mail":  N,
#       "dns":   N
#     },
#     "duration_seconds": N
#   }
#
# Deployed to: /usr/local/onoxsoft/bin/onx-snapshot-restore
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

readonly BACKUP_ROOT_ALLOWED="/var/backups"

require_root

if [[ "${MOCK_MODE}" != "1" ]]; then
    command -v tar       >/dev/null 2>&1 || onx_die 2 "tar not found"
    command -v rsync     >/dev/null 2>&1 || onx_die 2 "rsync not found"
    command -v sha256sum >/dev/null 2>&1 || onx_die 2 "sha256sum not found"
fi

onx_json_input

SNAPSHOT_PATH=$(onx_json_field "snapshot_path")
TARGET_USERNAME=$(onx_json_field "target_username")
FORMAT=$(onx_json_field "format" "auto")

[[ -n "${SNAPSHOT_PATH}" ]] || onx_die 1 "snapshot_path is required"
onx_validate_username "${TARGET_USERNAME}"

case "${FORMAT}" in
    cpmove|onoxsoft|auto) : ;;
    *) onx_die 1 "format must be one of 'cpmove' | 'onoxsoft' | 'auto' (got '${FORMAT}')" ;;
esac

SNAP_REAL="$(realpath -m "${SNAPSHOT_PATH}" 2>/dev/null || printf '%s' "${SNAPSHOT_PATH}")"
case "${SNAP_REAL}" in
    ${BACKUP_ROOT_ALLOWED}|${BACKUP_ROOT_ALLOWED}/*) : ;;
    *) onx_die 1 "snapshot_path must be under ${BACKUP_ROOT_ALLOWED}/" ;;
esac

[[ -f "${SNAP_REAL}" ]] || onx_die 2 "snapshot file not found: ${SNAP_REAL}"

# Checksum (best-effort like onx-backup-restore)
SHA_FILE="${SNAP_REAL}.sha256"
if [[ -f "${SHA_FILE}" ]]; then
    EXPECTED_SHA=$(awk '{print $1}' "${SHA_FILE}")
    ACTUAL_SHA=$(sha256sum "${SNAP_REAL}" | awk '{print $1}')
    [[ "${EXPECTED_SHA}" == "${ACTUAL_SHA}" ]] \
        || onx_die 2 "checksum mismatch: expected=${EXPECTED_SHA} actual=${ACTUAL_SHA}"
fi

WORK_DIR="/tmp/onx-snapshot-restore-${TARGET_USERNAME}-$$"
mkdir -p "${WORK_DIR}" || onx_die 3 "cannot create work dir"
chmod 0700 "${WORK_DIR}"

trap 'onx_rollback_run' ERR
onx_rollback_register "rm -rf '${WORK_DIR}' 2>/dev/null || true"

START_TS=$(date +%s)
onx_log "snapshot-restore start: src=${SNAP_REAL} target=${TARGET_USERNAME} fmt=${FORMAT}"

# ── Extract snapshot ─────────────────────────────────────────────────────────
if [[ "${MOCK_MODE}" == "1" ]]; then
    # Mock: synthesise a cpmove-* tree
    SRC_USER="${TARGET_USERNAME}"
    mkdir -p "${WORK_DIR}/cpmove-${SRC_USER}/homedir" \
             "${WORK_DIR}/cpmove-${SRC_USER}/mysql"
    printf 'MOCK\n' > "${WORK_DIR}/cpmove-${SRC_USER}/homedir/.mock"
    printf -- '-- mock\n' > "${WORK_DIR}/cpmove-${SRC_USER}/mysql/mockdb.sql"
    cat > "${WORK_DIR}/cpmove-${SRC_USER}/main" <<EOF
USER=${SRC_USER}
DOMAIN=mock.example.com
EOF
else
    tar -xzf "${SNAP_REAL}" -C "${WORK_DIR}" 2>/dev/null \
        || onx_die 3 "snapshot extract failed"
fi

# ── Auto-detect format ───────────────────────────────────────────────────────
CPMOVE_DIR=""
if [[ "${FORMAT}" == "auto" || "${FORMAT}" == "cpmove" ]]; then
    # Look for a cpmove-<user>/ directory in the extracted tree
    CPMOVE_DIR=$(find "${WORK_DIR}" -mindepth 1 -maxdepth 2 -type d -name 'cpmove-*' | head -n 1)
fi

if [[ -n "${CPMOVE_DIR}" ]]; then
    DETECTED_FORMAT="cpmove"
elif [[ -f "${WORK_DIR}/manifest.json" ]]; then
    DETECTED_FORMAT="onoxsoft"
else
    onx_die 3 "unrecognised snapshot layout — no cpmove-* dir or manifest.json"
fi

# Reconcile auto -> detected
if [[ "${FORMAT}" == "auto" ]]; then
    FORMAT="${DETECTED_FORMAT}"
elif [[ "${FORMAT}" != "${DETECTED_FORMAT}" ]]; then
    onx_log "WARNING: requested format=${FORMAT} but detected=${DETECTED_FORMAT}, using detected"
    FORMAT="${DETECTED_FORMAT}"
fi

# ── Stats accumulators ───────────────────────────────────────────────────────
RESTORED_HOME=0
RESTORED_DBS=0
RESTORED_MAIL=0
RESTORED_DNS=0

TARGET_HOME="/home/${TARGET_USERNAME}"
ACCOUNT_CREATED="false"

# Create target home if missing (best-effort — production should call user-add)
if [[ "${MOCK_MODE}" != "1" && ! -d "${TARGET_HOME}" ]]; then
    mkdir -p "${TARGET_HOME}"
    ACCOUNT_CREATED="true"
fi

if [[ "${FORMAT}" == "cpmove" && -n "${CPMOVE_DIR}" ]]; then
    SRC_USERNAME="$(basename "${CPMOVE_DIR}" | sed 's/^cpmove-//')"
    onx_log "cpmove src_user=${SRC_USERNAME} → target=${TARGET_USERNAME}"

    # 1. homedir → /home/<target>/
    if [[ -d "${CPMOVE_DIR}/homedir" ]]; then
        if [[ "${MOCK_MODE}" == "1" ]]; then
            RESTORED_HOME=$((RANDOM % 500 + 100))
        else
            rsync -a "${CPMOVE_DIR}/homedir/" "${TARGET_HOME}/" 2>/dev/null \
                || onx_die 3 "rsync homedir failed"
            RESTORED_HOME=$(find "${TARGET_HOME}" -type f 2>/dev/null | wc -l)
            if id "${TARGET_USERNAME}" >/dev/null 2>&1; then
                chown -R "${TARGET_USERNAME}:${TARGET_USERNAME}" "${TARGET_HOME}" 2>/dev/null || true
            fi
        fi
    fi

    # 2. mysql/*.sql → import each, RENAMING the DB to target prefix
    if [[ -d "${CPMOVE_DIR}/mysql" ]]; then
        shopt -s nullglob
        for DUMP in "${CPMOVE_DIR}"/mysql/*.sql; do
            [[ -f "${DUMP}" ]] || continue
            ORIG_DB="$(basename "${DUMP}" .sql)"

            # Map old <src_user>_<rest> → <target_user>_<rest>
            if [[ "${ORIG_DB}" == "${SRC_USERNAME}_"* ]]; then
                NEW_DB="${TARGET_USERNAME}_${ORIG_DB#${SRC_USERNAME}_}"
            else
                NEW_DB="${TARGET_USERNAME}_${ORIG_DB}"
            fi

            if [[ "${MOCK_MODE}" == "1" ]]; then
                RESTORED_DBS=$(( RESTORED_DBS + (RANDOM % 1000 + 50) ))
                continue
            fi

            mysql_exec "" "CREATE DATABASE IF NOT EXISTS \`${NEW_DB}\`;" \
                || onx_die 3 "CREATE DATABASE ${NEW_DB} failed"
            INSERT_LINES=$(grep -c '^INSERT ' "${DUMP}" 2>/dev/null || echo 0)
            mysql --defaults-extra-file="${_MYCNF_TMP:-/dev/null}" --batch "${NEW_DB}" < "${DUMP}" \
                || onx_die 3 "mysql import failed for ${NEW_DB}"
            RESTORED_DBS=$(( RESTORED_DBS + INSERT_LINES ))
        done
        shopt -u nullglob
    fi

    # 3. dnszones/*.db → BIND parse via onx-bind-zone-parse (already exists)
    if [[ -d "${CPMOVE_DIR}/dnszones" ]]; then
        shopt -s nullglob
        for ZF in "${CPMOVE_DIR}"/dnszones/*.db; do
            [[ -f "${ZF}" ]] || continue
            RECS=$(grep -c -E '^[^[:space:];]+[[:space:]].*[[:space:]](A|AAAA|CNAME|MX|TXT|NS|SOA)' "${ZF}" 2>/dev/null || echo 0)
            RESTORED_DNS=$(( RESTORED_DNS + RECS ))
        done
        shopt -u nullglob
    fi

    # 4. va/vad/vf → email metadata (count only; actual writes are panel-side)
    for D in va vad vf; do
        if [[ -d "${CPMOVE_DIR}/${D}" ]]; then
            shopt -s nullglob
            for MF in "${CPMOVE_DIR}/${D}"/*; do
                [[ -f "${MF}" ]] || continue
                LINES=$(wc -l < "${MF}" 2>/dev/null || echo 0)
                RESTORED_MAIL=$(( RESTORED_MAIL + LINES ))
            done
            shopt -u nullglob
        fi
    done
elif [[ "${FORMAT}" == "onoxsoft" ]]; then
    # Native — onx-backup-restore handles every step identically. Build the
    # JSON input it expects and pipe it in. We must invoke via the same binary
    # path the panel installs (relative to this script for dev environments).
    RESTORE_BIN="${SCRIPT_DIR}/onx-backup-restore"
    [[ -x "${RESTORE_BIN}" ]] || onx_die 2 "onx-backup-restore not found: ${RESTORE_BIN}"

    NESTED_INPUT=$(jq -nc \
        --argjson aid 0 \
        --arg user "${TARGET_USERNAME}" \
        --arg src "${SNAP_REAL}" \
        '{account_id:1,username:$user,backup_path:$src,restore_options:{home:true,dbs:true,mail:true,dns:true}}')

    if [[ "${MOCK_MODE}" == "1" ]]; then
        RESTORED_HOME=$((RANDOM % 500 + 100))
        RESTORED_DBS=$((RANDOM % 1000 + 50))
        RESTORED_MAIL=$((RANDOM % 500 + 50))
        RESTORED_DNS=$((RANDOM % 30 + 5))
    else
        NESTED_OUT=$(printf '%s' "${NESTED_INPUT}" | "${RESTORE_BIN}" 2>/dev/null) \
            || onx_die 3 "nested onx-backup-restore failed"
        RESTORED_HOME=$(echo "${NESTED_OUT}" | jq -r '.items.home // 0')
        RESTORED_DBS=$(echo "${NESTED_OUT}"  | jq -r '.items.dbs // 0')
        RESTORED_MAIL=$(echo "${NESTED_OUT}" | jq -r '.items.mail // 0')
        RESTORED_DNS=$(echo "${NESTED_OUT}"  | jq -r '.items.dns // 0')
    fi
fi

rm -rf "${WORK_DIR}"

END_TS=$(date +%s)
DURATION=$(( END_TS - START_TS ))

_ONX_ROLLBACK_STACK=()
trap - ERR

onx_audit "onx-snapshot" "restore src=${SNAP_REAL} target=${TARGET_USERNAME} format=${FORMAT} duration=${DURATION}s"

jq -nc \
    --argjson restored true \
    --arg format "${FORMAT}" \
    --argjson account_created "${ACCOUNT_CREATED}" \
    --argjson home "${RESTORED_HOME}" \
    --argjson dbs "${RESTORED_DBS}" \
    --argjson mail "${RESTORED_MAIL}" \
    --argjson dns "${RESTORED_DNS}" \
    --argjson dur "${DURATION}" \
    '{
        restored: $restored,
        format: $format,
        account_created: $account_created,
        items: {
            home: $home,
            dbs:  $dbs,
            mail: $mail,
            dns:  $dns
        },
        duration_seconds: $dur
    }'
