#!/usr/bin/env bash
#
# validar_imagens_docker.sh
#
# Lista as imagens Docker LOCAIS e, para cada uma, valida pelo HASH (digest)
# se a tag local esta atualizada em relacao ao registro remoto
# (gitlab.microuniverso.com.br:5050).
#
# Motivacao: ao subir os sistemas com os scripts Python (docker compose pull/up),
# quando uma tag e reaproveitada (ex.: uma "versao" recompilada ou "latest"), a
# imagem local pode ficar defasada em relacao ao registro. Comparar o digest
# local (RepoDigests) com o digest remoto (manifest do registro) detecta isso
# de forma confiavel, independente da tag.
#
# Para cada imagem local sao mostradas as colunas:
#   STATUS      ATUALIZADA / DESATUALIZADA / SEM_DIGESTO / ERRO
#   USO         sim (container rodando) / parado (container parado) / nao
#   SHA LOCAL   primeiros digitos do digest local
#   SHA REMOTO  primeiros digitos do digest remoto
#   IMAGEM      repositorio:tag
#
# Status:
#   ATUALIZADA    -> digest local == digest remoto
#   DESATUALIZADA -> digest local != digest remoto (tag reaproveitada no registro)
#   SEM_DIGESTO   -> imagem local sem RepoDigests (build local / nao veio de pull)
#   ERRO          -> nao foi possivel consultar o registro (ex.: falta login)
#
# Uso tipico (rodar no host Linux/WSL onde o Docker esta):
#   ./validar_imagens_docker.sh --config /microuniversoWeb/microuniversoWeb.yaml --login
#
# Opcoes:
#   --config ARQ        YAML de configuracao. Usado para as credenciais do
#                       registro (acessos.registroDocker.usuario/senha) no --login.
#   --login             Faz "docker login" no registro usando as credenciais do --config.
#   --registro HOST     Registro a validar/logar (padrao: gitlab.microuniverso.com.br:5050).
#   --incluir-publicas  Tambem lista imagens locais fora do registro da empresa
#                       (nginx, mongo, ...). Por padrao so as do registro.
#   --filtro PADRAO     So valida imagens cujo "repo:tag" casa com PADRAO (regex).
#   --imagem REF        Valida apenas esta imagem (repetivel); ignora a lista local.
#   --pull              Refaz o "docker pull" das imagens DESATUALIZADAS.
#   --json              Saida em JSON (uma linha por imagem).
#   -h | --help         Mostra esta ajuda.
#
# Observacoes:
#   - Honra DOCKER_HOST (ex.: ssh://usuario@host) para consultar outro host.
#   - Requer docker com plugin buildx (le o digest remoto sem baixar a imagem).
#
set -uo pipefail

REGISTRO_PADRAO="gitlab.microuniverso.com.br:5050"

# ---------------------------------------------------------------------------
# Cores
# ---------------------------------------------------------------------------
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
  C_RESET=$'\033[0m'; C_VERM=$'\033[31m'; C_VERD=$'\033[32m'
  C_AMAR=$'\033[33m'; C_AZUL=$'\033[36m'; C_NEG=$'\033[1m'
else
  C_RESET=""; C_VERM=""; C_VERD=""; C_AMAR=""; C_AZUL=""; C_NEG=""
fi

info()  { printf '%s\n' "$*" >&2; }
aviso() { printf '%s%s%s\n' "$C_AMAR" "$*" "$C_RESET" >&2; }
erro()  { printf '%s%s%s\n' "$C_VERM" "$*" "$C_RESET" >&2; }

# ---------------------------------------------------------------------------
# Argumentos
# ---------------------------------------------------------------------------
CONFIG=""
REGISTRO="$REGISTRO_PADRAO"
FAZER_LOGIN=0
FAZER_PULL=0
INCLUIR_PUBLICAS=0
SAIDA_JSON=0
FILTRO=""
IMAGENS_EXPLICITAS=()

mostrar_ajuda() { sed -n '2,/^set -uo/p' "$0" | sed 's/^# \{0,1\}//; s/^#$//'; }

