#!/bin/bash

# Script para redeployar stacks en Portainer, actualizando todas sus imágenes
# Implementa filosofía GitOps para despliegue continuo
# Soporta stacks basados en archivos y stacks conectados a repositorios Git

# Configuración por defecto
PRUNE_IMAGES=false

# Función para mostrar ayuda
function show_help {
  echo "Uso: $0 [opciones]"
  echo ""
  echo "Opciones:"
  echo "  --url URL               URL de Portainer (obligatorio)"
  echo "  --username USUARIO      Nombre de usuario de Portainer (obligatorio)"
  echo "  --password PASSWORD     Contraseña de Portainer (obligatorio)"
  echo "  --environment ENTORNO   Nombre del entorno en Portainer (obligatorio)"
  echo "  --stack STACK           Nombre del stack a actualizar (obligatorio)"
  echo "  --prune                 Eliminar imágenes no utilizadas después del despliegue"
  echo "  --help                  Mostrar esta ayuda"
  exit 1
}

# Verificar que los comandos necesarios estén disponibles
if ! command -v curl &> /dev/null; then
  echo "Error: curl no está instalado. Por favor, instálalo e inténtalo de nuevo."
  exit 1
fi

if ! command -v jq &> /dev/null; then
  echo "Error: jq no está instalado. Por favor, instálalo e inténtalo de nuevo."
  exit 1
fi

# Procesar argumentos
while [[ $# -gt 0 ]]; do
  case $1 in
    --url)
      PORTAINER_URL="${2}"
      shift 2
      ;;
    --username)
      PORTAINER_USERNAME="${2}"
      shift 2
      ;;
    --password)
      PORTAINER_PASSWORD="${2}"
      shift 2
      ;;
    --environment)
      ENVIRONMENT_NAME="${2}"
      shift 2
      ;;
    --stack)
      STACK_NAME="${2}"
      shift 2
      ;;
    --prune)
      PRUNE_IMAGES=true
      shift
      ;;
    --help)
      show_help
      ;;
    *)
      echo "Opción desconocida: $1"
      show_help
      ;;
  esac
done

# Verificar argumentos obligatorios
if [ -z "$PORTAINER_URL" ] || [ -z "$PORTAINER_USERNAME" ] || [ -z "$PORTAINER_PASSWORD" ] || [ -z "$ENVIRONMENT_NAME" ] || [ -z "$STACK_NAME" ]; then
  echo "Error: Faltan argumentos obligatorios."
  show_help
fi

# Eliminar la barra final de la URL si existe
PORTAINER_URL=${PORTAINER_URL%/}

# Función para autenticar con Portainer
function authenticate {
  echo "Autenticando con Portainer..."
  
  AUTH_RESPONSE=$(curl -s -X POST \
    "$PORTAINER_URL/api/auth" \
    -H "Content-Type: application/json" \
    -d "{\"Username\":\"$PORTAINER_USERNAME\",\"Password\":\"$PORTAINER_PASSWORD\"}")
  
  if [ $? -ne 0 ]; then
    echo "Error al conectar con Portainer"
    exit 1
  fi
  
  # Extraer token JWT
  JWT_TOKEN=$(echo $AUTH_RESPONSE | jq -r '.jwt')
  
  if [ "$JWT_TOKEN" == "null" ] || [ -z "$JWT_TOKEN" ]; then
    echo "Error de autenticación. Verifica tus credenciales."
    exit 1
  fi
  
  echo "Autenticación exitosa"
}

# Función para obtener el ID del entorno
function get_environment_id {
  echo "Buscando entorno '$ENVIRONMENT_NAME'..."
  
  ENVIRONMENTS_RESPONSE=$(curl -s -X GET \
    "$PORTAINER_URL/api/endpoints" \
    -H "Authorization: Bearer $JWT_TOKEN")
  
  ENVIRONMENT_ID=$(echo $ENVIRONMENTS_RESPONSE | jq -r ".[] | select(.Name  == \"$ENVIRONMENT_NAME\" ) | .Id")
  
  if [ -z "$ENVIRONMENT_ID" ]; then
    echo "Error: Entorno '$ENVIRONMENT_NAME' no encontrado"
    exit 1
  fi
  
  echo "Entorno '$ENVIRONMENT_NAME' encontrado con ID: $ENVIRONMENT_ID"
}

