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

detect() {
    if dpkg -l docker-compose-plugin > /dev/null 2>&1; then
        return 0
    else
        return 1
    fi
}

# Checkmk local check: one service per docker compose container (expected from compose config)
# Metrics:
# - creation : seconds since container creation
# - uptime      : seconds since container start (0 if not running)
# - restarts      : restart count
#
# Requirements:
# - docker CLI
# - docker compose v2 plugin
# - permission to run docker (root or docker group)

LC_ALL=C

now_epoch() { date +%s; }

# Parse RFC3339 / Docker timestamps like: 2025-12-20T10:12:13.123456789Z
to_epoch() {
  local ts="${1:-}"
  if [[ -z "$ts" || "$ts" == "0001-01-01T00:00:00Z" ]]; then
    echo 0
    return
  fi
  # GNU date can parse this; cut fractional seconds for portability.
  ts="${ts%%.*}Z"
  date -u -d "$ts" +%s 2>/dev/null || echo 0
}

# Get unique compose projects currently present (running or exited)
get_projects() {
  docker ps -a --format '{{.Label "com.docker.compose.project"}}' \
    | awk 'NF' | sort -u
}

# Find a representative container ID for a project (any container)
get_any_container_for_project() {
  local project="$1"
  docker ps -a --filter "label=com.docker.compose.project=${project}" -q | head -n1
}

# Build -f args from label com.docker.compose.project.config_files (comma-separated)
build_compose_file_args() {
  local config_files="$1"
  local -a args=()
  if [[ -n "${config_files:-}" ]]; then
    IFS=',' read -r -a files <<< "$config_files"
    for f in "${files[@]}"; do
      # Keep as-is; label usually contains absolute paths
      args+=( -f "$f" )
    done
  fi
  printf '%q ' "${args[@]}"
}

emit_service() {
  local state="$1" svcname="$2" perf="$3" text="$4"
  # Checkmk local check format: <status> <service_name> <perfdata> <text>
  echo "${state} \"${svcname}\" ${perf} ${text}"
}

run() {
  local now; now="$(now_epoch)"

  # If docker is not accessible, output a single UNKNOWN service (3)
  if ! docker info >/dev/null 2>&1; then
    emit_service 3 "docker_compose" "-" "Docker not accessible (permission or daemon down)"
    exit 0
  fi

  echo "<<<local:sep(0)>>>"
  local project
  while read -r project; do
    [[ -n "$project" ]] || continue

    local cid
    cid="$(get_any_container_for_project "$project")"
    if [[ -z "$cid" ]]; then
      continue
    fi

    # Discover working dir + compose config files from labels (most reliable)
    local wdir cfgfiles
    wdir="$(docker inspect -f '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}' "$cid" 2>/dev/null || true)"
    cfgfiles="$(docker inspect -f '{{ index .Config.Labels "com.docker.compose.project.config_files" }}' "$cid" 2>/dev/null || true)"

    # Build docker compose invocation
    local -a compose=( docker compose )
    # Prefer config_files label if present; it’s more precise than just "cd into wdir"
    if [[ -n "${cfgfiles:-}" ]]; then
      IFS=',' read -r -a files <<< "$cfgfiles"
      for f in "${files[@]}"; do
        compose+=( -f "$f" )
      done
    elif [[ -n "${wdir:-}" ]]; then
      compose+=( --project-directory "$wdir" )
    fi
    compose+=( --project-name "$project" )

    # Determine expected services from config
    # If config fails (e.g. missing files), fall back to "currently known services from labels"
    local expected_ok=1
    local -a expected_services=()
    if expected="$("${compose[@]}" config --services 2>/dev/null)"; then
      expected_ok=0
      while read -r s; do
        [[ -n "$s" ]] && expected_services+=( "$s" )
      done <<< "$expected"
    else
      # fallback: services from docker labels (less strict, but better than nothing)
      while read -r s; do
        [[ -n "$s" ]] && expected_services+=( "$s" )
      done < <(docker ps -a --filter "label=com.docker.compose.project=${project}" \
              --format '{{.Label "com.docker.compose.service"}}' | awk 'NF' | sort -u)
    fi

    # Emit a project-level service if config couldn't be parsed
    if [[ "$expected_ok" -ne 0 ]]; then
      emit_service 1 "Compose ${project} project" "-" "Could not parse compose config; falling back to container labels (wdir='${wdir:-?}')"
    fi

    # For each expected service, output one check per container instance
    local svc
    for svc in "${expected_services[@]}"; do
      # Get container ids for this service (can be multiple for scale)
      local -a cids=()
      while read -r x; do
        [[ -n "$x" ]] && cids+=( "$x" )
      done < <("${compose[@]}" ps -q "$svc" 2>/dev/null || true)

      if [[ "${#cids[@]}" -eq 0 ]]; then
        # Expected but missing -> CRIT
        emit_service 2 "Compose ${project} ${svc}" "creation=0 uptime=0 restarts=0" "Container missing (expected service not present)"
        continue
      fi

      local idx=0
      local scid
      for scid in "${cids[@]}"; do
        idx=$((idx+1))

        # Inspect container
        local running status created started restarts name
        name="$(docker inspect -f '{{.Name}}' "$scid" 2>/dev/null | sed 's#^/##')"
        image="$(docker inspect -f '{{.Config.Image}}' "$scid" 2>/dev/null || echo "unknown")"
        running="$(docker inspect -f '{{.State.Running}}' "$scid" 2>/dev/null || echo "false")"
        status="$(docker inspect -f '{{.State.Status}}' "$scid" 2>/dev/null || echo "unknown")"
        created="$(docker inspect -f '{{.Created}}' "$scid" 2>/dev/null || echo "")"
        started="$(docker inspect -f '{{.State.StartedAt}}' "$scid" 2>/dev/null || echo "")"
        restarts="$(docker inspect -f '{{.RestartCount}}' "$scid" 2>/dev/null || echo "0")"

        local created_epoch started_epoch created_age uptime
        created_epoch="$(to_epoch "$created")"
        started_epoch="$(to_epoch "$started")"
        created_age=0
        uptime=0
        if [[ "$created_epoch" -gt 0 ]]; then
          created_age=$(( now - created_epoch ))
          (( created_age < 0 )) && created_age=0
        fi
        if [[ "$running" == "true" && "$started_epoch" -gt 0 ]]; then
          uptime=$(( now - started_epoch ))
          (( uptime < 0 )) && uptime=0
        fi

        # Check state -> status code
        # 0 OK running
        # 2 CRIT missing / not running
        # 1 WARN for "restarting" etc.
        local state=2 msg=""
        if [[ "$running" == "true" ]]; then
          state=0
          msg="running"
        else
          # Treat "exited" and "dead" as CRIT, "restarting" as WARN
          if [[ "$status" == "restarting" ]]; then
            state=1
            msg="restarting"
          else
            state=2
            msg="$status"
          fi
        fi

        # One service per container
        local svcname="Compose ${project} ${svc}"
        if [[ "${#cids[@]}" -gt 1 ]]; then
          svcname="Compose ${project} ${svc} ${idx}"
        fi

        local perf
        perf="creation=${created_age}s uptime=${uptime}s restarts=${restarts}"

        emit_service "$state" "$svcname" "$perf" "${msg} (container=${name}, image=${image})"
      done
    done

  done < <(get_projects)

  exit 0
}

main() {
  case "${1:-}" in
    --detect)
      detect
      exit $?
      ;;
    "" )
      run
      ;;
    * )
      echo "Usage: $0 [--detect]" >&2
      exit 2
      ;;
  esac
}

main "$@"