while [[ $# -gt 0 ]]; do
  case "$1" in
    --config)   CONFIG="${2:-}"; shift 2 ;;
    --registro) REGISTRO="${2:-}"; shift 2 ;;
    --filtro)   FILTRO="${2:-}"; shift 2 ;;
    --imagem)   IMAGENS_EXPLICITAS+=("${2:-}"); shift 2 ;;
    --login)    FAZER_LOGIN=1; shift ;;
    --pull)     FAZER_PULL=1; shift ;;
    --incluir-publicas) INCLUIR_PUBLICAS=1; shift ;;
    --json)     SAIDA_JSON=1; shift ;;
    -h|--help)  mostrar_ajuda; exit 0 ;;
    *) erro "Opcao desconhecida: $1"; erro "Use --help para ajuda."; exit 2 ;;
  esac
done

# ---------------------------------------------------------------------------
# Pre-requisitos
# ---------------------------------------------------------------------------
if ! command -v docker >/dev/null 2>&1; then
  erro "docker nao encontrado no PATH."; exit 2
fi
if ! docker buildx version >/dev/null 2>&1; then
  erro "docker buildx nao disponivel. E necessario para ler o digest remoto sem baixar a imagem."
  exit 2
fi

# ---------------------------------------------------------------------------
# Leitura de chaves do YAML (via python3 + PyYAML) - so para credenciais
# ---------------------------------------------------------------------------
ler_config() {
  local arquivo="$1" chave="$2"
  python3 - "$arquivo" "$chave" <<'PY' 2>/dev/null
import sys
try:
    import yaml
except Exception:
    sys.exit(3)
arquivo, chave = sys.argv[1], sys.argv[2]
try:
    with open(arquivo, encoding="utf-8") as f:
        dados = yaml.safe_load(f)
except Exception:
    sys.exit(4)
cur = dados
for parte in chave.split("."):
    if not isinstance(cur, dict) or parte not in cur:
        sys.exit(2)
    cur = cur[parte]
if cur is None:
    sys.exit(2)
print(cur)
PY
}

USUARIO_REG=""; SENHA_REG=""
if [[ -n "$CONFIG" ]]; then
  if [[ ! -f "$CONFIG" ]]; then
    erro "Arquivo de configuracao nao encontrado: $CONFIG"; exit 2
  fi
  USUARIO_REG="$(ler_config "$CONFIG" "acessos.registroDocker.usuario" || true)"
  SENHA_REG="$(ler_config "$CONFIG" "acessos.registroDocker.senha" || true)"
fi

# ---------------------------------------------------------------------------
# Login no registro (opcional)
# ---------------------------------------------------------------------------
if [[ "$FAZER_LOGIN" -eq 1 ]]; then
  if [[ -z "$USUARIO_REG" || -z "$SENHA_REG" ]]; then
    erro "--login requer --config com acessos.registroDocker.usuario/senha."
    exit 2
  fi
  info "Fazendo docker login em $REGISTRO ..."
  if ! printf '%s' "$SENHA_REG" | docker login "$REGISTRO" -u "$USUARIO_REG" --password-stdin >/dev/null 2>&1; then
    aviso "Falha no docker login em $REGISTRO (a consulta ao registro pode falhar)."
  fi
fi

