#!/usr/bin/env bash
# List Alibaba Cloud ECS instances with last-7-day CPU utilization summary.
set -euo pipefail

usage() {
  cat <<'EOF'
Usage: ecs-cpu-inventory.sh --profile <name> --region <region-id> [options]

Options:
  --output report|markdown|csv|json   default: report (writes to reports/)
  --report-dir <path>                 default: reports

Query ECS instances and 7-day CPUUtilization via aliyun CLI (International).
Requires: aliyun, jq
EOF
  exit 1
}

PROFILE=""
REGION=""
OUTPUT="report"
REPORT_DIR="reports"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --profile) PROFILE="$2"; shift 2 ;;
    --region) REGION="$2"; shift 2 ;;
    --output) OUTPUT="$2"; shift 2 ;;
    --report-dir) REPORT_DIR="$2"; shift 2 ;;
    -h|--help) usage ;;
    *) echo "Unknown option: $1" >&2; usage ;;
  esac
done

[[ -n "$PROFILE" && -n "$REGION" ]] || usage
command -v aliyun >/dev/null 2>&1 || { echo "aliyun CLI not found" >&2; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq not found" >&2; exit 1; }

aliyun_cmd() {
  aliyun --profile "$PROFILE" --region "$REGION" "$@"
}

utc_now() {
  date -u +"%Y-%m-%dT%H:%M:%SZ"
}

utc_7d_ago() {
  if date -u -v-7d +"%Y-%m-%dT%H:%M:%SZ" >/dev/null 2>&1; then
    date -u -v-7d +"%Y-%m-%dT%H:%M:%SZ"
  else
    date -u -d '7 days ago' +"%Y-%m-%dT%H:%M:%SZ"
  fi
}

fetch_monthly_price() {
  local instance_type="$1"
  local disk_category resp price_json
  for disk_category in cloud_essd cloud_efficiency cloud; do
    if resp=$(aliyun_cmd ecs DescribePrice \
      --RegionId "$REGION" \
      --ResourceType instance \
      --InstanceType "$instance_type" \
      --PriceUnit Month \
      --Period 1 \
      --InternetChargeType PayByTraffic \
      --SystemDisk.Category "$disk_category" \
      --SystemDisk.Size 40 2>&1); then
      price_json=$(echo "$resp" | jq -c '{
        currency: .PriceInfo.Price.Currency,
        monthly_price: ([.PriceInfo.Price.DetailInfos.DetailInfo[]? | select(.Resource == "instanceType") | .TradePrice][0])
      }')
      echo "$price_json"
      return 0
    fi
  done
  echo '{"currency":"","monthly_price":""}'
  return 1
}

billing_cycles_between() {
  local start_ym="$1"
  local end_ym="$2"
  local y m ey em
  y=${start_ym%-*}
  m=${start_ym#*-}
  m=$((10#$m))
  ey=${end_ym%-*}
  em=${end_ym#*-}
  em=$((10#$em))
  while [[ $y -lt $ey ]] || [[ $y -eq $ey && $m -le $em ]]; do
    printf '%04d-%02d\n' "$y" "$m"
    m=$((m + 1))
    if [[ $m -gt 12 ]]; then
      m=1
      y=$((y + 1))
    fi
  done
}

fetch_instance_billed_cost() {
  local instance_id="$1"
  local created_time="$2"
  local start_ym="${created_time:0:7}"
  local end_ym total=0 currency="USD"

  if [[ -z "$start_ym" || ! "$start_ym" =~ ^[0-9]{4}-[0-9]{2}$ ]]; then
    echo '{"cost_since_launch":"","cost_currency":""}'
    return 0
  fi

  end_ym=$(date -u +"%Y-%m")
  while IFS= read -r cycle; do
    local next_token="" page_sum=0
    while true; do
      local -a args=(
        bssopenapi DescribeInstanceBill
        --BillingCycle "$cycle"
        --InstanceID "$instance_id"
        --ProductCode ecs
        --Granularity MONTHLY
        --MaxResults 300
      )
      [[ -n "$next_token" ]] && args+=(--NextToken "$next_token")

      local resp
      if ! resp=$(aliyun --profile "$PROFILE" "${args[@]}" 2>&1); then
        echo "Warning: DescribeInstanceBill failed for $instance_id cycle $cycle" >&2
        break
      fi

      currency=$(echo "$resp" | jq -r '[.Data.Items[]? | .Currency] | map(select(. != null and . != "")) | .[0] // "USD"')
      page_sum=$(echo "$resp" | jq '[.Data.Items[]? | (.PretaxAmount // 0 | tonumber)] | add // 0')
      total=$(echo "$total + $page_sum" | bc -l)

      next_token=$(echo "$resp" | jq -r '.Data.NextToken // empty')
      [[ -z "$next_token" || "$next_token" == "null" ]] && break
    done
  done < <(billing_cycles_between "$start_ym" "$end_ym")

  jq -n --argjson total "$(echo "$total" | jq -n --arg t "$total" '($t | tonumber * 100 | round / 100)')" --arg c "$currency" \
    '{cost_since_launch: $total, cost_currency: $c}'
}

enrich_unused_with_costs() {
  local rows="$1"
  local unused_rows enriched="[]" count=0 total_unused

  unused_rows=$(echo "$rows" | jq -c '[.[] | select(
    (.status == "Stopped") or
    ((.cpu_max_7d | tonumber? // null) != null and (.cpu_max_7d | tonumber) < 5)
  )]')
  total_unused=$(echo "$unused_rows" | jq 'length')

  while IFS= read -r row; do
    [[ -z "$row" ]] && continue
    count=$((count + 1))
    local iid created status cost_json merged
    iid=$(echo "$row" | jq -r '.instance_id')
    created=$(echo "$row" | jq -r '.created_time')
    status=$(echo "$row" | jq -r '.status')
    if [[ "$status" == "Stopped" ]]; then
      cost_json='{"cost_since_launch":"","cost_currency":""}'
    else
      echo "Fetching billed cost (${count}/${total_unused}): ${iid}..." >&2
      cost_json=$(fetch_instance_billed_cost "$iid" "$created")
    fi
    merged=$(echo "$row" | jq -c --argjson cost "$cost_json" '. + $cost')
    enriched=$(echo "$enriched" | jq -c --argjson item "$merged" '. + [$item]')
  done < <(echo "$unused_rows" | jq -c '.[]')

  echo "$rows" | jq -c --argjson patch "$(echo "$enriched" | jq 'map({key: .instance_id, value: {cost_since_launch: .cost_since_launch, cost_currency: .cost_currency}}) | from_entries')" '
    [.[] | . + ($patch[.instance_id] // {cost_since_launch: "", cost_currency: ""})]
  '
}

build_type_summary() {
  local rows="$1"
  local types_json summary="[]"
  types_json=$(echo "$rows" | jq -c '
    group_by(.instance_type)
    | map({
        instance_type: .[0].instance_type,
        count: length,
        cpu_cores: ((.[0].cpu_cores | tonumber?) // 0),
        memory_gb: (((.[0].memory_mb | tonumber?) // 0) / 1024 * 100 | round / 100)
      })
    | sort_by(-.cpu_cores, -.memory_gb, .instance_type)
  ')

  while IFS= read -r entry; do
    [[ -z "$entry" ]] && continue
    local itype price_entry merged
    itype=$(echo "$entry" | jq -r '.instance_type')
    price_entry=$(fetch_monthly_price "$itype" 2>/dev/null || echo '{"currency":"","monthly_price":""}')
    merged=$(echo "$entry" | jq -c --argjson price "$price_entry" '. + {
      currency: $price.currency,
      monthly_price: $price.monthly_price
    }')
    summary=$(echo "$summary" | jq -c --argjson item "$merged" '. + [$item]')
  done < <(echo "$types_json" | jq -c '.[]')

  echo "$summary"
}

render_report() {
  local rows="$1"
  local type_summary="$2"
  echo "$rows" | jq -r \
    --arg profile "$PROFILE" \
    --arg region "$REGION" \
    --arg account "$ACCOUNT_ID" \
    --arg today "$TODAY" \
    --arg start "$START_TIME" \
    --arg end "$END_TIME" \
    --argjson count "$COUNT" \
    --argjson type_summary "$type_summary" '
    def avg_key:
      if .cpu_avg_7d == "" or .cpu_avg_7d == null then 999999
      else (.cpu_avg_7d | tonumber) end;

    def is_unused:
      (.status == "Stopped") or
      ((.cpu_max_7d | tonumber? // null) != null and (.cpu_max_7d | tonumber) < 5);

    def unused_reason:
      if .status == "Stopped" and ((.cpu_max_7d | tonumber? // null) != null and (.cpu_max_7d | tonumber) < 5)
      then "stopped;cpu_max_7d<5%"
      elif .status == "Stopped" then "stopped"
      else "cpu_max_7d<5%" end;

    def fmt_price($v):
      if $v == "" or $v == null then ""
      else (($v | tonumber * 100 | round) / 100 | tostring) end;

    def fmt_cost:
      if .cost_since_launch == "" or .cost_since_launch == null then ""
      else "\(fmt_price(.cost_since_launch)) \(.cost_currency // "USD")" end;

    def unused_table($items):
      if ($items | length) == 0 then
        "| | | | | | | | | | | | | | |"
      else
        $items[] |
        "| \(.rank) | \(.reason) | \(.instance_id) | \(.instance_name) | \(.created_time) | \(.instance_type) | \(.cpu_cores) | \(.memory_mb) | \(.status) | \(.cpu_avg_7d) | \(.cpu_max_7d) | \(.cpu_min_7d) | \(fmt_cost) |"
      end;

    def all_table($items):
      $items[] |
      "| \(.rank) | \(.instance_id) | \(.instance_name) | \(.created_time) | \(.instance_type) | \(.cpu_cores) | \(.memory_mb) | \(.status) | \(.cpu_avg_7d) | \(.cpu_max_7d) | \(.cpu_min_7d) |";

    def type_summary_table($items):
      if ($items | length) == 0 then
        "| | | | | | |"
      else
        $items[] |
        "| \(.instance_type) | \(.cpu_cores) | \(.memory_gb) | \(.count) | \(fmt_price(.monthly_price)) \(.currency // "") |"
      end;

    def unused_cost_total($items):
      [$items[]
        | select(.status != "Stopped")
        | select(.cost_since_launch != "" and .cost_since_launch != null)
        | .cost_since_launch
        | tonumber] | add // 0;

    (. | sort_by(avg_key) | to_entries | map(.value + {rank: (.key + 1)})) as $sorted
    | ([.[] | select(is_unused) | . + {reason: unused_reason}]
       | sort_by(avg_key)
       | to_entries
       | map(.value + {rank: (.key + 1)})) as $unused
    | ($unused | [.[] | select(.cost_currency != null and .cost_currency != "") | .cost_currency] | .[0] // "USD") as $unused_currency
    | (unused_cost_total($unused)) as $unused_cost_total
    | "# ECS CPU Inventory — \($profile) / \($region) (\($today))",
      "",
      "**account_id**: `\($account)`",
      "**region**: `\($region)`",
      "**profile**: `\($profile)`",
      "**total instances**: \($count)",
      "",
      "## Instance Type 汇总",
      "",
      "按规格从大到小（vCPU → memory_gb）。月价：`ecs DescribePrice`，Region `\($region)`，`PriceUnit=Month`，ECS 计算资源单月成交价（不含系统盘/数据盘）。",
      "",
      "| instance_type | cpu_cores | memory_gb | count | monthly_price |",
      "|---------------|-----------|-----------|-------|---------------|",
      (type_summary_table($type_summary)),
      "",
      "**cpu window**: \($start) → \($end) (UTC)",
      "",
      "## 疑似无人使用",
      "",
      "判定：`cpu_max_7d < 5%` 或 `status = Stopped`",
      "",
      "累计费用：自创建所在账期起至统计日（`\($end)`）所在账期，`bssopenapi DescribeInstanceBill` 按实例 `PretaxAmount` 汇总（以账单为准，含退款/调账）。**Stopped 实例不计算 cost。**",
      "",
      "| rank | reason | instance_id | instance_name | created_time | instance_type | cpu_cores | memory_mb | status | cpu_avg_7d | cpu_max_7d | cpu_min_7d | cost_since_launch |",
      "|------|--------|-------------|---------------|--------------|---------------|-----------|-----------|--------|------------|------------|------------|-------------------|",
      (unused_table($unused)),
      "",
      "**cost_since_launch 合计**（仅统计上表中有费用的行，不含 Stopped）：\(fmt_price($unused_cost_total)) \($unused_currency)",
      "",
      "## 全部 ECS（按 CPU 使用率从低到高）",
      "",
      "排序字段：`cpu_avg_7d` 升序",
      "",
      "| rank | instance_id | instance_name | created_time | instance_type | cpu_cores | memory_mb | status | cpu_avg_7d | cpu_max_7d | cpu_min_7d |",
      "|------|-------------|---------------|--------------|---------------|-----------|-----------|--------|------------|------------|------------|",
      (all_table($sorted))
  '
}

START_TIME=$(utc_7d_ago)
END_TIME=$(utc_now)
TODAY=$(date -u +"%Y-%m-%d")

identity_json=""
if ! identity_json=$(aliyun_cmd sts GetCallerIdentity 2>&1); then
  echo "Failed sts GetCallerIdentity for profile '$PROFILE':" >&2
  echo "$identity_json" >&2
  exit 1
fi

ACCOUNT_ID=$(echo "$identity_json" | jq -r '.AccountId // .AccountUid // empty')

instances_json=""
if ! instances_json=$(aliyun_cmd ecs DescribeInstances --RegionId "$REGION" --pager 2>&1); then
  echo "Failed ecs DescribeInstances for region '$REGION':" >&2
  echo "$instances_json" >&2
  exit 1
fi

INSTANCES=$(echo "$instances_json" | jq -c '[.Instances.Instance[]?] | sort_by(.InstanceId)')
COUNT=$(echo "$INSTANCES" | jq 'length')

fetch_cpu_for_batch() {
  local ids_json="$1"
  local dims
  dims=$(echo "$ids_json" | jq -c '[.[] | {instanceId: .}]')

  local all_points="[]"
  local next_token=""

  while true; do
    local -a args=(
      cms DescribeMetricList
      --Namespace acs_ecs_dashboard
      --MetricName CPUUtilization
      --Period 3600
      --StartTime "$START_TIME"
      --EndTime "$END_TIME"
      --Dimensions "$dims"
      --Length 1440
    )
    if [[ -n "$next_token" ]]; then
      args+=(--NextToken "$next_token")
    fi

    local resp
    if ! resp=$(aliyun_cmd "${args[@]}" 2>&1); then
      echo "Warning: DescribeMetricList failed for batch: $resp" >&2
      break
    fi

    local raw_points
    raw_points=$(echo "$resp" | jq -r '.Datapoints // "[]"')
    if [[ -n "$raw_points" && "$raw_points" != "[]" && "$raw_points" != "null" ]]; then
      local parsed
      parsed=$(echo "$raw_points" | jq -c 'if type == "string" then fromjson else . end')
      all_points=$(jq -s 'add' <(echo "$all_points") <(echo "$parsed"))
    fi

    next_token=$(echo "$resp" | jq -r '.NextToken // empty')
    [[ -z "$next_token" || "$next_token" == "null" ]] && break
  done

  echo "$all_points"
}

ids=()
while IFS= read -r id; do
  [[ -n "$id" ]] && ids+=("$id")
done < <(echo "$INSTANCES" | jq -r '.[].InstanceId')

CPU_STATS='{}'
batch=()
for id in "${ids[@]}"; do
  batch+=("$id")
  if [[ ${#batch[@]} -ge 50 ]]; then
    batch_json=$(printf '%s\n' "${batch[@]}" | jq -R . | jq -s .)
    points=$(fetch_cpu_for_batch "$batch_json")
    batch_stats=$(echo "$points" | jq -c '
      [group_by(.instanceId)[] | {
        key: .[0].instanceId,
        value: {
          cpu_avg_7d: ((map(.Average | tonumber) | add / length * 10 | round) / 10),
          cpu_max_7d: ((map(.Maximum | tonumber) | max * 10 | round) / 10),
          cpu_min_7d: ((map(.Minimum | tonumber) | min * 10 | round) / 10)
        }
      }] | from_entries
    ')
    CPU_STATS=$(jq -s 'add' <(echo "$CPU_STATS") <(echo "$batch_stats"))
    batch=()
  fi
done

if [[ ${#batch[@]} -gt 0 ]]; then
  batch_json=$(printf '%s\n' "${batch[@]}" | jq -R . | jq -s .)
  points=$(fetch_cpu_for_batch "$batch_json")
  batch_stats=$(echo "$points" | jq -c '
    [group_by(.instanceId)[] | {
      key: .[0].instanceId,
      value: {
        cpu_avg_7d: ((map(.Average | tonumber) | add / length * 10 | round) / 10),
        cpu_max_7d: ((map(.Maximum | tonumber) | max * 10 | round) / 10),
        cpu_min_7d: ((map(.Minimum | tonumber) | min * 10 | round) / 10)
      }
    }] | from_entries
  ')
  CPU_STATS=$(jq -s 'add' <(echo "$CPU_STATS") <(echo "$batch_stats"))
fi

ROWS=$(echo "$INSTANCES" | jq -c --arg account "$ACCOUNT_ID" --arg region "$REGION" --argjson stats "$CPU_STATS" '
  [.[] | {
    instance_id: .InstanceId,
    instance_name: (.InstanceName // ""),
    account_id: $account,
    region: $region,
    created_time: (.CreationTime // ""),
    instance_type: (.InstanceType // ""),
    cpu_cores: (if .Cpu then (.Cpu | tostring) else "" end),
    memory_mb: (if .Memory then (.Memory | tostring) else "" end),
    status: (.Status // ""),
    cpu_avg_7d: ($stats[.InstanceId].cpu_avg_7d // ""),
    cpu_max_7d: ($stats[.InstanceId].cpu_max_7d // ""),
    cpu_min_7d: ($stats[.InstanceId].cpu_min_7d // "")
  }]
')

REPORT_FILE="${REPORT_DIR}/ecs-cpu-inventory-${PROFILE}-${REGION}-${TODAY}.md"

echo "Fetching monthly prices per instance type..." >&2
TYPE_SUMMARY=$(build_type_summary "$ROWS")

echo "Fetching billed costs for unused instances..." >&2
ROWS=$(enrich_unused_with_costs "$ROWS")

case "$OUTPUT" in
  json)
    jq -n --argjson rows "$ROWS" --argjson type_summary "$TYPE_SUMMARY" '{instances: $rows, type_summary: $type_summary}'
    ;;
  csv)
    echo "instance_id,instance_name,created_time,instance_type,cpu_cores,memory_mb,status,cpu_avg_7d,cpu_max_7d,cpu_min_7d"
    echo "$ROWS" | jq -r '.[] | [
      .instance_id, .instance_name, .created_time,
      .instance_type, .cpu_cores, .memory_mb, .status,
      .cpu_avg_7d, .cpu_max_7d, .cpu_min_7d
    ] | @csv'
    ;;
  markdown)
    render_report "$ROWS" "$TYPE_SUMMARY"
    ;;
  report)
    mkdir -p "$REPORT_DIR"
    render_report "$ROWS" "$TYPE_SUMMARY" > "$REPORT_FILE"
    echo "$REPORT_FILE"
    ;;
  *)
    echo "Unknown --output: $OUTPUT (use report, markdown, csv, or json)" >&2
    exit 1
    ;;
esac
