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

SCRIPT_NAME="$(basename "$0")"
DOCKER_ROOT="${DOCKER_ROOT:-/var/lib/docker}"
TOP_N="${TOP_N:-20}"
LOG_SIZE_MB="${LOG_SIZE_MB:-200}"

RUN_SAFE_PRUNE=0
RUN_UNUSED_IMAGES_PRUNE=0
RUN_TRUNCATE_LOGS=0
RUN_GUIDED=0
CLEANUP_RAN=0

INITIAL_FS_AVAILABLE_BYTES=0
INITIAL_DOCKER_ROOT_BYTES=0
PREVIOUS_FS_AVAILABLE_BYTES=0
PREVIOUS_DOCKER_ROOT_BYTES=0
CURRENT_FS_AVAILABLE_BYTES=0
CURRENT_DOCKER_ROOT_BYTES=0

info() {
  printf '[INFO] %s\n' "$*"
}

warn() {
  printf '[WARN] %s\n' "$*" >&2
}

die() {
  printf '[ERROR] %s\n' "$*" >&2
  exit 1
}

usage() {
  cat <<EOF
Uso:
  sudo ./${SCRIPT_NAME} [opciones]

Opciones:
  --report                 Solo diagnostico. Es el modo por defecto.
  --guided                 Ejecuta un flujo guiado paso a paso con confirmacion.
  --safe-prune             Limpia contenedores detenidos, imagenes dangling,
                           cache de build y redes no usadas.
  --prune-unused-images    Elimina todas las imagenes no usadas por contenedores.
                           Es mas agresivo que --safe-prune.
  --truncate-logs          Trunca logs Docker grandes (*.log) en ejecucion.
                           Pierde historial de logs, pero libera espacio rapido.
  --log-size-mb N          Umbral para --truncate-logs. Default: ${LOG_SIZE_MB} MB.
  --top N                  Cuantos items mostrar en reportes. Default: ${TOP_N}.
  --docker-root PATH       Ruta Docker root. Default: ${DOCKER_ROOT}.
  -h, --help               Muestra esta ayuda.

Ejemplos:
  sudo ./${SCRIPT_NAME}
  sudo ./${SCRIPT_NAME} --guided
  sudo ./${SCRIPT_NAME} --safe-prune
  sudo ./${SCRIPT_NAME} --safe-prune --prune-unused-images
  sudo ./${SCRIPT_NAME} --safe-prune --truncate-logs --log-size-mb 100
EOF
}

require_root() {
  if [[ "${EUID}" -ne 0 ]]; then
    die "Ejecuta este script con sudo o como root."
  fi
}

require_cmd() {
  local cmd="$1"
  command -v "${cmd}" >/dev/null 2>&1 || die "No se encontro el comando requerido: ${cmd}"
}

section() {
  printf '\n========== %s ==========\n' "$*"
}

human_ts() {
  date '+%Y-%m-%d %H:%M:%S %Z'
}

path_available_bytes() {
  local path="$1"
  df -Pk "${path}" 2>/dev/null | awk 'NR==2 {print $4 * 1024}'
}

dir_size_bytes() {
  local path="$1"
  if [[ -d "${path}" ]]; then
    du -sxk "${path}" 2>/dev/null | awk 'NR==1 {print $1 * 1024}'
  else
    printf '0\n'
  fi
}

format_bytes() {
  local bytes="${1:-0}"

  awk -v bytes="${bytes}" '
    function human(value, sign, abs, idx, units) {
      split("B KB MB GB TB PB", units, " ");
      sign = "";
      abs = value;
      if (value < 0) {
        sign = "-";
        abs = -value;
      }
      idx = 1;
      while (abs >= 1024 && idx < 6) {
        abs /= 1024;
        idx++;
      }
      return sprintf("%s%.2f %s", sign, abs, units[idx]);
    }
    BEGIN {
      print human(bytes);
    }
  '
}

capture_current_metrics() {
  CURRENT_FS_AVAILABLE_BYTES="$(path_available_bytes "${DOCKER_ROOT}")"
  CURRENT_DOCKER_ROOT_BYTES="$(dir_size_bytes "${DOCKER_ROOT}")"
}

initialize_metrics() {
  capture_current_metrics
  INITIAL_FS_AVAILABLE_BYTES="${CURRENT_FS_AVAILABLE_BYTES}"
  INITIAL_DOCKER_ROOT_BYTES="${CURRENT_DOCKER_ROOT_BYTES}"
  PREVIOUS_FS_AVAILABLE_BYTES="${CURRENT_FS_AVAILABLE_BYTES}"
  PREVIOUS_DOCKER_ROOT_BYTES="${CURRENT_DOCKER_ROOT_BYTES}"
}