# ---------------------------------------------------------------------------
# Monta a lista de imagens (local por padrao; explicitas se informadas)
# ---------------------------------------------------------------------------
IMAGENS=()
if [[ ${#IMAGENS_EXPLICITAS[@]} -gt 0 ]]; then
  IMAGENS=("${IMAGENS_EXPLICITAS[@]}")
else
  while IFS= read -r ref; do
    [[ -z "$ref" || "$ref" == *"<none>"* ]] && continue
    if [[ "$INCLUIR_PUBLICAS" -eq 0 && "$ref" != "$REGISTRO"* ]]; then continue; fi
    if [[ -n "$FILTRO" ]] && ! grep -qE "$FILTRO" <<< "$ref"; then continue; fi
    IMAGENS+=("$ref")
  done < <(docker images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | sort -u)
fi

if [[ ${#IMAGENS[@]} -eq 0 ]]; then
  erro "Nenhuma imagem local encontrada para validar."
  [[ "$INCLUIR_PUBLICAS" -eq 0 ]] && info "Dica: use --incluir-publicas para imagens fora de $REGISTRO."
  exit 2
fi

# ---------------------------------------------------------------------------
# Mapa de imagens em uso por containers (por ID de imagem)
# ---------------------------------------------------------------------------
declare -A USO_RUN=() USO_ALL=()
while IFS= read -r id; do [[ -n "$id" ]] && USO_RUN["$id"]=1; done \
  < <(docker ps -q 2>/dev/null | xargs -r docker inspect -f '{{.Image}}' 2>/dev/null)
while IFS= read -r id; do [[ -n "$id" ]] && USO_ALL["$id"]=1; done \
  < <(docker ps -aq 2>/dev/null | xargs -r docker inspect -f '{{.Image}}' 2>/dev/null)

# ---------------------------------------------------------------------------
# Consulta de digests
# ---------------------------------------------------------------------------

# Digest local: procura em RepoDigests o digest cujo repo casa com a ref.
# Retorna: 0=ok (imprime digest), 1=imagem ausente, 2=sem RepoDigests.
digest_local() {
  local ref="$1" repo
  repo="$(printf '%s' "$ref" | sed -E 's/(.*):[^/:]+$/\1/')"
  local repodigests
  repodigests="$(docker image inspect "$ref" --format '{{range .RepoDigests}}{{println .}}{{end}}' 2>/dev/null)" || return 1
  [[ -z "$repodigests" ]] && return 2
  local linha
  while IFS= read -r linha; do
    [[ -z "$linha" ]] && continue
    if [[ "${linha%@*}" == "$repo" ]]; then
      printf '%s\n' "${linha#*@}"; return 0
    fi
  done <<< "$repodigests"
  printf '%s\n' "$(printf '%s' "$repodigests" | head -n1 | sed -E 's/.*@//')"
}

id_local() { docker image inspect "$1" --format '{{.Id}}' 2>/dev/null; }

# Digest remoto: buildx imagetools (nao baixa). Resultado nas globais.
REMOTO_DIGEST=""; REMOTO_ERRO=""; REMOTO_AUTH=0
digest_remoto() {
  local ref="$1" rc tmp
  REMOTO_DIGEST=""; REMOTO_ERRO=""
  tmp="$(mktemp)"
  REMOTO_DIGEST="$(docker buildx imagetools inspect "$ref" --format '{{.Manifest.Digest}}' 2>"$tmp")"
  rc=$?
  if [[ $rc -ne 0 || -z "$REMOTO_DIGEST" ]]; then
    REMOTO_DIGEST=""
    REMOTO_ERRO="$(tr -d '\r' < "$tmp" | grep -m1 . || true)"
    if printf '%s' "$REMOTO_ERRO" | grep -qiE 'unauthor|denied|authorize|forbidden|401|403|login'; then
      REMOTO_AUTH=1
    fi
    rm -f "$tmp"; return 1
  fi
  rm -f "$tmp"; return 0
}

# Encurta um digest "sha256:abcdef..." para os primeiros N caracteres do hex.
sha_curto() {
  local d="${1#sha256:}"
  [[ -z "$d" ]] && { printf -- '-'; return; }
  printf '%s' "${d:0:12}"
}

# ---------------------------------------------------------------------------
# Validacao
# ---------------------------------------------------------------------------
n_ok=0; n_desatualizada=0; n_semdig=0; n_erro=0; n_ausente=0
HOUVE_AUTH=0
declare -a A_PULL=()
JSON_LINHAS=()

if [[ "$SAIDA_JSON" -eq 0 ]]; then
  info "${C_NEG}Validando ${#IMAGENS[@]} imagem(ns) local(is) contra o registro...${C_RESET}"
  info ""
  printf -v cab '%-14s %-7s %-13s %-13s %s' "STATUS" "USO" "SHA LOCAL" "SHA REMOTO" "IMAGEM"
  info "${C_NEG}${cab}${C_RESET}"
fi

for ref in "${IMAGENS[@]}"; do
  local_dig=""; status=""; rc=0
  local_dig="$(digest_local "$ref")"; rc=$?

  digest_remoto "$ref" || true
  remoto_dig="$REMOTO_DIGEST"
  motivo_erro="$REMOTO_ERRO"
  [[ "$REMOTO_AUTH" -eq 1 ]] && HOUVE_AUTH=1

  if [[ $rc -eq 1 ]]; then
    status="AUSENTE"
  elif [[ -z "$remoto_dig" ]]; then
    status="ERRO"
  elif [[ $rc -eq 2 || -z "$local_dig" ]]; then
    status="SEM_DIGESTO"
  elif [[ "$local_dig" == "$remoto_dig" ]]; then
    status="ATUALIZADA"
  else
    status="DESATUALIZADA"
  fi

  # Em uso?
  iid="$(id_local "$ref")"
  if [[ -n "$iid" && -n "${USO_RUN[$iid]:-}" ]]; then uso="sim"
  elif [[ -n "$iid" && -n "${USO_ALL[$iid]:-}" ]]; then uso="parado"
  else uso="nao"; fi

  case "$status" in
    ATUALIZADA)    ((n_ok++));            cor="$C_VERD" ;;
    DESATUALIZADA) ((n_desatualizada++)); cor="$C_VERM"; A_PULL+=("$ref") ;;
    SEM_DIGESTO)   ((n_semdig++));        cor="$C_AMAR" ;;
    AUSENTE)       ((n_ausente++));       cor="$C_AMAR"; A_PULL+=("$ref") ;;
    ERRO)          ((n_erro++));          cor="$C_VERM" ;;
  esac

  sl="$(sha_curto "$local_dig")"
  sr="$(sha_curto "$remoto_dig")"

  if [[ "$SAIDA_JSON" -eq 1 ]]; then
    JSON_LINHAS+=("$(printf '{"imagem":"%s","status":"%s","em_uso":"%s","digest_local":"%s","digest_remoto":"%s"}' \
      "$ref" "$status" "$uso" "${local_dig:-}" "${remoto_dig:-}")")
  else
    # cor do USO: verde se rodando, amarelo se parado
    cor_uso="$C_RESET"
    [[ "$uso" == "sim" ]] && cor_uso="$C_VERD"
    [[ "$uso" == "parado" ]] && cor_uso="$C_AMAR"
    printf -v st '%-14s' "$status"
    printf -v us '%-7s'  "$uso"
    printf '%s%s%s %s%s%s %-13s %-13s %s\n' \
      "$cor" "$st" "$C_RESET" "$cor_uso" "$us" "$C_RESET" "$sl" "$sr" "$ref"
    if [[ "$status" == "ERRO" && -n "$motivo_erro" ]]; then
      printf '   motivo: %s\n' "$motivo_erro"
    fi
  fi
done

# ---------------------------------------------------------------------------
# Saida JSON
# ---------------------------------------------------------------------------
if [[ "$SAIDA_JSON" -eq 1 ]]; then
  printf '[\n'
  for i in "${!JSON_LINHAS[@]}"; do
    sep=","; [[ $i -eq $((${#JSON_LINHAS[@]}-1)) ]] && sep=""
    printf '  %s%s\n' "${JSON_LINHAS[$i]}" "$sep"
  done
  printf ']\n'
fi

# ---------------------------------------------------------------------------
# Pull das desatualizadas
# ---------------------------------------------------------------------------
if [[ "$FAZER_PULL" -eq 1 && ${#A_PULL[@]} -gt 0 ]]; then
  info ""
  info "${C_NEG}Atualizando ${#A_PULL[@]} imagem(ns) com docker pull...${C_RESET}"
  for ref in "${A_PULL[@]}"; do
    info "  docker pull $ref"
    docker pull "$ref" >&2 || aviso "  Falha ao baixar $ref"
  done
fi

# ---------------------------------------------------------------------------
# Resumo
# ---------------------------------------------------------------------------
if [[ "$SAIDA_JSON" -eq 0 ]]; then
  info ""
  info "${C_NEG}Resumo:${C_RESET}"
  info "  ${C_VERD}Atualizadas   : $n_ok${C_RESET}"
  info "  ${C_VERM}Desatualizadas: $n_desatualizada${C_RESET}"
  info "  ${C_AMAR}Sem digesto   : $n_semdig${C_RESET}"
  [[ $n_ausente -gt 0 ]] && info "  ${C_AMAR}Ausentes      : $n_ausente${C_RESET}"
  info "  ${C_VERM}Erros         : $n_erro${C_RESET}"
  if [[ "$HOUVE_AUTH" -eq 1 ]]; then
    info ""
    aviso "Parece falta de autenticacao no registro. Rode com login, ex.:"
    aviso "  $0 --config /microuniversoWeb/microuniversoWeb.yaml --login"
    aviso "ou faca 'docker login $REGISTRO' manualmente antes."
  fi
fi

# Codigo de saida: 0 = tudo atualizado; 1 = ha desatualizadas/ausentes/erros.
if [[ $n_desatualizada -gt 0 || $n_ausente -gt 0 || $n_erro -gt 0 ]]; then
  exit 1
fi
exit 0
