#!/usr/bin/env bash

#########################################################################################
#### azure-blob-manager.sh — Manage Azure Blob Storage containers, lifecycle, and    ####
#### access auditing via az CLI. Upload, sync, tier, and audit blob storage           ####
#### Requires: bash 4+, az CLI, jq                                                  ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 1.01                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   ./azure-blob-manager.sh --list                                                ####
####                                                                                 ####
#### 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"
}

# ── Severity counters (for audit mode) ───────────────────────────────
TOTAL_CRIT=0
TOTAL_WARN=0
TOTAL_INFO=0
TOTAL_OK=0

flag_crit() { ((TOTAL_CRIT++)) || true; }
flag_warn() { ((TOTAL_WARN++)) || true; }
flag_info() { ((TOTAL_INFO++)) || true; }
flag_ok()   { ((TOTAL_OK++)) || true; }

# ── Defaults ──────────────────────────────────────────────────────────
RUN_MODE=""
STORAGE_ACCOUNT=""
CONTAINER_NAME=""
RESOURCE_GROUP=""
OUTPUT_FORMAT="${ABM_FORMAT:-text}"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"
MAX_AGE="${ABM_MAX_AGE:-90}"
TIER_TARGET=""
SOURCE_PATH=""
SUBSCRIPTION=""

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

# ── Dependency and credential checks ────────────────────────────────
check_deps() {
    command -v az &>/dev/null || die "az CLI is required (install: https://aka.ms/InstallAzureCLIDeb)"
    command -v jq &>/dev/null || die "jq is required"
}

check_credentials() {
    local acct
    acct=$(az account show --output json 2>&1) || die "Azure credentials not configured — run 'az login'"

    local sub_name
    sub_name=$(echo "$acct" | jq -r '.name')
    log "Subscription: ${sub_name}"

    if [[ -n "$SUBSCRIPTION" ]]; then
        az account set --subscription "$SUBSCRIPTION" 2>/dev/null \
            || die "Cannot switch to subscription: ${SUBSCRIPTION}"
    fi
}

# ── Azure CLI wrapper ────────────────────────────────────────────────
az_cmd() {
    local args=("$@")
    [[ -n "$SUBSCRIPTION" ]] && args+=(--subscription "$SUBSCRIPTION")
    verbose "az ${args[*]}"
    az "${args[@]}"
}

# ══════════════════════════════════════════════════════════════════════
#                          LIST
# ══════════════════════════════════════════════════════════════════════
do_list() {
    if [[ -n "$STORAGE_ACCOUNT" && -n "$CONTAINER_NAME" ]]; then
        list_blobs
    elif [[ -n "$STORAGE_ACCOUNT" ]]; then
        list_containers
    else
        list_accounts
    fi
}

list_accounts() {
    section_header "Storage Accounts"

    local accounts
    local args=(storage account list --output json)
    [[ -n "$RESOURCE_GROUP" ]] && args+=(--resource-group "$RESOURCE_GROUP")
    accounts=$(az_cmd "${args[@]}" 2>/dev/null)

    local count
    count=$(echo "$accounts" | jq 'length')

    printf "  %-28s %-16s %-12s %-12s %s\n" \
        "ACCOUNT" "RESOURCE_GROUP" "KIND" "REPLICATION" "LOCATION"
    printf "  %s\n" "$(printf '%.0s─' {1..90})"

    echo "$accounts" | jq -c '.[]' | while IFS= read -r acct; do
        local name rg kind repl location
        name=$(echo "$acct" | jq -r '.name')
        rg=$(echo "$acct" | jq -r '.resourceGroup')
        kind=$(echo "$acct" | jq -r '.kind')
        repl=$(echo "$acct" | jq -r '.sku.name')
        location=$(echo "$acct" | jq -r '.location')

        printf "  %-28s %-16s %-12s %-12s %s\n" \
            "${name:0:27}" "${rg:0:15}" "${kind:0:11}" "${repl:0:11}" "$location"
    done

    echo ""
    field "Total accounts:" "$count"
}

