#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
HOMELAB_STATE_DIR="${HOMELAB_STATE_DIR:-${XDG_DATA_HOME:-${HOME}/.local/share}/homelab}"
SECURITY_REPORT_DIR="${SECURITY_REPORT_DIR:-${HOMELAB_STATE_DIR}/security-reports}"
SECURITY_TARGETS_FILE="${SECURITY_TARGETS_FILE:-${REPO_ROOT}/security/targets.txt}"
TRIVY_IMAGE="${TRIVY_IMAGE:-aquasec/trivy:latest}"
ZAP_IMAGE="${ZAP_IMAGE:-ghcr.io/zaproxy/zaproxy:stable}"
KUBE_BENCH_IMAGE="${KUBE_BENCH_IMAGE:-aquasec/kube-bench:latest}"
NUCLEI_IMAGE="${NUCLEI_IMAGE:-projectdiscovery/nuclei:latest}"

usage() {
    cat <<'EOF'
Usage: scripts/security-scan {all|prepare|trivy|zap|kube-bench|nuclei|host}

Environment:
  SECURITY_REPORT_DIR    Report output directory.
  SECURITY_TARGETS_FILE  Target allowlist for web scans.
  SECURITY_ZAP_TARGET    Single URL override for ZAP.
  SECURITY_NUCLEI_TARGET Single URL override for nuclei.
EOF
}

timestamp() {
    date -u +%Y%m%dT%H%M%SZ
}

ensure_report_dir() {
    mkdir -p "${SECURITY_REPORT_DIR}"
}

require_docker() {
    if ! command -v docker >/dev/null 2>&1; then
        echo "docker is required for containerized security scans." >&2
        exit 1
    fi
}

prepare_security_scans() {
    local image
    local -a images=(
        "${TRIVY_IMAGE}"
        "${ZAP_IMAGE}"
        "${KUBE_BENCH_IMAGE}"
        "${NUCLEI_IMAGE}"
    )

    ensure_report_dir
    require_docker

    if ! docker info >/dev/null 2>&1; then
        echo "docker is installed but not reachable by this user." >&2
        exit 1
    fi
    if [[ ! -s "${SECURITY_TARGETS_FILE}" ]]; then
        echo "Missing target file: ${SECURITY_TARGETS_FILE}" >&2
        exit 1
    fi
    if [[ ! -w "${SECURITY_REPORT_DIR}" ]]; then
        echo "Report directory is not writable: ${SECURITY_REPORT_DIR}" >&2
        exit 1
    fi

    echo "Preparing security scan images..."
    for image in "${images[@]}"; do
        echo "--> docker pull ${image}"
        docker pull "${image}"
    done

    echo "Security scan prerequisites are ready."
    echo "Report directory: ${SECURITY_REPORT_DIR}"
}

run_trivy() {
    local stamp
    stamp="$(timestamp)"
    ensure_report_dir

    echo "Running Trivy filesystem and IaC/config scans..."
    if command -v trivy >/dev/null 2>&1; then
        trivy fs --scanners vuln,secret,misconfig --format table --output "${SECURITY_REPORT_DIR}/trivy-fs-${stamp}.txt" "${REPO_ROOT}"
        trivy config --format table --output "${SECURITY_REPORT_DIR}/trivy-config-${stamp}.txt" "${REPO_ROOT}"
    else
        require_docker
        docker run --rm \
            -v "${REPO_ROOT}:/repo:ro" \
            -v "${SECURITY_REPORT_DIR}:/reports" \
            "${TRIVY_IMAGE}" fs --scanners vuln,secret,misconfig --format table --output "/reports/trivy-fs-${stamp}.txt" /repo
        docker run --rm \
            -v "${REPO_ROOT}:/repo:ro" \
            -v "${SECURITY_REPORT_DIR}:/reports" \
            "${TRIVY_IMAGE}" config --format table --output "/reports/trivy-config-${stamp}.txt" /repo
    fi

    echo "Trivy reports written to ${SECURITY_REPORT_DIR}"
}

scan_targets() {
    local target_override="$1"

    if [[ -n "${target_override}" ]]; then
        printf '%s\n' "${target_override}"
        return 0
    fi

    if [[ ! -s "${SECURITY_TARGETS_FILE}" ]]; then
        echo "Missing target file: ${SECURITY_TARGETS_FILE}" >&2
        exit 1
    fi

    sed -e 's/#.*//' -e '/^[[:space:]]*$/d' "${SECURITY_TARGETS_FILE}"
}

safe_target_name() {
    printf '%s' "$1" | sed -E 's#^https?://##; s#[^A-Za-z0-9._-]+#_#g; s#_+$##'
}

