#!/usr/bin/env bash

#########################################################################################
#### lighthouse-audit.sh — Automated Lighthouse audits with Prometheus integration   ####
#### Runs headless Chrome via Lighthouse CLI, extracts scores with jq, pushes        ####
#### metrics to Pushgateway — saves HTML reports and color-codes output               ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 1.00                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   ./lighthouse-audit.sh https://mylinux.work                                    ####
####   ./lighthouse-audit.sh --file urls.txt --pushgateway http://localhost:9091      ####
####                                                                                 ####
#### See --help for all options.                                                     ####
#########################################################################################

set -euo pipefail

# ── Defaults ──────────────────────────────────────────────────────────
URLS_FILE=""
OUTPUT_DIR="${OUTPUT_DIR:-/var/lib/lighthouse/reports}"
PUSHGATEWAY_URL="${PUSHGATEWAY_URL:-}"
CHROME_FLAGS="--headless --no-sandbox --disable-dev-shm-usage --disable-gpu"
JOB_NAME="${JOB_NAME:-lighthouse}"
LIGHTHOUSE_TIMEOUT="${LIGHTHOUSE_TIMEOUT:-60}"
RUNS="${RUNS:-1}"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"

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

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

# ── Logging ───────────────────────────────────────────────────────────
log()     { echo -e "${CYAN}[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; }

# ── Cleanup ───────────────────────────────────────────────────────────
cleanup() {
    if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
        rm -rf "$TEMP_DIR"
    fi
}
trap cleanup EXIT

# ── Color-coded score output ─────────────────────────────────────────
color_score() {
    local score=$1
    if (( score >= 90 )); then
        echo -e "${GREEN}${score}${RESET}"
    elif (( score >= 50 )); then
        echo -e "${YELLOW}${score}${RESET}"
    else
        echo -e "${RED}${score}${RESET}"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       AUDIT
# ══════════════════════════════════════════════════════════════════════

run_audit() {
    local url="$1"
    local date_stamp
    date_stamp=$(date +%Y-%m-%d_%H%M%S)
    local slug
    slug=$(echo "$url" | sed 's|https\?://||;s|/|_|g;s|[^a-zA-Z0-9_.-]||g')

    log "Auditing: ${url}"

    local perf_total=0 a11y_total=0 bp_total=0 seo_total=0
    local lcp_total=0 cls_total=0 tbt_total=0
    local best_json=""
    local successful_runs=0

    for ((run=1; run<=RUNS; run++)); do
        [[ "$RUNS" -gt 1 ]] && log "  Run ${run}/${RUNS}"

        local json_file="${TEMP_DIR}/${slug}_run${run}.json"

        lighthouse "$url" \
            --output json \
            --output-path "$json_file" \
            --chrome-flags="$CHROME_FLAGS" \
            --max-wait-for-load "$((LIGHTHOUSE_TIMEOUT * 1000))" \
            --quiet 2>/dev/null || {
                warn "Lighthouse failed for ${url} (run ${run})"
                continue
            }

        local perf a11y bp seo lcp cls tbt
        perf=$(jq -r '.categories.performance.score * 100 | floor' "$json_file")
        a11y=$(jq -r '.categories.accessibility.score * 100 | floor' "$json_file")
        bp=$(jq -r '.categories["best-practices"].score * 100 | floor' "$json_file")
        seo=$(jq -r '.categories.seo.score * 100 | floor' "$json_file")
        lcp=$(jq -r '.audits["largest-contentful-paint"].numericValue / 1000' "$json_file")
        cls=$(jq -r '.audits["cumulative-layout-shift"].numericValue' "$json_file")
        tbt=$(jq -r '.audits["total-blocking-time"].numericValue' "$json_file")

        perf_total=$(echo "$perf_total + $perf" | bc)
        a11y_total=$(echo "$a11y_total + $a11y" | bc)
        bp_total=$(echo "$bp_total + $bp" | bc)
        seo_total=$(echo "$seo_total + $seo" | bc)
        lcp_total=$(echo "$lcp_total + $lcp" | bc)
        cls_total=$(echo "$cls_total + $cls" | bc)
        tbt_total=$(echo "$tbt_total + $tbt" | bc)

        successful_runs=$((successful_runs + 1))
        best_json="$json_file"
    done

    if [[ -z "$best_json" ]]; then
        warn "All runs failed for ${url}"
        return 1
    fi

    # Average scores
    local perf_avg a11y_avg bp_avg seo_avg lcp_avg cls_avg tbt_avg
    perf_avg=$(echo "$perf_total / $successful_runs" | bc)
    a11y_avg=$(echo "$a11y_total / $successful_runs" | bc)
    bp_avg=$(echo "$bp_total / $successful_runs" | bc)
    seo_avg=$(echo "$seo_total / $successful_runs" | bc)
    lcp_avg=$(echo "scale=2; $lcp_total / $successful_runs" | bc)
    cls_avg=$(echo "scale=3; $cls_total / $successful_runs" | bc)
    tbt_avg=$(echo "scale=0; $tbt_total / $successful_runs" | bc)

    # Generate HTML report from last run's JSON
    local report_file="${OUTPUT_DIR}/${slug}_${date_stamp}.html"
    lighthouse "$url" \
        --output html \
        --output-path "$report_file" \
        --chrome-flags="$CHROME_FLAGS" \
        --max-wait-for-load "$((LIGHTHOUSE_TIMEOUT * 1000))" \
        --quiet 2>/dev/null || true

    # Print results
    log "Results for ${url}:"
    printf "  ${BOLD}Performance:${RESET}    %s\n" "$(color_score "$perf_avg")"
    printf "  ${BOLD}Accessibility:${RESET}  %s\n" "$(color_score "$a11y_avg")"
    printf "  ${BOLD}Best Practices:${RESET} %s\n" "$(color_score "$bp_avg")"
    printf "  ${BOLD}SEO:${RESET}            %s\n" "$(color_score "$seo_avg")"
    printf "  LCP: %ss  CLS: %s  TBT: %sms\n" "$lcp_avg" "$cls_avg" "$tbt_avg"
    [[ -f "$report_file" ]] && log "Report: ${report_file}"

    # Push to Prometheus Pushgateway
    if [[ -n "$PUSHGATEWAY_URL" ]]; then
        local encoded_url
        encoded_url=$(echo "$url" | sed 's|/|%2F|g;s|:|%3A|g')

        cat <<METRICS | curl -s --data-binary @- "${PUSHGATEWAY_URL}/metrics/job/${JOB_NAME}/url/${encoded_url}"
# HELP lighthouse_performance_score Lighthouse performance score
# TYPE lighthouse_performance_score gauge
lighthouse_performance_score{url="${url}"} ${perf_avg}
# HELP lighthouse_accessibility_score Lighthouse accessibility score
# TYPE lighthouse_accessibility_score gauge
lighthouse_accessibility_score{url="${url}"} ${a11y_avg}
# HELP lighthouse_best_practices_score Lighthouse best practices score
# TYPE lighthouse_best_practices_score gauge
lighthouse_best_practices_score{url="${url}"} ${bp_avg}
# HELP lighthouse_seo_score Lighthouse SEO score
# TYPE lighthouse_seo_score gauge
lighthouse_seo_score{url="${url}"} ${seo_avg}
# HELP lighthouse_lcp_seconds Largest Contentful Paint in seconds
# TYPE lighthouse_lcp_seconds gauge
lighthouse_lcp_seconds{url="${url}"} ${lcp_avg}
# HELP lighthouse_cls_score Cumulative Layout Shift score
# TYPE lighthouse_cls_score gauge
lighthouse_cls_score{url="${url}"} ${cls_avg}
# HELP lighthouse_tbt_milliseconds Total Blocking Time in milliseconds
# TYPE lighthouse_tbt_milliseconds gauge
lighthouse_tbt_milliseconds{url="${url}"} ${tbt_avg}
METRICS
        log "Pushed metrics to ${PUSHGATEWAY_URL}"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       USAGE
# ══════════════════════════════════════════════════════════════════════

usage() {
    cat <<EOF
${SCRIPT_NAME} — Automated Lighthouse audits with Prometheus integration

USAGE:
  ${SCRIPT_NAME} [OPTIONS] [URL...]
  ${SCRIPT_NAME} --file urls.txt [OPTIONS]
  echo "https://example.com" | ${SCRIPT_NAME}

OPTIONS:
  --file FILE           Read URLs from file (one per line)
  --output-dir DIR      Directory for HTML reports (default: ${OUTPUT_DIR})
  --pushgateway URL     Pushgateway URL (e.g., http://localhost:9091)
  --job NAME            Pushgateway job name (default: ${JOB_NAME})
  --timeout SECONDS     Lighthouse page load timeout (default: ${LIGHTHOUSE_TIMEOUT})
  --runs N              Number of runs to average (default: ${RUNS})
  --verbose             Enable debug output
  --no-color            Disable colored output
  --help                Show this help

ENVIRONMENT VARIABLES:
  PUSHGATEWAY_URL       Pushgateway URL
  LIGHTHOUSE_TIMEOUT    Page load timeout in seconds (default: 60)
  OUTPUT_DIR            Report output directory
  JOB_NAME              Pushgateway job name (default: lighthouse)
  RUNS                  Number of runs to average (default: 1)
EOF
}

# ══════════════════════════════════════════════════════════════════════
#                       ARGUMENT PARSING
# ══════════════════════════════════════════════════════════════════════

parse_args() {
    POSITIONAL_URLS=()
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --file)          URLS_FILE="$2"; shift 2 ;;
            --output-dir)    OUTPUT_DIR="$2"; shift 2 ;;
            --pushgateway)   PUSHGATEWAY_URL="$2"; shift 2 ;;
            --job)           JOB_NAME="$2"; shift 2 ;;
            --timeout)       LIGHTHOUSE_TIMEOUT="$2"; shift 2 ;;
            --runs)          RUNS="$2"; shift 2 ;;
            --verbose)       VERBOSE="true"; shift ;;
            --no-color)      COLOR="never"; shift ;;
            --help|-h)       setup_colors; usage; exit 0 ;;
            --)              shift; break ;;
            -*)
                err "Unknown option: $1"
                echo "Run ${SCRIPT_NAME} --help for usage" >&2
                exit 1 ;;
            *)               POSITIONAL_URLS+=("$1"); shift ;;
        esac
    done
}

