#!/bin/bash
# sync_to_ibmi.sh — Sync changed files to an IBM i library
#
# Usage: sync_to_ibmi.sh <connection_name> <library_name> <file_path_1> [file_path_2] ...
#
# Arguments:
#   connection_name — SSH config alias for the IBM i connection (set up by the ssh feature)
#   library_name    — The IBM i library to sync files to
#   file_path_N     — Absolute paths of changed files to sync
#
# SSH connectivity:
#   The ssh feature creates an SSH config entry for the connection, so
#   `ssh <connection_name>` just works — no need to specify host, user, or key path.
#
# Remote command execution:
#   All remote commands run through QShell: ssh <connection> /usr/bin/qsh -c '...'
#   QShell is required because IBM i utilities used by this script only work in QShell.
#   The -c flag passes a command string; -e can be used for early exit on error.
#
# Log format:
#   Every line is prefixed with an ISO timestamp (YYYY-MM-DDTHH:MM:SS).
#   Steps are announced before execution, then confirmed or reported as failed.
#   Failed command output (stdout/stderr) is included in the log.
#
# This script lives on the server and is piped into the container via stdin.
# See Part B for the execution mechanism.
#
# Expected container env vars:
#   TASK_ID — used in CRTLIB TEXT attribute when creating the target library

# --- Logging helper ---
log() {
  echo "$(date -u +%Y-%m-%dT%H:%M:%S) $*"
}

# --- QShell escaping helper ---
# Escape $ as \$ so values survive QShell double-quoted expansion.
# IBM i names (libraries, source files, members) can contain $ which is
# a valid character but triggers variable expansion in QShell.
qsh_escape() {
  printf '%s' "${1//\$/\\$}"
}

# --- Argument parsing ---
CONNECTION="$1"
LIBRARY="$2"
shift 2

FILES=("$@")

if [ -z "${CONNECTION}" ]; then
  log "ERROR: Connection name is required"
  exit 1
fi

if [ -z "${LIBRARY}" ]; then
  log "ERROR: Library name is required"
  exit 1
fi