# Función para obtener stacks
function get_stacks {
  echo "Obteniendo stacks del entorno..."
  
  STACKS_RESPONSE=$(curl -s -X GET \
    "$PORTAINER_URL/api/stacks" \
    -H "Authorization: Bearer $JWT_TOKEN")
  
  if [ $? -ne 0 ]; then
    echo "Error al obtener stacks"
    exit 1
  fi
  
  # Filtrar stacks por ID de entorno
  ENVIRONMENT_STACKS=$(echo $STACKS_RESPONSE | jq -c "[.[] | select(.EndpointId == $ENVIRONMENT_ID)]")
  
  # Contar total de stacks
  STACK_COUNT=$(echo $ENVIRONMENT_STACKS | jq '. | length')
  echo "Encontrados $STACK_COUNT stacks en el entorno"
}

# Función para encontrar el stack por nombre
function find_target_stack {
  echo "Buscando stack '$STACK_NAME'..."
  
  TARGET_STACK=$(echo $ENVIRONMENT_STACKS | jq -c "[.[] | select(.Name == \"$STACK_NAME\")][0]")
  
  if [ "$(echo $TARGET_STACK | jq -r '.')" == "null" ]; then
    echo "Error: Stack '$STACK_NAME' no encontrado en el entorno '$ENVIRONMENT_NAME'"
    exit 1
  fi
  
  STACK_ID=$(echo $TARGET_STACK | jq -r '.Id')
  STACK_ENV=$(echo $TARGET_STACK | jq -r '.Env')
  STACK_ENDPOINT=$(echo $TARGET_STACK | jq -r '.EndpointId')
  STACK_TYPE=$(echo $TARGET_STACK | jq -r '.GitConfig | if . == null then "file" else "git" end')
  
  echo "Stack '$STACK_NAME' encontrado con ID: $STACK_ID (Tipo: $STACK_TYPE)"
}