report_recovered_space() {
  local label="$1"
  local fs_delta=0
  local docker_delta=0

  capture_current_metrics

  fs_delta=$((CURRENT_FS_AVAILABLE_BYTES - PREVIOUS_FS_AVAILABLE_BYTES))
  docker_delta=$((PREVIOUS_DOCKER_ROOT_BYTES - CURRENT_DOCKER_ROOT_BYTES))

  info "${label}: espacio libre recuperado en filesystem: $(format_bytes "${fs_delta}")"
  info "${label}: reduccion estimada en ${DOCKER_ROOT}: $(format_bytes "${docker_delta}")"

  PREVIOUS_FS_AVAILABLE_BYTES="${CURRENT_FS_AVAILABLE_BYTES}"
  PREVIOUS_DOCKER_ROOT_BYTES="${CURRENT_DOCKER_ROOT_BYTES}"
}

report_total_recovered_space() {
  local fs_delta=0
  local docker_delta=0

  capture_current_metrics

  fs_delta=$((CURRENT_FS_AVAILABLE_BYTES - INITIAL_FS_AVAILABLE_BYTES))
  docker_delta=$((INITIAL_DOCKER_ROOT_BYTES - CURRENT_DOCKER_ROOT_BYTES))

  section "Espacio recuperado"
  info "Espacio libre recuperado en filesystem: $(format_bytes "${fs_delta}")"
  info "Reduccion estimada dentro de ${DOCKER_ROOT}: $(format_bytes "${docker_delta}")"
}

confirm_prompt() {
  local prompt="$1"
  local default="${2:-N}"
  local answer=""
  local options="[y/N]"

  if [[ "${default}" == "Y" ]]; then
    options="[Y/n]"
  fi

  while true; do
    read -r -p "${prompt} ${options} " answer || return 1
    answer="${answer:-${default}}"

    case "${answer}" in
      y|Y|yes|YES|s|S|si|SI|sí|SÍ)
        return 0
        ;;
      n|N|no|NO)
        return 1
        ;;
      *)
        warn "Respuesta no valida. Usa y/n."
        ;;
    esac
  done
}