if [ ${#FILES[@]} -eq 0 ]; then
  log "WARNING: No files to sync"
  exit 0
fi

# --- SSH multiplexing setup ---
# Use a per-invocation control socket so all ssh/scp commands share one connection.
# Configured entirely via CLI args — no config file changes needed.
SSH_CTRL_DIR=$(mktemp -d)
SSH_CTRL_PATH="${SSH_CTRL_DIR}/ctrl-%r@%h:%p"
SSH_MUX_OPTS="-o ControlPath=${SSH_CTRL_PATH}"

cleanup_ssh() {
  # Tear down the master connection and remove the socket dir
  ssh ${SSH_MUX_OPTS} -O exit "${CONNECTION}" 2>/dev/null || true
  rm -rf "${SSH_CTRL_DIR}"
}
trap cleanup_ssh EXIT

# Helper wrappers that route through the multiplexed connection.
# run_ssh executes commands on IBM i via QShell (/usr/bin/qsh -c '...').
# stdin is closed (< /dev/null) to prevent accidental consumption.
run_ssh() {
  local cmd="$1"
  ssh ${SSH_MUX_OPTS} "${CONNECTION}" /usr/bin/qsh -c "'${cmd}'" < /dev/null
}
# run_ssh_piped is the same but allows stdin to flow through from the caller's pipe.
run_ssh_piped() {
  local cmd="$1"
  ssh ${SSH_MUX_OPTS} "${CONNECTION}" /usr/bin/qsh -c "'${cmd}'"
}

# Escape library name for QShell commands ($ is valid in IBM i names)
LIBRARY_E=$(qsh_escape "${LIBRARY}")

log "Syncing ${#FILES[@]} file(s) to IBM i library ${LIBRARY} via connection ${CONNECTION}"

# --- Credential override (Prompt mode) ---
# When SYNC_SSH_USER and SYNC_SSH_PASSWORD are set, override the SSH connection
# to use password-based auth instead of the connection's SSH key.
# SYNC_SSH_HOST is required in this mode — it is passed from the server because
# the SSH feature (which creates ~/.ssh/config) may not be enabled.
if [ -n "${SYNC_SSH_USER}" ] && [ -n "${SYNC_SSH_PASSWORD}" ]; then
  if ! command -v sshpass &>/dev/null; then
    log "ERROR: sshpass is not installed. Password-based sync requires a container image rebuild."
    exit 1
  fi
  log "Using prompted credentials for user ${SYNC_SSH_USER}"
  SSH_HOST="${SYNC_SSH_HOST}"
  if [ -z "${SSH_HOST}" ]; then
    log "ERROR: SYNC_SSH_HOST is required when using prompted credentials"
    exit 1
  fi
  # Override SSH commands to use sshpass with the prompted user/password
  export SSHPASS="${SYNC_SSH_PASSWORD}"
  SSH_MUX_OPTS="${SSH_MUX_OPTS} -o User=${SYNC_SSH_USER} -o PreferredAuthentications=password -o PubkeyAuthentication=no"
  # Open master connection with sshpass
  log "Opening SSH connection to ${SSH_HOST} as ${SYNC_SSH_USER} ..."
  output=$(sshpass -e ssh ${SSH_MUX_OPTS} -o ControlMaster=yes -o ControlPersist=yes \
      -o StrictHostKeyChecking=accept-new -N -f \
      "${SYNC_SSH_USER}@${SSH_HOST}" 2>&1)
  sshpass_rc=$?
  if [ $sshpass_rc -eq 0 ]; then
    # Update CONNECTION for subsequent run_ssh calls to use user@host directly
    CONNECTION="${SYNC_SSH_USER}@${SSH_HOST}"
    log "SSH connection established as ${SYNC_SSH_USER}"
  elif [ $sshpass_rc -eq 5 ]; then
    log "ERROR: Failed to authenticate as ${SYNC_SSH_USER}@${SSH_HOST}"
    echo "$output" | while IFS= read -r line; do log "  $line"; done
    exit 2
  else
    log "ERROR: Failed to open SSH connection as ${SYNC_SSH_USER} (exit code ${sshpass_rc})"
    echo "$output" | while IFS= read -r line; do log "  $line"; done
    exit 1
  fi
  # Clear password from environment immediately after SSH connection is established
  unset SYNC_SSH_PASSWORD
  unset SSHPASS
else
  # Standard flow: use connection's SSH key (existing behavior)
  log "Opening SSH connection to ${CONNECTION} ..."
  output=$(ssh ${SSH_MUX_OPTS} -o ControlMaster=yes -o ControlPersist=yes -N -f "${CONNECTION}" 2>&1)
  ssh_rc=$?
  if [ $ssh_rc -eq 0 ]; then
    log "SSH connection established"
  else
    # Check output for authentication-related failures
    if echo "$output" | grep -qi "permission denied\|authentication failed\|publickey"; then
      log "ERROR: Failed to authenticate to ${CONNECTION}"
      echo "$output" | while IFS= read -r line; do log "  $line"; done
      exit 2
    fi
    log "ERROR: Failed to open SSH connection to ${CONNECTION} (exit code ${ssh_rc})"
    echo "$output" | while IFS= read -r line; do log "  $line"; done
    exit 1
  fi
fi

# --- Ensure target library exists ---
log "Checking if library ${LIBRARY} exists ..."
if run_ssh "test -e /qsys.lib/${LIBRARY_E}.lib" 2>/dev/null; then
  log "Library ${LIBRARY} already exists"
else
  log "Creating library ${LIBRARY} ..."
  if output=$(run_ssh "system \"CRTLIB LIB(${LIBRARY_E}) TEXT('\\''CoderFlow task ${TASK_ID}'\\'')\"" 2>&1); then
    log "Created library ${LIBRARY}"
  else
    log "ERROR: Failed to create library ${LIBRARY}"
    echo "$output" | while IFS= read -r line; do log "  $line"; done
    exit 1
  fi
fi

# --- Determine and create source physical files ---
# The source file name is derived from the parent directory of each changed file.
# e.g., 'source/qrpglesrc/mypgm.rpgle' → source file 'QRPGLESRC'
#        'src/myfile.rpgle'             → source file 'SRC'
# Collect unique source file names, then check/create them all up front.

declare -A SRCPF_MAP  # associative array: uppercase source file name → 1
for FILE in "${FILES[@]}"; do
  PARENT_DIR=$(basename "$(dirname "${FILE}")")
  SRCPF=$(echo "${PARENT_DIR}" | tr '[:lower:]' '[:upper:]')
  SRCPF_MAP["${SRCPF}"]=1
done

log "Source physical files needed: ${!SRCPF_MAP[*]}"

for SRCPF in "${!SRCPF_MAP[@]}"; do
  SRCPF_E=$(qsh_escape "${SRCPF}")
  log "Checking if source file ${SRCPF} exists in ${LIBRARY} ..."
  if run_ssh "test -e /qsys.lib/${LIBRARY_E}.lib/${SRCPF_E}.file" 2>/dev/null; then
    log "Source file ${SRCPF} already exists"
  else
    log "Creating source file ${SRCPF} in ${LIBRARY} ..."
    if output=$(run_ssh "system \"CRTSRCPF FILE(${LIBRARY_E}/${SRCPF_E}) RCDLEN(112)\"" 2>&1); then
      log "Created source file ${SRCPF}"
    else
      log "ERROR: Failed to create source file ${SRCPF} in ${LIBRARY}"
      echo "$output" | while IFS= read -r line; do log "  $line"; done
      exit 1
    fi
  fi
done

SUCCEEDED=0
FAILED=0

for FILE in "${FILES[@]}"; do
  # Derive member name (filename without extension, uppercased) and source type (extension without dot, uppercased)
  BASENAME=$(basename "${FILE}")
  MEMBER=$(echo "${BASENAME%%.*}" | tr '[:lower:]' '[:upper:]')
  SRCTYPE=$(echo "${BASENAME##*.}" | tr '[:lower:]' '[:upper:]')
  PARENT_DIR=$(basename "$(dirname "${FILE}")")
  SRCPF=$(echo "${PARENT_DIR}" | tr '[:lower:]' '[:upper:]')

  # Escaped versions for QShell commands (protect $ from expansion)
  MEMBER_E=$(qsh_escape "${MEMBER}")
  SRCPF_E=$(qsh_escape "${SRCPF}")

  log "Processing ${FILE} → ${LIBRARY}/${SRCPF}(${MEMBER}) type ${SRCTYPE}"

  # --- Extract @MBRTEXT from source file (first 5 lines) ---
  MBRTEXT=$(head -5 "${FILE}" | grep -oP '@MBRTEXT:\s*\K.*' | head -1)
  # Trim trailing whitespace (from CL-style */ suffix)
  MBRTEXT=$(echo "${MBRTEXT}" | sed 's/\s*\*\/\s*$//' | sed 's/[[:space:]]*$//')
  # Truncate to IBM i max 50 characters
  MBRTEXT="${MBRTEXT:0:50}"
  # Build TEXT() parameter if description is available
  TEXT_PARAM=""
  if [ -n "${MBRTEXT}" ]; then
    # Escape single quotes for CL commands (double them)
    MBRTEXT_ESCAPED="${MBRTEXT//\'/\'\'}"
    TEXT_PARAM=" TEXT('\\''${MBRTEXT_ESCAPED}'\\'') "
    log "Member text: ${MBRTEXT}"
  fi

  # --- Check/create/update member ---
  MBR_PATH="/qsys.lib/${LIBRARY_E}.lib/${SRCPF_E}.file/${MEMBER_E}.mbr"

  if run_ssh "test -e ${MBR_PATH}" 2>/dev/null; then
    # Member exists — update source type (and text if available)
    log "Member ${MEMBER} exists, setting source type to ${SRCTYPE} ..."
    if output=$(run_ssh "system \"CHGPFM FILE(${LIBRARY_E}/${SRCPF_E}) MBR(${MEMBER_E}) SRCTYPE(${SRCTYPE})${TEXT_PARAM}\"" 2>&1); then
      log "Updated source type for ${MEMBER}"
    else
      log "ERROR: Failed to update source type for ${MEMBER}"
      echo "$output" | while IFS= read -r line; do log "  $line"; done
      FAILED=$((FAILED + 1))
      continue
    fi
  else
    # Member does not exist — create it
    log "Creating member ${MEMBER} in ${SRCPF} ..."
    if output=$(run_ssh "system \"ADDPFM FILE(${LIBRARY_E}/${SRCPF_E}) MBR(${MEMBER_E}) SRCTYPE(${SRCTYPE})${TEXT_PARAM}\"" 2>&1); then
      log "Created member ${MEMBER}"
    else
      log "ERROR: Failed to create member ${MEMBER}"
      echo "$output" | while IFS= read -r line; do log "  $line"; done
      FAILED=$((FAILED + 1))
      continue
    fi
  fi

  # --- Write file content to member via Rfile ---
  # Pipe the local file content through stdin to Rfile on the remote side.
  # Rfile -Qw writes to the member using IBM i native naming: LIBRARY/FILE(MEMBER)
  log "Writing ${FILE} to ${LIBRARY}/${SRCPF}(${MEMBER}) ..."
  if output=$(cat "${FILE}" | run_ssh_piped "Rfile -Qw \"${LIBRARY_E}/${SRCPF_E}(${MEMBER_E})\"" 2>&1); then
    log "Wrote ${MEMBER} successfully"
    SUCCEEDED=$((SUCCEEDED + 1))
  else
    log "ERROR: Failed to write ${MEMBER}"
    echo "$output" | while IFS= read -r line; do log "  $line"; done
    FAILED=$((FAILED + 1))
  fi
done

# --- Summary ---
TOTAL=${#FILES[@]}
if [ ${FAILED} -eq 0 ]; then
  log "Sync completed successfully (${TOTAL}/${TOTAL})"
else
  log "Sync completed with errors (${SUCCEEDED}/${TOTAL} succeeded)"
  exit 1
fi