run_zap() {
    local stamp
    local target
    local safe_name
    stamp="$(timestamp)"
    ensure_report_dir
    require_docker

    echo "Running OWASP ZAP baseline passive scans..."
    while IFS= read -r target; do
        [[ -n "${target}" ]] || continue
        safe_name="$(safe_target_name "${target}")"
        echo "--> ZAP baseline ${target}"
        docker run --rm \
            -v "${SECURITY_REPORT_DIR}:/zap/wrk:rw" \
            "${ZAP_IMAGE}" zap-baseline.py \
            -t "${target}" \
            -I \
            -r "zap-${safe_name}-${stamp}.html" \
            -J "zap-${safe_name}-${stamp}.json"
    done < <(scan_targets "${SECURITY_ZAP_TARGET:-}")

    echo "ZAP reports written to ${SECURITY_REPORT_DIR}"
}

run_nuclei() {
    local stamp
    local target
    local safe_name
    stamp="$(timestamp)"
    ensure_report_dir
    require_docker

    echo "Running nuclei low-noise HTTP exposure scans..."
    while IFS= read -r target; do
        [[ -n "${target}" ]] || continue
        safe_name="$(safe_target_name "${target}")"
        echo "--> nuclei ${target}"
        docker run --rm \
            -v "${SECURITY_REPORT_DIR}:/reports:rw" \
            "${NUCLEI_IMAGE}" \
            -u "${target}" \
            -severity low,medium,high,critical \
            -rate-limit "${SECURITY_NUCLEI_RATE_LIMIT:-5}" \
            -retries 1 \
            -timeout 5 \
            -o "/reports/nuclei-${safe_name}-${stamp}.txt"
    done < <(scan_targets "${SECURITY_NUCLEI_TARGET:-}")

    echo "nuclei reports written to ${SECURITY_REPORT_DIR}"
}

run_kube_bench() {
    local stamp
    stamp="$(timestamp)"
    ensure_report_dir
    require_docker

    echo "Running kube-bench CIS checks against this kubeadm host..."
    docker run --rm --pid=host \
        -v /etc:/etc:ro \
        -v /var:/var:ro \
        -v /usr/bin:/usr/local/mount-from-host/bin:ro \
        "${KUBE_BENCH_IMAGE}" run --targets master,node | tee "${SECURITY_REPORT_DIR}/kube-bench-${stamp}.txt"
}

print_lynis_recommendations() {
    local report_file="$1"
    local warnings
    local suggestions
    local hardening_index

    hardening_index="$(awk -F= '$1 == "hardening_index" { print $2; exit }' "${report_file}" 2>/dev/null || true)"
    warnings="$(sed -n 's/^warning\[\]=//p' "${report_file}" 2>/dev/null | awk -F'|' '{ print $1 (NF > 1 ? " [" $NF "]" : "") }')"
    suggestions="$(sed -n 's/^suggestion\[\]=//p' "${report_file}" 2>/dev/null | awk -F'|' '{ print $1 (NF > 1 ? " [" $NF "]" : "") }')"

    printf '\n== Lynis Host Recommendations ==\n'
    if [[ -n "${hardening_index}" ]]; then
        printf 'Hardening index: %s\n' "${hardening_index}"
    fi

    printf '\n-- Warnings\n'
    if [[ -n "${warnings}" ]]; then
        printf '%s\n' "${warnings}" | sed 's/^/- /'
    else
        printf 'none\n'
    fi

    printf '\n-- Suggestions\n'
    if [[ -n "${suggestions}" ]]; then
        printf '%s\n' "${suggestions}" | sed 's/^/- /'
    else
        printf 'none\n'
    fi
}

run_lynis_host() {
    local stamp
    local report_file
    local log_file
    stamp="$(timestamp)"
    ensure_report_dir
    report_file="${SECURITY_REPORT_DIR}/lynis-host-${stamp}.dat"
    log_file="${SECURITY_REPORT_DIR}/lynis-host-${stamp}.log"

    if ! command -v lynis >/dev/null 2>&1; then
        cat >&2 <<'EOF'
lynis is not installed on this host.

Install it on Debian with:

  sudo apt install lynis

Jeannie does not install Lynis automatically because host hardening audits should
remain explicit and report-only.
EOF
        exit 1
    fi

    echo "Running Lynis host audit in report-only mode..."
    sudo lynis audit system \
        --no-colors \
        --quiet \
        --report-file "${report_file}" \
        --log-file "${log_file}" >/dev/null

    print_lynis_recommendations "${report_file}"
    printf '\nFull Lynis report: %s\n' "${report_file}"
    printf 'Full Lynis log:    %s\n' "${log_file}"
}

case "${1:-all}" in
    all)
        run_trivy
        run_zap
        ;;
    prepare)
        prepare_security_scans
        ;;
    trivy)
        run_trivy
        ;;
    zap)
        run_zap
        ;;
    nuclei)
        run_nuclei
        ;;
    kube-bench)
        run_kube_bench
        ;;
    host)
        run_lynis_host
        ;;
    -h | --help | help)
        usage
        ;;
    *)
        usage >&2
        exit 1
        ;;
esac
