#!/usr/bin/env bash

#########################################################################################
#### contabo-cost-monitor.sh — Track and report Contabo spending via the REST API.   ####
#### Instance costs, snapshot usage, and alert thresholds with Prometheus output      ####
#### Requires: bash 4+, curl, jq                                                     ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 1.01                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   ./contabo-cost-monitor.sh --summary                                           ####
####                                                                                 ####
#### See --help for all options.                                                     ####
#########################################################################################

set -euo pipefail

# ── Colors (pre-initialized) ─────────────────────────────────────────
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""

setup_colors() {
    if [[ "${COLOR:-auto}" == "never" ]]; then
        return
    fi
    if [[ "${COLOR:-auto}" == "always" ]] || [[ -t 1 ]]; then
        RED='\033[0;31m'
        GREEN='\033[0;32m'
        YELLOW='\033[0;33m'
        BLUE='\033[0;34m'
        CYAN='\033[0;36m'
        BOLD='\033[1m'
        DIM='\033[2m'
        RESET='\033[0m'
    fi
}

# ── Logging ───────────────────────────────────────────────────────────
log()     { echo -e "${BLUE}[INFO]${RESET} $*"; }
warn()    { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
err()     { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }
die()     { err "$*"; exit 1; }

section_header() {
    echo ""
    echo -e "  ${BOLD}${CYAN}── $1 ──${RESET}"
    echo ""
}

field() {
    printf "  ${BOLD}%-22s${RESET} %s\n" "$1" "$2"
}

field_color() {
    printf "  ${BOLD}%-22s${RESET} %b\n" "$1" "$2"
}

elapsed() {
    local end_time
    end_time=$(date +%s)
    echo "$(( end_time - START_TIME ))s"
}

# ── Defaults ──────────────────────────────────────────────────────────
RUN_MODE=""
OUTPUT_FORMAT="${CCM_FORMAT:-table}"
ALERT_THRESHOLD="${CCM_ALERT:-0}"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"

# ── Credentials ───────────────────────────────────────────────────────
CONTABO_CLIENT_ID="${CONTABO_CLIENT_ID:-}"
CONTABO_CLIENT_SECRET="${CONTABO_CLIENT_SECRET:-}"
CONTABO_API_USER="${CONTABO_API_USER:-}"
CONTABO_API_PASS="${CONTABO_API_PASS:-}"

# ── State ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
START_TIME=""

# ── API helpers ──────────────────────────────────────────────────────
contabo_token() {
    local resp
    resp=$(curl -s -d "client_id=${CONTABO_CLIENT_ID}" \
        -d "client_secret=${CONTABO_CLIENT_SECRET}" \
        --data-urlencode "username=${CONTABO_API_USER}" \
        --data-urlencode "password=${CONTABO_API_PASS}" \
        -d "grant_type=password" \
        "https://auth.contabo.com/auth/realms/contabo/protocol/openid-connect/token")
    local token
    token=$(echo "$resp" | jq -r '.access_token // empty' 2>/dev/null)
    if [[ -z "$token" ]]; then
        die "Failed to obtain access token — check credentials"
    fi
    echo "$token"
}

contabo_api() {
    local method="$1" endpoint="$2"
    shift 2
    local attempt=0 max_attempts=3

    while (( attempt < max_attempts )); do
        local http_code
        http_code=$(curl -s -o /tmp/ccm_resp.json -w "%{http_code}" \
            -X "$method" \
            -H "Authorization: Bearer $(contabo_token)" \
            -H "Content-Type: application/json" \
            -H "x-request-id: $(cat /proc/sys/kernel/random/uuid 2>/dev/null || date +%s%N)" \
            "https://api.contabo.com/v1${endpoint}" "$@")

        verbose "API ${method} ${endpoint} → HTTP ${http_code}"

        if [[ "$http_code" == "429" ]]; then
            ((attempt++)) || true
            local wait=$(( attempt * 5 ))
            warn "Rate limited — retrying in ${wait}s (attempt ${attempt}/${max_attempts})"
            sleep "$wait"
            continue
        fi

        cat /tmp/ccm_resp.json
        return 0
    done

    err "API request failed after ${max_attempts} attempts: ${method} ${endpoint}"
    return 1
}

check_credentials() {
    [[ -z "$CONTABO_CLIENT_ID" ]] && die "CONTABO_CLIENT_ID not set"
    [[ -z "$CONTABO_CLIENT_SECRET" ]] && die "CONTABO_CLIENT_SECRET not set"
    [[ -z "$CONTABO_API_USER" ]] && die "CONTABO_API_USER not set"
    [[ -z "$CONTABO_API_PASS" ]] && die "CONTABO_API_PASS not set"
}

check_deps() {
    command -v curl &>/dev/null || die "curl is required"
    command -v jq &>/dev/null || die "jq is required"
}

# ── Pagination helper ────────────────────────────────────────────────
fetch_all_contabo() {
    local endpoint="$1" key="$2"
    local page=1 size=100 all_data="[]"
    while true; do
        local sep="?"
        [[ "$endpoint" == *"?"* ]] && sep="&"
        local resp
        resp=$(contabo_api GET "${endpoint}${sep}page=${page}&size=${size}")
        local page_data
        page_data=$(echo "$resp" | jq ".${key} // []" 2>/dev/null)
        local page_count
        page_count=$(echo "$page_data" | jq 'length' 2>/dev/null || echo 0)
        [[ "$page_count" -eq 0 ]] && break
        all_data=$(echo -e "${all_data}\n${page_data}" | jq -s 'add' 2>/dev/null)
        (( page_count < size )) && break
        ((page++)) || true
    done
    echo "$all_data"
}

# ══════════════════════════════════════════════════════════════════════
#                          SUMMARY
# ══════════════════════════════════════════════════════════════════════
do_summary() {
    local instances
    instances=$(fetch_all_contabo "/compute/instances" "data")
    local instance_count
    instance_count=$(echo "$instances" | jq 'length' 2>/dev/null || echo 0)
    local running_count
    running_count=$(echo "$instances" | jq '[.[] | select(.status == "running")] | length' 2>/dev/null || echo 0)

    local snapshots
    snapshots=$(fetch_all_contabo "/compute/snapshots" "data")
    local snapshot_count
    snapshot_count=$(echo "$snapshots" | jq 'length' 2>/dev/null || echo 0)

    local object_storage
    object_storage=$(fetch_all_contabo "/object-storages" "data")
    local storage_count
    storage_count=$(echo "$object_storage" | jq 'length' 2>/dev/null || echo 0)
    local storage_tb
    storage_tb=$(echo "$object_storage" | jq '[.[].totalPurchasedSpaceTB // 0] | add // 0' 2>/dev/null || echo 0)
    local storage_used_bytes
    storage_used_bytes=$(echo "$object_storage" | jq '[.[].usedSpaceBytes // 0] | add // 0' 2>/dev/null || echo 0)
    local storage_used_gb
    storage_used_gb=$(awk "BEGIN {printf \"%.1f\", ${storage_used_bytes} / 1073741824}")

    # Estimate costs from instance product IDs
    # Contabo uses fixed monthly pricing per product tier
    local instance_cost="0.00"
    while IFS=$'\t' read -r pid pname status; do
        [[ -z "$pid" ]] && continue
        # Extract monthly cost from product info if available
        local cost_per_month="0"
        # Contabo productId maps to fixed monthly rates
        # These are approximations — actual billing comes from the Contabo panel
        case "$pid" in
            V1)  cost_per_month="4.99" ;;
            V2)  cost_per_month="5.99" ;;
            V4)  cost_per_month="8.99" ;;
            V8)  cost_per_month="13.99" ;;
            V16) cost_per_month="19.99" ;;
            V24) cost_per_month="24.99" ;;
            V30) cost_per_month="29.99" ;;
            V45) cost_per_month="39.99" ;;
            V60) cost_per_month="49.99" ;;
            *)   cost_per_month="0" ;;
        esac
        instance_cost=$(awk "BEGIN {printf \"%.2f\", ${instance_cost} + ${cost_per_month}}")
    done < <(echo "$instances" | jq -r \
        '.[] | "\(.productId // "—")\t\(.name // .displayName // "unknown")\t\(.status // "—")"' \
        2>/dev/null)

    local total_cost="$instance_cost"

    # Alert check
    local alert_triggered="false"
    if [[ "$ALERT_THRESHOLD" != "0" ]]; then
        local over
        over=$(awk "BEGIN {print (${total_cost} > ${ALERT_THRESHOLD}) ? 1 : 0}")
        [[ "$over" == "1" ]] && alert_triggered="true"
    fi

    case "$OUTPUT_FORMAT" in
        json)
            jq -n \
                --argjson instances "$instance_count" \
                --argjson running "$running_count" \
                --argjson snapshots "$snapshot_count" \
                --argjson object_storage "$storage_count" \
                --arg storage_tb "$storage_tb" \
                --arg storage_used_gb "$storage_used_gb" \
                --arg instance_cost "$instance_cost" \
                --arg total_cost "$total_cost" \
                --arg alert_threshold "$ALERT_THRESHOLD" \
                --argjson alert_triggered "$alert_triggered" \
                '{
                    instances: $instances, running: $running,
                    snapshots: $snapshots,
                    object_storage: $object_storage,
                    storage_purchased_tb: ($storage_tb | tonumber),
                    storage_used_gb: ($storage_used_gb | tonumber),
                    monthly_estimate: {
                        instances: $instance_cost, total: $total_cost
                    },
                    alert: { threshold: $alert_threshold, triggered: $alert_triggered }
                }'
            ;;
        prometheus)
            cat <<EOF