show_step_preview() {
  local title="$1"
  local detail="$2"
  shift 2

  section "${title}"
  printf '%s\n' "${detail}"

  if [[ $# -gt 0 ]]; then
    printf 'Se ejecutara:\n'
    for cmd in "$@"; do
      printf '  - %s\n' "${cmd}"
    done
  fi
}

report_df() {
  section "Uso de disco"
  df -h /
  if [[ -d "${DOCKER_ROOT}" ]]; then
    df -h "${DOCKER_ROOT}" || true
  else
    warn "No existe ${DOCKER_ROOT}"
  fi
}

report_docker_df() {
  section "Docker system df"
  docker system df || warn "No se pudo ejecutar docker system df"
}

report_docker_root_breakdown() {
  section "Top de ${DOCKER_ROOT}"
  if [[ -d "${DOCKER_ROOT}" ]]; then
    du -xhd1 "${DOCKER_ROOT}" 2>/dev/null | sort -h || true
  else
    warn "No existe ${DOCKER_ROOT}"
  fi
}

report_large_container_logs() {
  section "Logs grandes de contenedores"
  if [[ ! -d "${DOCKER_ROOT}/containers" ]]; then
    warn "No existe ${DOCKER_ROOT}/containers"
    return 0
  fi

  local found=0
  while IFS= read -r line; do
    found=1
    printf '%s\n' "${line}"
  done < <(find "${DOCKER_ROOT}/containers" -type f -name '*-json.log' -exec du -m {} + 2>/dev/null | sort -nr | head -n "${TOP_N}")

  if [[ "${found}" -eq 0 ]]; then
    info "No se encontraron logs JSON de contenedores."
  fi
}

report_journal_usage() {
  if command -v journalctl >/dev/null 2>&1; then
    section "Journal usage"
    journalctl --disk-usage || true
  fi
}

safe_prune() {
  section "Safe prune"
  info "Eliminando contenedores detenidos..."
  docker container prune -f

  info "Eliminando imagenes dangling..."
  docker image prune -f

  info "Eliminando cache de build..."
  docker builder prune -af

  info "Eliminando redes no usadas..."
  docker network prune -f
}

unused_images_prune() {
  section "Unused images prune"
  warn "Se eliminaran todas las imagenes no usadas por contenedores existentes."
  docker image prune -af
}

truncate_large_logs() {
  section "Truncate logs"
  info "Truncando logs mayores a ${LOG_SIZE_MB} MB en ${DOCKER_ROOT}/containers"

  local matched=0
  while IFS= read -r file; do
    [[ -z "${file}" ]] && continue
    matched=1
    info "Truncando ${file}"
    : > "${file}"
  done < <(find "${DOCKER_ROOT}/containers" -type f -name '*-json.log' -size +"${LOG_SIZE_MB}"M 2>/dev/null)

  if [[ "${matched}" -eq 0 ]]; then
    info "No se encontraron logs mayores a ${LOG_SIZE_MB} MB."
  fi
}

report_after_step() {
  local label="${1:-Paso}"
  section "Resumen despues del paso"
  report_df
  report_docker_df
  report_recovered_space "${label}"
}

run_guided_mode() {
  [[ -t 0 && -t 1 ]] || die "El modo guiado requiere una terminal interactiva."

  section "Modo guiado"
  info "El diagnostico inicial ya fue ejecutado."
  info "Ahora puedes aprobar cada paso manualmente."

  show_step_preview \
    "Paso 1: Safe prune" \
    "Limpia residuos seguros de Docker. No elimina volumenes ni contenedores en ejecucion." \
    "docker container prune -f" \
    "docker image prune -f" \
    "docker builder prune -af" \
    "docker network prune -f"

  if confirm_prompt "¿Deseas ejecutar el paso 1?" "N"; then
    CLEANUP_RAN=1
    safe_prune
    report_after_step "Paso 1"
  else
    info "Paso 1 omitido."
  fi

  show_step_preview \
    "Paso 2: Prune de imagenes no usadas" \
    "Elimina todas las imagenes que no esten siendo usadas por contenedores actuales. Libera bastante espacio, pero el proximo deploy podria volver a descargarlas." \
    "docker image prune -af"

  if confirm_prompt "¿Deseas ejecutar el paso 2?" "N"; then
    CLEANUP_RAN=1
    unused_images_prune
    report_after_step "Paso 2"
  else
    info "Paso 2 omitido."
  fi

  show_step_preview \
    "Paso 3: Truncado de logs grandes" \
    "Vacía el contenido de logs JSON de Docker mayores al umbral configurado. Libera espacio rapido, pero se pierde historial de logs antiguos." \
    "find ${DOCKER_ROOT}/containers -type f -name '*-json.log' -size +${LOG_SIZE_MB}M" \
    ": > archivo-log"

  if confirm_prompt "¿Deseas ejecutar el paso 3?" "N"; then
    CLEANUP_RAN=1
    truncate_large_logs
    report_after_step "Paso 3"
  else
    info "Paso 3 omitido."
  fi
}

parse_args() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --report)
        ;;
      --guided)
        RUN_GUIDED=1
        ;;
      --safe-prune)
        RUN_SAFE_PRUNE=1
        ;;
      --prune-unused-images)
        RUN_UNUSED_IMAGES_PRUNE=1
        ;;
      --truncate-logs)
        RUN_TRUNCATE_LOGS=1
        ;;
      --log-size-mb)
        shift
        [[ $# -gt 0 ]] || die "Falta valor para --log-size-mb"
        LOG_SIZE_MB="$1"
        ;;
      --top)
        shift
        [[ $# -gt 0 ]] || die "Falta valor para --top"
        TOP_N="$1"
        ;;
      --docker-root)
        shift
        [[ $# -gt 0 ]] || die "Falta valor para --docker-root"
        DOCKER_ROOT="$1"
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        die "Opcion no reconocida: $1"
        ;;
    esac
    shift
  done
}

main() {
  parse_args "$@"
  require_root
  require_cmd docker
  require_cmd df
  require_cmd du
  require_cmd awk
  require_cmd find
  require_cmd sort
  require_cmd head

  section "Inicio"
  info "Fecha: $(human_ts)"
  info "Host: $(hostname)"
  info "Docker root: ${DOCKER_ROOT}"
  initialize_metrics

  report_df
  report_docker_df
  report_docker_root_breakdown
  report_large_container_logs
  report_journal_usage

  if [[ "${RUN_GUIDED}" -eq 1 ]]; then
    if [[ "${RUN_SAFE_PRUNE}" -eq 1 || "${RUN_UNUSED_IMAGES_PRUNE}" -eq 1 || "${RUN_TRUNCATE_LOGS}" -eq 1 ]]; then
      warn "Las banderas de limpieza directa se ignoran en modo guiado."
    fi

    run_guided_mode

    section "Reporte final"
    report_df
    report_docker_df
    report_docker_root_breakdown
    report_large_container_logs
    report_total_recovered_space
    return 0
  fi

  if [[ "${RUN_SAFE_PRUNE}" -eq 1 ]]; then
    CLEANUP_RAN=1
    safe_prune
  fi

  if [[ "${RUN_UNUSED_IMAGES_PRUNE}" -eq 1 ]]; then
    CLEANUP_RAN=1
    unused_images_prune
  fi

  if [[ "${RUN_TRUNCATE_LOGS}" -eq 1 ]]; then
    CLEANUP_RAN=1
    truncate_large_logs
  fi

  if [[ "${CLEANUP_RAN}" -eq 1 ]]; then
    section "Reporte despues de limpieza"
    report_df
    report_docker_df
    report_docker_root_breakdown
    report_large_container_logs
    report_total_recovered_space
  fi
}

main "$@"
