#!/bin/bash
# ============================================================================
# Webhook Relay Server
# Installs and configures a webhook relay server using adnanh/webhook
# ============================================================================
# Author  : Phil Connor
# Contact : contact@mylinux.work
# License : MIT
# Version : 1.0.0
# ============================================================================

set -euo pipefail

# ── Defaults ─────────────────────────────────────────────────────────────────
WEBHOOK_VERSION="${WEBHOOK_VERSION:-2.8.2}"
WEBHOOK_PORT="${WEBHOOK_PORT:-9000}"
WEBHOOK_USER="webhook"
WEBHOOK_BIN="/usr/local/bin/webhook"
WEBHOOK_CONF_DIR="/etc/webhook"
WEBHOOK_HOOKS_FILE="${WEBHOOK_CONF_DIR}/hooks.json"
WEBHOOK_HANDLER_DIR="${WEBHOOK_CONF_DIR}/handlers"
WEBHOOK_LOG_DIR="/var/log/webhook"
WEBHOOK_SECRET="${WEBHOOK_SECRET:-}"
PROM_TEXTFILE_DIR="/var/lib/node_exporter/textfile"
PROM_FILE="${PROM_TEXTFILE_DIR}/webhook.prom"

SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
MODE="install"

# ── Colors ───────────────────────────────────────────────────────────────────
if [[ -t 1 ]]; then
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[0;33m'
    CYAN='\033[0;36m'
    BOLD='\033[1m'
    RESET='\033[0m'
else
    RED="" GREEN="" YELLOW="" CYAN="" BOLD="" RESET=""
fi

# ── Logging ──────────────────────────────────────────────────────────────────
info()  { echo -e "${GREEN}[INFO]${RESET}  $*"; }
warn()  { echo -e "${YELLOW}[WARN]${RESET}  $*" >&2; }
err()   { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
die()   { err "$*"; exit 1; }

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

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

# ── Usage ────────────────────────────────────────────────────────────────────
show_help() {
    cat <<EOF
Usage: $SCRIPT_NAME [OPTIONS]

Install and configure a webhook relay server using adnanh/webhook.

OPTIONS:
    --port PORT         Listen port (default: 9000)
    --secret SECRET     Webhook HMAC secret for hooks.json
    --version VER       webhook binary version (default: 2.8.2)
    --status            Show service status and configuration
    --uninstall         Remove webhook server and all configuration
    --help              Show this help

ENVIRONMENT:
    WEBHOOK_VERSION     Override binary version
    WEBHOOK_PORT        Override listen port
    WEBHOOK_SECRET      HMAC secret for hook validation

EXAMPLES:
    sudo $SCRIPT_NAME
    sudo $SCRIPT_NAME --port 8080 --secret my-secret
    sudo $SCRIPT_NAME --status
    sudo $SCRIPT_NAME --uninstall
EOF
}

# ── Argument parsing ─────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
    case "$1" in
        --port)       WEBHOOK_PORT="$2"; shift 2 ;;
        --secret)     WEBHOOK_SECRET="$2"; shift 2 ;;
        --version)    WEBHOOK_VERSION="$2"; shift 2 ;;
        --status)     MODE="status"; shift ;;
        --uninstall)  MODE="uninstall"; shift ;;
        --help|-h)    show_help; exit 0 ;;
        *)            die "Unknown option: $1 (see --help)" ;;
    esac
done

# ── Preflight ────────────────────────────────────────────────────────────────
check_root() {
    [[ $EUID -eq 0 ]] || die "This script must be run as root"
}

detect_os() {
    if [[ -f /etc/os-release ]]; then
        # shellcheck disable=SC1091
        . /etc/os-release
        OS_ID="${ID}"
        OS_VERSION="${VERSION_ID:-}"
    else
        die "Cannot detect OS — /etc/os-release not found"
    fi

    case "$OS_ID" in
        debian|ubuntu)
            OS_FAMILY="debian"
            ;;
        rhel|centos|almalinux|rocky|fedora)
            OS_FAMILY="rhel"
            ;;
        *)
            die "Unsupported OS: $OS_ID"
            ;;
    esac
    info "Detected OS: ${OS_ID} ${OS_VERSION} (${OS_FAMILY})"
}