# HELP contabo_cost_monthly_estimate_euros Estimated monthly cost in euros
# TYPE contabo_cost_monthly_estimate_euros gauge
contabo_cost_monthly_estimate_euros ${total_cost}
# HELP contabo_cost_instances_euros Estimated monthly instance cost
# TYPE contabo_cost_instances_euros gauge
contabo_cost_instances_euros ${instance_cost}
# HELP contabo_cost_instances_total Total instances
# TYPE contabo_cost_instances_total gauge
contabo_cost_instances_total ${instance_count}
# HELP contabo_cost_instances_running Running instances
# TYPE contabo_cost_instances_running gauge
contabo_cost_instances_running ${running_count}
# HELP contabo_cost_snapshots_total Total snapshots
# TYPE contabo_cost_snapshots_total gauge
contabo_cost_snapshots_total ${snapshot_count}
# HELP contabo_cost_object_storage_total Total object storage buckets
# TYPE contabo_cost_object_storage_total gauge
contabo_cost_object_storage_total ${storage_count}
# HELP contabo_cost_storage_used_bytes Object storage used bytes
# TYPE contabo_cost_storage_used_bytes gauge
contabo_cost_storage_used_bytes ${storage_used_bytes}
# HELP contabo_cost_alert_triggered Whether cost alert threshold exceeded
# TYPE contabo_cost_alert_triggered gauge
contabo_cost_alert_triggered $([ "$alert_triggered" == "true" ] && echo 1 || echo 0)
EOF
            ;;
        *)
            section_header "Contabo Cost Summary"

            printf "  ${BOLD}%-22s %8s %10s %12s${RESET}\n" \
                "RESOURCE" "COUNT" "USAGE" "MONTHLY €"
            printf "  %s\n" "$(printf '%.0s─' {1..56})"

            printf "  %-22s %8s %10s %12s\n" \
                "Instances (running)" "$running_count" "—" "${instance_cost}"
            printf "  %-22s %8s %10s %12s\n" \
                "Instances (total)" "$instance_count" "—" "—"
            printf "  %-22s %8s %10s %12s\n" \
                "Snapshots" "$snapshot_count" "—" "included"
            printf "  %-22s %8s %10s %12s\n" \
                "Object Storage" "$storage_count" "${storage_used_gb} GB" "included"

            printf "  %s\n" "$(printf '%.0s─' {1..56})"
            printf "  ${BOLD}%-22s %8s %10s %12s${RESET}\n" \
                "TOTAL" "" "" "${total_cost}"

            if [[ "$alert_triggered" == "true" ]]; then
                echo ""
                echo -e "  ${RED}⚠ ALERT: Monthly estimate €${total_cost} exceeds threshold €${ALERT_THRESHOLD}${RESET}"
            fi
            ;;
    esac
}