list_containers() {
    section_header "Containers in ${STORAGE_ACCOUNT}"

    local containers
    containers=$(az_cmd storage container list \
        --account-name "$STORAGE_ACCOUNT" --auth-mode login \
        --output json 2>/dev/null) || die "Failed to list containers — check permissions"

    local count
    count=$(echo "$containers" | jq 'length')

    printf "  %-32s %-16s %-12s %s\n" \
        "CONTAINER" "PUBLIC_ACCESS" "LEASE_STATE" "LAST_MODIFIED"
    printf "  %s\n" "$(printf '%.0s─' {1..80})"

    echo "$containers" | jq -c '.[]' | while IFS= read -r ctr; do
        local name public_access lease_state last_mod
        name=$(echo "$ctr" | jq -r '.name')
        public_access=$(echo "$ctr" | jq -r '.properties.publicAccess // "none"')
        lease_state=$(echo "$ctr" | jq -r '.properties.leaseState // "available"')
        last_mod=$(echo "$ctr" | jq -r '.properties.lastModified // ""' | cut -dT -f1)

        printf "  %-32s %-16s %-12s %s\n" \
            "${name:0:31}" "$public_access" "$lease_state" "$last_mod"
    done

    echo ""
    field "Total containers:" "$count"
}

list_blobs() {
    section_header "Blobs in ${STORAGE_ACCOUNT}/${CONTAINER_NAME}"

    local blobs
    blobs=$(az_cmd storage blob list \
        --account-name "$STORAGE_ACCOUNT" --container-name "$CONTAINER_NAME" \
        --auth-mode login --output json 2>/dev/null) \
        || die "Failed to list blobs — check permissions"

    local count
    count=$(echo "$blobs" | jq 'length')

    printf "  %-40s %-12s %-8s %s\n" \
        "NAME" "SIZE" "TIER" "LAST_MODIFIED"
    printf "  %s\n" "$(printf '%.0s─' {1..80})"

    echo "$blobs" | jq -c '.[]' | while IFS= read -r blob; do
        local name size tier last_mod size_str
        name=$(echo "$blob" | jq -r '.name')
        size=$(echo "$blob" | jq -r '.properties.contentLength // 0')
        tier=$(echo "$blob" | jq -r '.properties.blobTier // "N/A"')
        last_mod=$(echo "$blob" | jq -r '.properties.lastModified // ""' | cut -dT -f1)

        if (( size > 1073741824 )); then
            size_str="$(( size / 1073741824 )) GB"
        elif (( size > 1048576 )); then
            size_str="$(( size / 1048576 )) MB"
        elif (( size > 1024 )); then
            size_str="$(( size / 1024 )) KB"
        else
            size_str="${size} B"
        fi

        printf "  %-40s %-12s %-8s %s\n" \
            "${name:0:39}" "$size_str" "$tier" "$last_mod"
    done

    echo ""
    field "Total blobs:" "$count"
}