# ══════════════════════════════════════════════════════════════════════
#                       MAIN
# ══════════════════════════════════════════════════════════════════════

main() {
    parse_args "$@"
    setup_colors

    # Collect URLs
    URLS=()

    for u in "${POSITIONAL_URLS[@]+"${POSITIONAL_URLS[@]}"}"; do
        URLS+=("$u")
    done

    if [[ -n "$URLS_FILE" ]]; then
        if [[ ! -f "$URLS_FILE" ]]; then
            err "URLs file not found: ${URLS_FILE}"
            exit 1
        fi
        while IFS= read -r line; do
            line=$(echo "$line" | xargs)
            [[ -z "$line" || "$line" == \#* ]] && continue
            URLS+=("$line")
        done < "$URLS_FILE"
    fi

    if [[ ! -t 0 ]]; then
        while IFS= read -r line; do
            line=$(echo "$line" | xargs)
            [[ -z "$line" || "$line" == \#* ]] && continue
            URLS+=("$line")
        done
    fi

    if [[ ${#URLS[@]} -eq 0 ]]; then
        err "No URLs provided (see --help)"
        exit 1
    fi

    # Validate dependencies
    command -v lighthouse >/dev/null 2>&1 || { err "lighthouse not found — npm install -g lighthouse"; exit 1; }
    command -v jq >/dev/null 2>&1 || { err "jq not found — apt install jq"; exit 1; }

    mkdir -p "$OUTPUT_DIR"
    TEMP_DIR=$(mktemp -d)

    log "Lighthouse Audit — $(date -u +%Y-%m-%dT%H:%M:%SZ)"
    log "URLs: ${#URLS[@]} | Runs: ${RUNS} | Timeout: ${LIGHTHOUSE_TIMEOUT}s"

    for url in "${URLS[@]}"; do
        run_audit "$url" || true
        [[ ${#URLS[@]} -gt 1 ]] && sleep 5
    done

    log "Done."
}

main "$@"