# ══════════════════════════════════════════════════════════════════════
#                          BREAKDOWN
# ══════════════════════════════════════════════════════════════════════
do_breakdown() {
    local instances
    instances=$(fetch_all_contabo "/compute/instances" "data")
    local instance_count
    instance_count=$(echo "$instances" | jq 'length' 2>/dev/null || echo 0)
    [[ "$instance_count" -eq 0 ]] && die "No instances found"

    case "$OUTPUT_FORMAT" in
        json)
            echo "$instances" | jq '[.[] | {
                id: .instanceId, name: (.name // .displayName),
                status: .status, product: .productId,
                region: .region, ip: .ipConfig.v4.ip
            }]'
            ;;
        *)
            section_header "Instance Cost Breakdown"

            printf "  ${BOLD}%-10s %-20s %-8s %-10s %-10s %10s${RESET}\n" \
                "ID" "NAME" "PRODUCT" "STATUS" "REGION" "MONTHLY €"
            printf "  %s\n" "$(printf '%.0s─' {1..72})"

            while IFS=$'\t' read -r iid iname pid status region; do
                [[ -z "$iid" ]] && continue
                local cost_per_month="0.00"
                case "$pid" in
                    V1)  cost_per_month="4.99" ;;
                    V2)  cost_per_month="5.99" ;;
                    V4)  cost_per_month="8.99" ;;
                    V8)  cost_per_month="13.99" ;;
                    V16) cost_per_month="19.99" ;;
                    V24) cost_per_month="24.99" ;;
                    V30) cost_per_month="29.99" ;;
                    V45) cost_per_month="39.99" ;;
                    V60) cost_per_month="49.99" ;;
                    *)   cost_per_month="—" ;;
                esac

                local status_color="$GREEN"
                case "$status" in
                    running) status_color="$GREEN" ;;
                    stopped) status_color="$YELLOW" ;;
                    *)       status_color="$RED" ;;
                esac

                printf "  %-10s %-20s %-8s " "$iid" "${iname:0:18}" "$pid"
                echo -ne "${status_color}"
                printf "%-10s" "$status"
                echo -ne "${RESET}"
                printf " %-10s %10s\n" "${region:0:8}" "$cost_per_month"
            done < <(echo "$instances" | jq -r \
                '.[] | "\(.instanceId)\t\(.name // .displayName // "unknown")\t\(.productId // "—")\t\(.status // "—")\t\(.region // "—")"' \
                2>/dev/null)

            echo ""
            field "Instances:" "$instance_count"
            ;;
    esac
}