# ══════════════════════════════════════════════════════════════════════
#                          AUDIT
# ══════════════════════════════════════════════════════════════════════
do_audit() {
    section_header "Storage Security Audit"

    local accounts
    local args=(storage account list --output json)
    [[ -n "$RESOURCE_GROUP" ]] && args+=(--resource-group "$RESOURCE_GROUP")
    accounts=$(az_cmd "${args[@]}" 2>/dev/null)

    printf "  %-28s %-16s %-14s %-14s %s\n" \
        "ACCOUNT" "HTTPS_ONLY" "PUBLIC_BLOB" "NETWORK_RULES" "SEVERITY"
    printf "  %s\n" "$(printf '%.0s─' {1..95})"

    echo "$accounts" | jq -c '.[]' | while IFS= read -r acct; do
        local name https_only public_access net_default
        name=$(echo "$acct" | jq -r '.name')
        https_only=$(echo "$acct" | jq -r '.enableHttpsTrafficOnly // true')
        public_access=$(echo "$acct" | jq -r '.allowBlobPublicAccess // false')
        net_default=$(echo "$acct" | jq -r '.networkRuleSet.defaultAction // "Allow"')

        local severity="OK" color="$GREEN"

        if [[ "$public_access" == "true" ]]; then
            severity="CRITICAL"; color="$RED"; flag_crit
        elif [[ "$https_only" != "true" ]]; then
            severity="WARN"; color="$YELLOW"; flag_warn
        elif [[ "$net_default" == "Allow" ]]; then
            severity="WARN"; color="$YELLOW"; flag_warn
        else
            flag_ok
        fi

        printf "  %-28s %-16s %-14s %-14s %b%s%b\n" \
            "${name:0:27}" "$https_only" "$public_access" "$net_default" \
            "$color" "$severity" "$RESET"
    done

    echo ""

    # Check individual containers for public access
    log "Checking container-level public access..."
    echo ""

    echo "$accounts" | jq -r '.[].name' | while IFS= read -r acct_name; do
        local containers
        containers=$(az_cmd storage container list \
            --account-name "$acct_name" --auth-mode login \
            --output json 2>/dev/null) || continue

        echo "$containers" | jq -c '.[]' | while IFS= read -r ctr; do
            local ctr_name public_access
            ctr_name=$(echo "$ctr" | jq -r '.name')
            public_access=$(echo "$ctr" | jq -r '.properties.publicAccess // "none"')

            if [[ "$public_access" != "none" && "$public_access" != "null" ]]; then
                printf "  %-28s %-28s %-14s %b%s%b\n" \
                    "${acct_name:0:27}" "${ctr_name:0:27}" "$public_access" \
                    "$RED" "CRITICAL" "$RESET"
                flag_crit
            fi
        done
    done

    print_summary
}