# Función para actualizar un stack basado en archivo
function update_file_stack {
  echo "Actualizando stack basado en archivo..."
  
  curl -s -X GET \
  "$PORTAINER_URL/api/stacks/$STACK_ID/file" \
  -H "Authorization: Bearer $JWT_TOKEN" > /tmp/stack_details.json
  
  # Extraer información relevante
  STACK_CONTENT=$(jq -r '.StackFileContent' /tmp/stack_details.json)
  
  # Escapar correctamente el contenido del archivo para JSON
  STACK_CONTENT_ESCAPED=$(echo "$STACK_CONTENT" | jq -Rs .)
  
  # Construir cuerpo de la solicitud para actualización
  UPDATE_BODY=$(cat << EOF
{
  "stackFileContent": ${STACK_CONTENT_ESCAPED},
  "env": $STACK_ENV,
  "prune": true,
  "pullImage": true
}
EOF
)
  
  
  # Actualizar stack
  echo "Redesplegando stack $STACK_NAME con pull de todas las imágenes..."
  UPDATE_RESPONSE=$(curl -s -X PUT \
    "$PORTAINER_URL/api/stacks/$STACK_ID?endpointId=$STACK_ENDPOINT" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $JWT_TOKEN" \
    -d "$UPDATE_BODY")
  
  if [ $? -ne 0 ] || [ "$(echo $UPDATE_RESPONSE | jq -r '.message // empty')" != "" ]; then
    echo "Error al actualizar stack: $(echo $UPDATE_RESPONSE | jq -r '.message // "Error desconocido"')"
    exit 1
  fi
  
  echo "Stack actualizado correctamente"
}

# Función para actualizar un stack basado en Git
function update_git_stack {
  echo "Actualizando stack conectado a Git..."
  
  # Obtener detalles del stack
  STACK_DETAILS=$(curl -s -X GET \
    "$PORTAINER_URL/api/stacks/$STACK_ID" \
    -H "Authorization: Bearer $JWT_TOKEN")
  
  # Extraer información Git
  GIT_CONFIG=$(echo $STACK_DETAILS | jq '.GitConfig')
  AUTO_UPDATE=$(echo $GIT_CONFIG | jq '.AutoUpdate')
  
  # Construir payload para actualización
  UPDATE_BODY=$(cat << EOF
{
  "env": $(echo $STACK_DETAILS | jq '.Env'),
  "prune": true,
  "pullImage": true
}
EOF
)
  
  # Para stacks Git, usamos el endpoint específico para pull
  echo "Solicitando pull de repositorio Git y actualización de imágenes..."
  UPDATE_RESPONSE=$(curl -s -X POST \
    "$PORTAINER_URL/api/stacks/$STACK_ID/git/redeploy?endpointId=$ENVIRONMENT_ID" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $JWT_TOKEN" \
    -d "$UPDATE_BODY")
  
  if [ $? -ne 0 ] || [ "$(echo $UPDATE_RESPONSE | jq -r '.message // empty')" != "" ]; then
    echo "Error al actualizar stack Git: $(echo $UPDATE_RESPONSE | jq -r '.message // "Error desconocido"')"
    exit 1
  fi
  
  echo "Stack Git actualizado correctamente"
}

# Función para limpiar imágenes no utilizadas
function prune_images {
  if [ "$PRUNE_IMAGES" = true ]; then
    echo "Eliminando imágenes no utilizadas..."
    
    PRUNE_RESPONSE=$(curl -s -X POST \
      "$PORTAINER_URL/api/endpoints/$ENVIRONMENT_ID/docker/images/prune?filters=%7B%22dangling%22%3A%7B%22true%22%3Atrue%7D%7D" \
      -H "Authorization: Bearer $JWT_TOKEN")
    
    if [ $? -eq 0 ]; then
      SPACE_RECLAIMED=$(echo $PRUNE_RESPONSE | jq -r '.SpaceReclaimed')
      SPACE_MB=$(echo "scale=2; $SPACE_RECLAIMED / 1024 / 1024" | bc)
      echo "Limpieza completada. Espacio recuperado: $SPACE_MB MB"
    else
      echo "Error durante la limpieza de imágenes"
    fi
  fi
}

# Función para verificar el estado del stack después de la actualización
function verify_stack_status {
  echo "Verificando estado del stack..."
  
  # Esperar un momento para que el stack se actualice
  sleep 5
  
  # Obtener detalles actualizados del stack
  STACK_STATUS=$(curl -s -X GET \
    "$PORTAINER_URL/api/stacks/$STACK_ID" \
    -H "Authorization: Bearer $JWT_TOKEN")
  
  # Verificar el estado
  STACK_STATUS_VALUE=$(echo $STACK_STATUS | jq -r '.Status')
  
  if [ "$STACK_STATUS_VALUE" == "1" ]; then
    echo "Stack '$STACK_NAME' actualizado y en ejecución correctamente"
  else
    echo "Advertencia: El stack puede no estar completamente activo. Status: $STACK_STATUS_VALUE"
    echo "Verifica el estado en la interfaz de Portainer para más detalles"
  fi
}

# Ejecución principal
authenticate
get_environment_id
get_stacks
find_target_stack

# Actualizar según el tipo de stack
if [ "$STACK_TYPE" == "file" ]; then
  update_file_stack
elif [ "$STACK_TYPE" == "git" ]; then
  update_git_stack
fi

# Acciones posteriores
verify_stack_status
prune_images

echo ""
echo "Proceso de actualización de stack completado"
exit 0