# ══════════════════════════════════════════════════════════════════════
#                          RESOURCES
# ══════════════════════════════════════════════════════════════════════
do_resources() {
    # Snapshots
    local snapshots
    snapshots=$(fetch_all_contabo "/compute/snapshots" "data")
    local snap_count
    snap_count=$(echo "$snapshots" | jq 'length' 2>/dev/null || echo 0)

    case "$OUTPUT_FORMAT" in
        json)
            local storage
            storage=$(fetch_all_contabo "/object-storages" "data")
            jq -n \
                --argjson snapshots "$snapshots" \
                --argjson object_storage "$storage" \
                '{snapshots: $snapshots, object_storage: $object_storage}'
            ;;
        *)
            if [[ "$snap_count" -gt 0 ]]; then
                section_header "Snapshots"
                printf "  ${BOLD}%-38s %-18s %-20s${RESET}\n" \
                    "SNAPSHOT_ID" "NAME" "CREATED"
                printf "  %s\n" "$(printf '%.0s─' {1..78})"

                echo "$snapshots" | jq -r \
                    '.[] | "\(.snapshotId // .id // "—")\t\(.name // "—")\t\(.createdDate // "—")"' \
                    2>/dev/null \
                    | while IFS=$'\t' read -r sid sname screated; do
                        printf "  %-38s %-18s %-20s\n" \
                            "${sid:0:36}" "${sname:0:16}" "${screated:0:19}"
                    done
                echo ""
                field "Snapshots:" "$snap_count"
            fi

            # Object Storage
            local storage
            storage=$(fetch_all_contabo "/object-storages" "data")
            local storage_count
            storage_count=$(echo "$storage" | jq 'length' 2>/dev/null || echo 0)

            if [[ "$storage_count" -gt 0 ]]; then
                section_header "Object Storage"
                printf "  ${BOLD}%-38s %-10s %-12s %-12s${RESET}\n" \
                    "STORAGE_ID" "REGION" "SIZE (TB)" "USED (GB)"
                printf "  %s\n" "$(printf '%.0s─' {1..74})"

                echo "$storage" | jq -r \
                    '.[] | "\(.objectStorageId // .id // "—")\t\(.region // "—")\t\(.totalPurchasedSpaceTB // 0)\t\(.usedSpaceBytes // 0)"' \
                    2>/dev/null \
                    | while IFS=$'\t' read -r oid oregion osize oused; do
                        local used_gb
                        used_gb=$(awk "BEGIN {printf \"%.1f\", ${oused} / 1073741824}")
                        printf "  %-38s %-10s %-12s %-12s\n" \
                            "${oid:0:36}" "${oregion:0:8}" "$osize" "$used_gb"
                    done
                echo ""
                field "Object Storage:" "$storage_count"
            fi
            ;;
    esac
}