detect_arch() {
    ARCH="$(uname -m)"
    case "$ARCH" in
        x86_64)  WEBHOOK_ARCH="amd64" ;;
        aarch64) WEBHOOK_ARCH="arm64" ;;
        armv7l)  WEBHOOK_ARCH="armhf" ;;
        *)       die "Unsupported architecture: $ARCH" ;;
    esac
}

check_dependencies() {
    local missing=()
    for cmd in curl jq tar; do
        if ! command -v "$cmd" &>/dev/null; then
            missing+=("$cmd")
        fi
    done

    if [[ ${#missing[@]} -gt 0 ]]; then
        info "Installing missing dependencies: ${missing[*]}"
        if [[ "$OS_FAMILY" == "debian" ]]; then
            apt-get update -qq
            apt-get install -y -qq "${missing[@]}"
        else
            dnf install -y -q "${missing[@]}"
        fi
    fi
}

# ── Install webhook binary ──────────────────────────────────────────────────
install_binary() {
    section "Installing webhook binary"

    if [[ -f "$WEBHOOK_BIN" ]]; then
        local current_ver
        current_ver=$("$WEBHOOK_BIN" -version 2>&1 || echo "unknown")
        info "Existing binary found: $current_ver"
    fi

    local download_url="https://github.com/adnanh/webhook/releases/download/${WEBHOOK_VERSION}/webhook-linux-${WEBHOOK_ARCH}.tar.gz"
    local tmp_dir
    tmp_dir="$(mktemp -d)"

    info "Downloading webhook ${WEBHOOK_VERSION} (${WEBHOOK_ARCH})..."
    if ! curl -fsSL "$download_url" -o "${tmp_dir}/webhook.tar.gz"; then
        rm -rf "$tmp_dir"
        die "Failed to download webhook from $download_url"
    fi

    tar -xzf "${tmp_dir}/webhook.tar.gz" -C "$tmp_dir"
    local extracted
    extracted=$(find "$tmp_dir" -name webhook -type f | head -1)
    if [[ -z "$extracted" ]]; then
        rm -rf "$tmp_dir"
        die "webhook binary not found in archive"
    fi

    install -m 0755 "$extracted" "$WEBHOOK_BIN"
    rm -rf "$tmp_dir"

    info "Installed: $WEBHOOK_BIN"
    field "Version" "$WEBHOOK_VERSION"
    field "Architecture" "$WEBHOOK_ARCH"
}

# ── Create user ──────────────────────────────────────────────────────────────
create_user() {
    if id "$WEBHOOK_USER" &>/dev/null; then
        info "User $WEBHOOK_USER already exists"
        return
    fi
    useradd --system --no-create-home --shell /usr/sbin/nologin "$WEBHOOK_USER"
    info "Created system user: $WEBHOOK_USER"
}

# ── Directory structure ──────────────────────────────────────────────────────
create_directories() {
    section "Creating directory structure"

    mkdir -p "$WEBHOOK_CONF_DIR" "$WEBHOOK_HANDLER_DIR" "$WEBHOOK_LOG_DIR" "$PROM_TEXTFILE_DIR"

    chown "$WEBHOOK_USER":"$WEBHOOK_USER" "$WEBHOOK_LOG_DIR"
    chown "$WEBHOOK_USER":"$WEBHOOK_USER" "$PROM_TEXTFILE_DIR"

    field "Config" "$WEBHOOK_CONF_DIR"
    field "Handlers" "$WEBHOOK_HANDLER_DIR"
    field "Logs" "$WEBHOOK_LOG_DIR"
    field "Metrics" "$PROM_TEXTFILE_DIR"
}

# ── hooks.json ───────────────────────────────────────────────────────────────
create_hooks_config() {
    section "Creating hooks.json"

    local secret="${WEBHOOK_SECRET:-CHANGE_ME}"

    if [[ -f "$WEBHOOK_HOOKS_FILE" ]]; then
        warn "hooks.json already exists — backing up to hooks.json.bak"
        cp "$WEBHOOK_HOOKS_FILE" "${WEBHOOK_HOOKS_FILE}.bak"
    fi

    cat > "$WEBHOOK_HOOKS_FILE" <<HOOKS
[
  {
    "id": "mirror-sync",
    "execute-command": "${WEBHOOK_HANDLER_DIR}/mirror-sync.sh",
    "command-working-directory": "/tmp",
    "pass-arguments-to-command": [
      { "source": "payload", "name": "repository.full_name" }
    ],
    "trigger-rule": {
      "match": {
        "type": "payload-hmac-sha256",
        "secret": "${secret}",
        "parameter": {
          "source": "header",
          "name": "X-Hub-Signature-256"
        }
      }
    }
  },
  {
    "id": "deploy",
    "execute-command": "${WEBHOOK_HANDLER_DIR}/deploy.sh",
    "command-working-directory": "/tmp",
    "pass-arguments-to-command": [
      { "source": "payload", "name": "repository.full_name" },
      { "source": "payload", "name": "ref" }
    ],
    "trigger-rule": {
      "and": [
        {
          "match": {
            "type": "payload-hmac-sha256",
            "secret": "${secret}",
            "parameter": {
              "source": "header",
              "name": "X-Hub-Signature-256"
            }
          }
        },
        {
          "match": {
            "type": "value",
            "value": "refs/heads/main",
            "parameter": {
              "source": "payload",
              "name": "ref"
            }
          }
        }
      ]
    }
  },
  {
    "id": "notify",
    "execute-command": "${WEBHOOK_HANDLER_DIR}/notify.sh",
    "command-working-directory": "/tmp",
    "pass-arguments-to-command": [
      { "source": "payload", "name": "repository.full_name" },
      { "source": "payload", "name": "sender.login" },
      { "source": "payload", "name": "head_commit.message" }
    ],
    "trigger-rule": {
      "match": {
        "type": "payload-hmac-sha256",
        "secret": "${secret}",
        "parameter": {
          "source": "header",
          "name": "X-Hub-Signature-256"
        }
      }
    }
  }
]
HOOKS

    chmod 640 "$WEBHOOK_HOOKS_FILE"
    chown root:"$WEBHOOK_USER" "$WEBHOOK_HOOKS_FILE"

    if [[ "$secret" == "CHANGE_ME" ]]; then
        warn "No --secret provided — hooks.json uses placeholder. Edit ${WEBHOOK_HOOKS_FILE} before use."
    fi
    info "Created: $WEBHOOK_HOOKS_FILE (3 hooks)"
}

# ── Handler scripts ──────────────────────────────────────────────────────────
create_handlers() {
    section "Creating handler scripts"

    # ── Metrics helper (sourced by all handlers) ──
    cat > "${WEBHOOK_HANDLER_DIR}/metrics.sh" <<'METRICS'
#!/bin/bash
# Prometheus textfile collector helper — sourced by handler scripts
PROM_FILE="${PROM_FILE:-/var/lib/node_exporter/webhook.prom}"
_WEBHOOK_START=$(date +%s%N)

write_metrics() {
    local hook_id="$1"
    local status="$2"
    local end_time
    end_time=$(date +%s%N)
    local duration
    duration=$(echo "scale=3; ($end_time - $_WEBHOOK_START) / 1000000000" | bc 2>/dev/null || echo "0")

    local tmp_file="${PROM_FILE}.$$"
    local existing=""
    [[ -f "$PROM_FILE" ]] && existing=$(cat "$PROM_FILE")

    {
        echo "$existing"
        echo "# HELP webhook_receive_total Total webhook events received"
        echo "# TYPE webhook_receive_total counter"
        echo "webhook_receive_total{hook=\"${hook_id}\"} 1"
        if [[ "$status" == "success" ]]; then
            echo "# HELP webhook_success_total Successful handler executions"
            echo "# TYPE webhook_success_total counter"
            echo "webhook_success_total{hook=\"${hook_id}\"} 1"
            echo "# HELP webhook_last_success_timestamp Unix timestamp of last success"
            echo "# TYPE webhook_last_success_timestamp gauge"
            echo "webhook_last_success_timestamp{hook=\"${hook_id}\"} $(date +%s)"
        else
            echo "# HELP webhook_failure_total Failed handler executions"
            echo "# TYPE webhook_failure_total counter"
            echo "webhook_failure_total{hook=\"${hook_id}\"} 1"
        fi
        echo "# HELP webhook_handler_duration_seconds Handler execution time"
        echo "# TYPE webhook_handler_duration_seconds gauge"
        echo "webhook_handler_duration_seconds{hook=\"${hook_id}\"} ${duration}"
    } > "$tmp_file"
    mv "$tmp_file" "$PROM_FILE"
}
METRICS

    # ── Mirror sync handler ──
    cat > "${WEBHOOK_HANDLER_DIR}/mirror-sync.sh" <<'HANDLER'
#!/bin/bash
# Mirror sync handler — fetches from origin and pushes to mirror remote
set -euo pipefail
source "$(dirname "$0")/metrics.sh"

REPO_NAME="${1:-}"
MIRROR_BASE="/srv/git/mirrors"
HOOK_ID="mirror-sync"

if [[ -z "$REPO_NAME" ]]; then
    echo "No repository name received" >&2
    write_metrics "$HOOK_ID" "failure"
    exit 1
fi

MIRROR_DIR="${MIRROR_BASE}/${REPO_NAME}.git"
if [[ ! -d "$MIRROR_DIR" ]]; then
    echo "Mirror not found: $MIRROR_DIR" >&2
    write_metrics "$HOOK_ID" "failure"
    exit 1
fi

cd "$MIRROR_DIR"
git fetch --prune origin
git push --mirror mirror

write_metrics "$HOOK_ID" "success"
echo "Mirror sync complete: $REPO_NAME"
HANDLER

    # ── Deploy handler ──
    cat > "${WEBHOOK_HANDLER_DIR}/deploy.sh" <<'HANDLER'
#!/bin/bash
# Deploy handler — pulls latest code and restarts the application service
set -euo pipefail
source "$(dirname "$0")/metrics.sh"

REPO_NAME="${1:-}"
REF="${2:-}"
DEPLOY_BASE="/srv/apps"
HOOK_ID="deploy"

if [[ -z "$REPO_NAME" ]]; then
    echo "No repository name received" >&2
    write_metrics "$HOOK_ID" "failure"
    exit 1
fi

APP_NAME="${REPO_NAME##*/}"
DEPLOY_DIR="${DEPLOY_BASE}/${APP_NAME}"

if [[ ! -d "$DEPLOY_DIR" ]]; then
    echo "Deploy directory not found: $DEPLOY_DIR" >&2
    write_metrics "$HOOK_ID" "failure"
    exit 1
fi

cd "$DEPLOY_DIR"
git pull origin main

if systemctl is-active --quiet "$APP_NAME"; then
    systemctl restart "$APP_NAME"
    echo "Restarted service: $APP_NAME"
fi

write_metrics "$HOOK_ID" "success"
echo "Deploy complete: $APP_NAME (ref: $REF)"
HANDLER

    # ── Notification relay handler ──
    cat > "${WEBHOOK_HANDLER_DIR}/notify.sh" <<'HANDLER'
#!/bin/bash
# Notification relay — posts push events to Slack and/or Discord
set -euo pipefail
source "$(dirname "$0")/metrics.sh"

REPO_NAME="${1:-}"
SENDER="${2:-unknown}"
COMMIT_MSG="${3:-no message}"
HOOK_ID="notify"

SLACK_WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"

if [[ -z "$SLACK_WEBHOOK_URL" && -z "$DISCORD_WEBHOOK_URL" ]]; then
    echo "No SLACK_WEBHOOK_URL or DISCORD_WEBHOOK_URL configured" >&2
    write_metrics "$HOOK_ID" "failure"
    exit 1
fi

PAYLOAD="{\"text\":\"[${REPO_NAME}] ${SENDER}: ${COMMIT_MSG}\"}"

if [[ -n "$SLACK_WEBHOOK_URL" ]]; then
    curl -sf -X POST -H 'Content-Type: application/json' -d "$PAYLOAD" "$SLACK_WEBHOOK_URL"
fi

if [[ -n "$DISCORD_WEBHOOK_URL" ]]; then
    curl -sf -X POST -H 'Content-Type: application/json' -d "$PAYLOAD" "$DISCORD_WEBHOOK_URL"
fi

write_metrics "$HOOK_ID" "success"
echo "Notification sent: $REPO_NAME ($SENDER)"
HANDLER

    chmod 750 "${WEBHOOK_HANDLER_DIR}"/*.sh
    chown root:"$WEBHOOK_USER" "${WEBHOOK_HANDLER_DIR}"/*.sh

    info "Created handler: mirror-sync.sh"
    info "Created handler: deploy.sh"
    info "Created handler: notify.sh"
    info "Created helper:  metrics.sh"
}

# ── Systemd service ──────────────────────────────────────────────────────────
create_systemd_service() {
    section "Creating systemd service"

    cat > /etc/systemd/system/webhook.service <<SERVICE
[Unit]
Description=Webhook Relay Server
Documentation=https://github.com/adnanh/webhook
After=network.target

[Service]
Type=simple
User=${WEBHOOK_USER}
Group=${WEBHOOK_USER}
ExecStart=${WEBHOOK_BIN} -hooks ${WEBHOOK_HOOKS_FILE} -port ${WEBHOOK_PORT} -verbose -logfile ${WEBHOOK_LOG_DIR}/webhook.log
Restart=on-failure
RestartSec=5

# Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=${WEBHOOK_LOG_DIR} ${PROM_TEXTFILE_DIR}
PrivateTmp=true

[Install]
WantedBy=multi-user.target
SERVICE

    systemctl daemon-reload
    systemctl enable webhook.service
    info "Created and enabled: webhook.service"
    field "Listen port" "$WEBHOOK_PORT"
}

# ── Logrotate ────────────────────────────────────────────────────────────────
create_logrotate() {
    section "Creating logrotate config"

    cat > /etc/logrotate.d/webhook <<LOGROTATE
${WEBHOOK_LOG_DIR}/webhook.log {
    weekly
    rotate 4
    compress
    delaycompress
    missingok
    notifempty
    create 640 ${WEBHOOK_USER} ${WEBHOOK_USER}
    postrotate
        systemctl reload webhook.service 2>/dev/null || true
    endscript
}
LOGROTATE

    info "Created: /etc/logrotate.d/webhook"
}

# ── Status ───────────────────────────────────────────────────────────────────
show_status() {
    section "Webhook Relay Server — Status"

    if [[ -f "$WEBHOOK_BIN" ]]; then
        local ver
        ver=$("$WEBHOOK_BIN" -version 2>&1 || echo "unknown")
        field "Binary" "$WEBHOOK_BIN"
        field "Version" "$ver"
    else
        field "Binary" "not installed"
    fi

    if systemctl is-active --quiet webhook.service 2>/dev/null; then
        field "Service" "active (running)"
    elif systemctl is-enabled --quiet webhook.service 2>/dev/null; then
        field "Service" "enabled (not running)"
    else
        field "Service" "not installed"
    fi

    if [[ -f "$WEBHOOK_HOOKS_FILE" ]]; then
        local hook_count
        hook_count=$(jq 'length' "$WEBHOOK_HOOKS_FILE" 2>/dev/null || echo "?")
        field "Hooks config" "${WEBHOOK_HOOKS_FILE} (${hook_count} hooks)"
    else
        field "Hooks config" "not found"
    fi

    if [[ -d "$WEBHOOK_HANDLER_DIR" ]]; then
        local handler_count
        handler_count=$(find "$WEBHOOK_HANDLER_DIR" -name '*.sh' -not -name 'metrics.sh' | wc -l)
        field "Handlers" "${handler_count} scripts in ${WEBHOOK_HANDLER_DIR}"
    else
        field "Handlers" "not found"
    fi

    field "Log directory" "$WEBHOOK_LOG_DIR"
    field "Metrics file" "$PROM_FILE"

    echo ""
    if systemctl is-active --quiet webhook.service 2>/dev/null; then
        systemctl status webhook.service --no-pager -l 2>/dev/null | head -15
    fi
}

# ── Uninstall ────────────────────────────────────────────────────────────────
do_uninstall() {
    check_root
    section "Uninstalling Webhook Relay Server"

    if systemctl is-active --quiet webhook.service 2>/dev/null; then
        systemctl stop webhook.service
        info "Stopped webhook.service"
    fi

    if systemctl is-enabled --quiet webhook.service 2>/dev/null; then
        systemctl disable webhook.service
    fi

    local items=(
        /etc/systemd/system/webhook.service
        /etc/logrotate.d/webhook
        "$WEBHOOK_BIN"
    )

    for item in "${items[@]}"; do
        if [[ -e "$item" ]]; then
            rm -f "$item"
            info "Removed: $item"
        fi
    done

    if [[ -d "$WEBHOOK_CONF_DIR" ]]; then
        rm -rf "$WEBHOOK_CONF_DIR"
        info "Removed: $WEBHOOK_CONF_DIR"
    fi

    if [[ -d "$WEBHOOK_LOG_DIR" ]]; then
        rm -rf "$WEBHOOK_LOG_DIR"
        info "Removed: $WEBHOOK_LOG_DIR"
    fi

    if [[ -f "$PROM_FILE" ]]; then
        rm -f "$PROM_FILE"
        info "Removed: $PROM_FILE"
    fi

    if id "$WEBHOOK_USER" &>/dev/null; then
        userdel "$WEBHOOK_USER" 2>/dev/null || true
        info "Removed user: $WEBHOOK_USER"
    fi

    systemctl daemon-reload
    info "Uninstall complete"
}

# ── Summary ──────────────────────────────────────────────────────────────────
print_summary() {
    section "Installation Summary"

    field "Binary" "$WEBHOOK_BIN ($WEBHOOK_VERSION)"
    field "Config" "$WEBHOOK_HOOKS_FILE"
    field "Handlers" "$WEBHOOK_HANDLER_DIR/"
    field "Service" "webhook.service (port $WEBHOOK_PORT)"
    field "Logs" "$WEBHOOK_LOG_DIR/webhook.log"
    field "Metrics" "$PROM_FILE"
    field "User" "$WEBHOOK_USER"

    echo ""
    info "Next steps:"
    echo "  1. Edit ${WEBHOOK_HOOKS_FILE} — set your webhook secret"
    echo "  2. Configure handler scripts in ${WEBHOOK_HANDLER_DIR}/"
    echo "  3. Start the service: systemctl start webhook"
    echo "  4. Test: curl -X POST http://localhost:${WEBHOOK_PORT}/hooks/mirror-sync"
    echo ""
}

# ── Main ─────────────────────────────────────────────────────────────────────
main() {
    case "$MODE" in
        status)
            show_status
            ;;
        uninstall)
            do_uninstall
            ;;
        install)
            check_root
            detect_os
            detect_arch
            check_dependencies
            install_binary
            create_user
            create_directories
            create_hooks_config
            create_handlers
            create_systemd_service
            create_logrotate
            print_summary
            ;;
    esac
}

main