# ══════════════════════════════════════════════════════════════════════
#                          SYNC
# ══════════════════════════════════════════════════════════════════════
do_sync() {
    [[ -z "$STORAGE_ACCOUNT" ]] && die "--sync requires --account"
    [[ -z "$CONTAINER_NAME" ]] && die "--sync requires --container"
    [[ -z "$SOURCE_PATH" ]] && die "--sync requires --source PATH"
    [[ -d "$SOURCE_PATH" ]] || die "Source path does not exist: ${SOURCE_PATH}"

    section_header "Syncing to ${STORAGE_ACCOUNT}/${CONTAINER_NAME}"
    field "Source:" "$SOURCE_PATH"
    echo ""

    if az_cmd storage blob upload-batch \
        --account-name "$STORAGE_ACCOUNT" \
        --destination "$CONTAINER_NAME" \
        --source "$SOURCE_PATH" \
        --auth-mode login \
        --overwrite 2>/dev/null; then
        echo -e "  ${GREEN}✓${RESET} Sync complete"
    else
        die "Sync failed"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                          TIER
# ══════════════════════════════════════════════════════════════════════
do_tier() {
    [[ -z "$STORAGE_ACCOUNT" ]] && die "--tier requires --account"
    [[ -z "$CONTAINER_NAME" ]] && die "--tier requires --container"
    [[ -z "$TIER_TARGET" ]] && die "--tier requires --set-tier TIER"

    section_header "Changing Blob Tier"
    field "Account:" "$STORAGE_ACCOUNT"
    field "Container:" "$CONTAINER_NAME"
    field "Target tier:" "$TIER_TARGET"
    echo ""

    local blobs
    blobs=$(az_cmd storage blob list \
        --account-name "$STORAGE_ACCOUNT" --container-name "$CONTAINER_NAME" \
        --auth-mode login --output json 2>/dev/null) \
        || die "Failed to list blobs"

    local changed=0 errors=0

    echo "$blobs" | jq -r '.[].name' | while IFS= read -r blob_name; do
        if az_cmd storage blob set-tier \
            --account-name "$STORAGE_ACCOUNT" --container-name "$CONTAINER_NAME" \
            --name "$blob_name" --tier "$TIER_TARGET" \
            --auth-mode login 2>/dev/null; then
            echo -e "  ${GREEN}✓${RESET} ${blob_name} → ${TIER_TARGET}"
            ((changed++)) || true
        else
            echo -e "  ${RED}✗${RESET} ${blob_name} — failed"
            ((errors++)) || true
        fi
    done

    echo ""
    field "Changed:" "$changed"
    [[ "$errors" -gt 0 ]] && field_color "Errors:" "${RED}${errors}${RESET}"
}

# ══════════════════════════════════════════════════════════════════════
#                          LIFECYCLE
# ══════════════════════════════════════════════════════════════════════
do_lifecycle() {
    [[ -z "$STORAGE_ACCOUNT" ]] && die "--lifecycle requires --account"
    [[ -z "$RESOURCE_GROUP" ]] && die "--lifecycle requires --resource-group"

    section_header "Lifecycle Management Policy"
    field "Account:" "$STORAGE_ACCOUNT"
    echo ""

    local policy
    policy=$(az_cmd storage account management-policy show \
        --account-name "$STORAGE_ACCOUNT" --resource-group "$RESOURCE_GROUP" \
        --output json 2>/dev/null)

    if [[ -z "$policy" || "$policy" == "null" ]]; then
        log "No lifecycle policy configured"
    else
        echo "$policy" | jq '.policy.rules[] | {name: .name, type: .type, definition: .definition}'
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                          STATS
# ══════════════════════════════════════════════════════════════════════
do_stats() {
    section_header "Storage Statistics"

    local accounts
    local args=(storage account list --output json)
    [[ -n "$RESOURCE_GROUP" ]] && args+=(--resource-group "$RESOURCE_GROUP")
    accounts=$(az_cmd "${args[@]}" 2>/dev/null)

    local total_accounts
    total_accounts=$(echo "$accounts" | jq 'length')

    printf "  %-28s %-16s %-12s %s\n" \
        "ACCOUNT" "LOCATION" "KIND" "REPLICATION"
    printf "  %s\n" "$(printf '%.0s─' {1..75})"

    echo "$accounts" | jq -c '.[]' | while IFS= read -r acct; do
        local name location kind repl
        name=$(echo "$acct" | jq -r '.name')
        location=$(echo "$acct" | jq -r '.location')
        kind=$(echo "$acct" | jq -r '.kind')
        repl=$(echo "$acct" | jq -r '.sku.name')

        printf "  %-28s %-16s %-12s %s\n" \
            "${name:0:27}" "$location" "${kind:0:11}" "$repl"
    done

    echo ""
    field "Total accounts:" "$total_accounts"
}

# ══════════════════════════════════════════════════════════════════════
#                          SUMMARY
# ══════════════════════════════════════════════════════════════════════
print_summary() {
    echo ""
    echo "  ══════════════════════════════════════════"
    echo "  Storage Audit Summary"
    echo "  ══════════════════════════════════════════"
    printf "  %-20s %b%d%b\n" "CRITICAL:" "$RED" "$TOTAL_CRIT" "$RESET"
    printf "  %-20s %b%d%b\n" "WARN:" "$YELLOW" "$TOTAL_WARN" "$RESET"
    printf "  %-20s %b%d%b\n" "INFO:" "$CYAN" "$TOTAL_INFO" "$RESET"
    printf "  %-20s %b%d%b\n" "OK:" "$GREEN" "$TOTAL_OK" "$RESET"
    echo "  ──────────────────────────────────────────"
    echo ""

    if [[ "$TOTAL_CRIT" -gt 0 ]]; then
        echo -e "  ${RED}${BOLD}Action required:${RESET} ${TOTAL_CRIT} critical finding(s)"
        echo ""
        echo "  Top recommendations:"
        echo "    • Disable public blob access on all storage accounts"
        echo "    • Set container access level to private"
        echo "    • Enable HTTPS-only traffic"
        echo "    • Configure network rules to restrict access"
        echo ""
    elif [[ "$TOTAL_WARN" -gt 0 ]]; then
        echo -e "  ${YELLOW}Review recommended:${RESET} ${TOTAL_WARN} warning(s)"
        echo ""
    else
        echo -e "  ${GREEN}All checks passed${RESET}"
        echo ""
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                          HELP
# ══════════════════════════════════════════════════════════════════════
show_help() {
    cat <<EOF
${BOLD}${SCRIPT_NAME}${RESET} — Azure Blob Manager

Manage Azure Blob Storage containers, lifecycle policies, and
security auditing via az CLI.

${BOLD}MODES${RESET}
  --list              List storage accounts, containers, or blobs
  --audit             Audit storage security (public access, HTTPS, network rules)
  --sync              Upload local directory to a container
  --tier              Change blob access tier
  --lifecycle         Show lifecycle management policy
  --stats             Storage statistics summary

${BOLD}TARGETING${RESET}
  --account NAME      Target storage account
  --container NAME    Target container
  --resource-group RG Limit to resource group

${BOLD}OPTIONS${RESET}
  --set-tier TIER     Target tier: Hot, Cool, Cold, Archive
  --max-age DAYS      Flag blobs older than N days in audit (default: 90)
  --source PATH       Local directory for sync
  --format FMT        Output format: text (default)
  --subscription SUB  Azure subscription name or ID
  --verbose           Debug output
  --no-color          Disable colored output
  --help              Show this help message

${BOLD}ENVIRONMENT VARIABLES${RESET}
  ABM_FORMAT           Default output format
  ABM_MAX_AGE          Default stale threshold for audit (days)
  VERBOSE              Enable verbose output (true/false)
  COLOR                Color mode: auto, always, never

${BOLD}EXAMPLES${RESET}
  # List all storage accounts
  ${SCRIPT_NAME} --list

  # List containers in an account
  ${SCRIPT_NAME} --list --account mystorageacct

  # List blobs in a container
  ${SCRIPT_NAME} --list --account mystorageacct --container backups

  # Audit storage security
  ${SCRIPT_NAME} --audit

  # Sync local directory to container
  ${SCRIPT_NAME} --sync --account mystorageacct --container uploads --source /data/uploads

  # Change tier for all blobs in a container
  ${SCRIPT_NAME} --tier --account mystorageacct --container archive --set-tier Cool

  # Show lifecycle policy
  ${SCRIPT_NAME} --lifecycle --account mystorageacct --resource-group prod-rg

  # Storage statistics
  ${SCRIPT_NAME} --stats

${BOLD}EXIT CODES${RESET}
  0   Success
  1   Runtime error
  2   Critical findings (audit mode)
EOF
}

# ══════════════════════════════════════════════════════════════════════
#                          PARSE ARGS
# ══════════════════════════════════════════════════════════════════════
parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --list)            RUN_MODE="list"; shift ;;
            --audit)           RUN_MODE="audit"; shift ;;
            --sync)            RUN_MODE="sync"; shift ;;
            --tier)            RUN_MODE="tier"; shift ;;
            --lifecycle)       RUN_MODE="lifecycle"; shift ;;
            --stats)           RUN_MODE="stats"; shift ;;
            --account)         STORAGE_ACCOUNT="${2:?--account requires a name}"; shift 2 ;;
            --container)       CONTAINER_NAME="${2:?--container requires a name}"; shift 2 ;;
            --resource-group)  RESOURCE_GROUP="${2:?--resource-group requires a value}"; shift 2 ;;
            --set-tier)        TIER_TARGET="${2:?--set-tier requires a tier}"; shift 2 ;;
            --max-age)         MAX_AGE="${2:?--max-age requires days}"; shift 2 ;;
            --source)          SOURCE_PATH="${2:?--source requires a path}"; shift 2 ;;
            --format)          OUTPUT_FORMAT="${2:?--format requires a value}"; shift 2 ;;
            --subscription)    SUBSCRIPTION="${2:?--subscription requires a value}"; 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
        err "No mode specified"
        echo ""
        show_help
        exit 1
    fi

    check_deps
    check_credentials

    START_TIME=$(date +%s)

    case "$RUN_MODE" in
        list)      do_list ;;
        audit)     do_audit ;;
        sync)      do_sync ;;
        tier)      do_tier ;;
        lifecycle) do_lifecycle ;;
        stats)     do_stats ;;
        *)         die "Unknown mode: ${RUN_MODE}" ;;
    esac

    echo ""
    field "Duration:" "$(elapsed)"

    if [[ "$RUN_MODE" == "audit" && "$TOTAL_CRIT" -gt 0 ]]; then
        exit 2
    fi
}

main "$@"