# ══════════════════════════════════════════════════════════════════════
#                          HELP
# ══════════════════════════════════════════════════════════════════════
show_help() {
    cat <<EOF
${BOLD}${SCRIPT_NAME}${RESET} — Contabo Cost Monitor

Track and report Contabo spending via the REST API.

${BOLD}MODES${RESET}
  --summary         Cost summary across all resources (default)
  --breakdown       Per-instance cost breakdown
  --resources       List all billable resources (snapshots, object storage)

${BOLD}OPTIONS${RESET}
  --format FMT      Output: table, json, prometheus (default: table)
  --alert EUROS     Alert if monthly estimate exceeds threshold
  --verbose         Debug output
  --no-color        Disable colored output
  --help            Show this help message

${BOLD}ENVIRONMENT VARIABLES${RESET}
  CONTABO_CLIENT_ID      OAuth2 Client ID (required)
  CONTABO_CLIENT_SECRET  OAuth2 Client Secret (required)
  CONTABO_API_USER       API username / email (required)
  CONTABO_API_PASS       API password (required)
  CCM_FORMAT             Default output format
  CCM_ALERT              Default alert threshold in euros
  VERBOSE                Enable verbose output (true/false)
  COLOR                  Color mode: auto, always, never

${BOLD}EXAMPLES${RESET}
  # Cost summary
  ${SCRIPT_NAME} --summary

  # Per-instance breakdown
  ${SCRIPT_NAME} --breakdown

  # List all resources
  ${SCRIPT_NAME} --resources

  # JSON output
  ${SCRIPT_NAME} --summary --format json

  # Alert if monthly cost exceeds €100
  ${SCRIPT_NAME} --summary --alert 100

  # Prometheus metrics
  ${SCRIPT_NAME} --summary --format prometheus

  # Cron — hourly cost metrics
  0 * * * * /usr/local/bin/contabo-cost-monitor.sh --summary --format prometheus --no-color > /var/lib/node_exporter/textfile/contabo_cost.prom 2>/dev/null

${BOLD}NOTES${RESET}
  Contabo uses fixed monthly pricing per product tier.
  Cost estimates are based on productId mapping — verify against your invoice.
  Snapshots and object storage are typically included in Contabo plans.

${BOLD}EXIT CODES${RESET}
  0   Success
  1   Runtime error
EOF
}

# ══════════════════════════════════════════════════════════════════════
#                          PARSE ARGS
# ══════════════════════════════════════════════════════════════════════
parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --summary)     RUN_MODE="summary"; shift ;;
            --breakdown)   RUN_MODE="breakdown"; shift ;;
            --resources)   RUN_MODE="resources"; shift ;;
            --format)      OUTPUT_FORMAT="${2:?--format requires a value}"; shift 2 ;;
            --alert)       ALERT_THRESHOLD="${2:?--alert requires a threshold}"; shift 2 ;;
            --verbose)     VERBOSE="true"; shift ;;
            --no-color)    COLOR="never"; shift ;;
            --help|-h)     setup_colors; show_help; exit 0 ;;
            *)             die "Unknown option: $1 (see --help)" ;;
        esac
    done
}

# ══════════════════════════════════════════════════════════════════════
#                          MAIN
# ══════════════════════════════════════════════════════════════════════
main() {
    parse_args "$@"
    setup_colors

    if [[ -z "$RUN_MODE" ]]; then
        RUN_MODE="summary"
    fi

    check_deps
    check_credentials

    START_TIME=$(date +%s)

    case "$RUN_MODE" in
        summary)   do_summary ;;
        breakdown) do_breakdown ;;
        resources) do_resources ;;
        *)         die "Unknown mode: ${RUN_MODE}" ;;
    esac

    if [[ "$OUTPUT_FORMAT" != "prometheus" ]]; then
        echo ""
        field "Duration:" "$(elapsed)"
    fi
}

main "$@"
