#!/usr/bin/env bash

set -uo pipefail
IFS=$'\n\t'
umask 077

COLLECTOR_VERSION="0.1.0"
UPLOAD_URL="${JBS_UPLOAD_URL:-}"
UPLOAD_TOKEN="${JBS_UPLOAD_TOKEN:-}"
SCHEMA_VERSION="1.0"
PRODUCT_NAME="JBS VPS Check"
BUNDLE_TYPE="jbs_vps_evidence_bundle"

START_DIR="$(pwd)"
CREATED_AT_UTC="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
STAMP="$(date -u +"%Y-%m-%d-%H%M")"
HOSTNAME_RAW="$(hostname 2>/dev/null || printf 'unknown-host')"

if command -v sha256sum >/dev/null 2>&1; then
  HOSTNAME_HASH="$(printf '%s' "${HOSTNAME_RAW}" | sha256sum | awk '{print $1}')"
else
  HOSTNAME_HASH="sha256sum-not-available"
fi

WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/jbs-vps-check.XXXXXX")"
BUNDLE_ROOT="${WORKDIR}/jbs-vps-check-${STAMP}"
EVIDENCE_DIR="${BUNDLE_ROOT}/evidence"
OUTPUT_FILE="${START_DIR}/jbs-vps-check-${STAMP}.jbscheck"

mkdir -p "${EVIDENCE_DIR}"

cleanup() {
  if [ -n "${WORKDIR:-}" ] && [ -d "${WORKDIR}" ]; then
    rm -rf "${WORKDIR}"
  fi
}
trap cleanup EXIT

redact_stream() {
  if command -v perl >/dev/null 2>&1; then
    perl -pe '
      s/(password|passwd|secret|token|api[_-]?key|authorization)[[:space:]]*[:=][[:space:]]*[^[:space:]]+/$1=[REDACTED]/ig;
      s/(bearer)[[:space:]]+[A-Za-z0-9._~+\/=-]+/$1 [REDACTED]/ig;
      s/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/[REDACTED_PRIVATE_KEY_BLOCK]/g;
      s/-----END [A-Z0-9 ]*PRIVATE KEY-----/[REDACTED_PRIVATE_KEY_BLOCK]/g;
    '
  else
    awk '
      {
        line=$0
        gsub(/[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd][[:space:]]*[:=][[:space:]]*[^[:space:]]+/, "password=[REDACTED]", line)
        gsub(/[Pp][Aa][Ss][Ss][Ww][Dd][[:space:]]*[:=][[:space:]]*[^[:space:]]+/, "passwd=[REDACTED]", line)
        gsub(/[Ss][Ee][Cc][Rr][Ee][Tt][[:space:]]*[:=][[:space:]]*[^[:space:]]+/, "secret=[REDACTED]", line)
        gsub(/[Tt][Oo][Kk][Ee][Nn][[:space:]]*[:=][[:space:]]*[^[:space:]]+/, "token=[REDACTED]", line)
        gsub(/[Aa][Pp][Ii][_ -]?[Kk][Ee][Yy][[:space:]]*[:=][[:space:]]*[^[:space:]]+/, "api_key=[REDACTED]", line)
        gsub(/[Bb][Ee][Aa][Rr][Ee][Rr][[:space:]]+[^[:space:]]+/, "Bearer [REDACTED]", line)
        print line
      }
    '
  fi
}

write_header() {
  local title="$1"
  local target="$2"

  {
    printf '===== %s =====\n' "${title}"
    printf 'created_at_utc=%s\n' "${CREATED_AT_UTC}"
    printf 'collector_version=%s\n' "${COLLECTOR_VERSION}"
    printf 'mode=read_only_evidence_collection\n'
    printf '\n'
  } >> "${target}"
}

append_note() {
  local target="$1"
  local message="$2"

  {
    printf '\n'
    printf '[JBS_NOTE] %s\n' "${message}"
  } >> "${target}"
}

run_capture() {
  local target="$1"
  local title="$2"
  shift 2

  {
    printf '\n'
    printf -- '--- command:'
    printf ' %q' "$@"
    printf ' ---\n'
  } >> "${target}"

  if "$@" 2>&1 | redact_stream >> "${target}"; then
    {
      printf '\n'
      printf '[JBS_COMMAND_STATUS] ok\n'
    } >> "${target}"
  else
    {
      printf '\n'
      printf '[JBS_COMMAND_STATUS] unavailable_or_failed\n'
    } >> "${target}"
  fi
}

run_shell_capture() {
  local target="$1"
  local title="$2"
  local command_text="$3"

  {
    printf '\n'
    printf -- '--- section: %s ---\n' "${title}"
  } >> "${target}"

  if bash -c "${command_text}" 2>&1 | redact_stream >> "${target}"; then
    {
      printf '\n'
      printf '[JBS_SECTION_STATUS] ok\n'
    } >> "${target}"
  else
    {
      printf '\n'
      printf '[JBS_SECTION_STATUS] unavailable_or_failed\n'
    } >> "${target}"
  fi
}

has_command() {
  command -v "$1" >/dev/null 2>&1
}

write_system_evidence() {
  local target="${EVIDENCE_DIR}/system.txt"
  write_header "system" "${target}"

  run_capture "${target}" "utc_date" date -u
  run_capture "${target}" "kernel" uname -a

  if has_command hostnamectl; then
    run_capture "${target}" "hostnamectl" hostnamectl
  else
    append_note "${target}" "hostnamectl not available"
  fi

  run_capture "${target}" "uptime" uptime
  run_capture "${target}" "root_disk" df -h /
  run_capture "${target}" "memory" free -h

  if has_command lsb_release; then
    run_capture "${target}" "lsb_release" lsb_release -a
  elif [ -r /etc/os-release ]; then
    run_shell_capture "${target}" "os_release_safe_fields" "grep -E '^(NAME|VERSION|ID|VERSION_ID|PRETTY_NAME)=' /etc/os-release"
  else
    append_note "${target}" "OS release info not available"
  fi
}

write_ports_evidence() {
  local target="${EVIDENCE_DIR}/ports.txt"
  write_header "ports" "${target}"

  if has_command ss; then
    run_capture "${target}" "listening_ports" ss -tulpen
  else
    append_note "${target}" "ss not available"
  fi
}

write_firewall_evidence() {
  local target="${EVIDENCE_DIR}/firewall.txt"
  write_header "firewall" "${target}"

  if has_command ufw; then
    run_capture "${target}" "ufw_status" ufw status verbose
  else
    append_note "${target}" "ufw not available"
  fi

  if has_command nft; then
    run_capture "${target}" "nft_ruleset" nft list ruleset
  else
    append_note "${target}" "nft not available"
  fi

  if has_command iptables; then
    run_capture "${target}" "iptables_rules" iptables -S
  else
    append_note "${target}" "iptables not available"
  fi

  if has_command ip6tables; then
    run_capture "${target}" "ip6tables_rules" ip6tables -S
  else
    append_note "${target}" "ip6tables not available"
  fi
}

write_ssh_evidence() {
  local target="${EVIDENCE_DIR}/ssh.txt"
  write_header "ssh" "${target}"

  if has_command sshd; then
    run_shell_capture "${target}" "sshd_effective_config_selected_fields" \
      "sshd -T 2>/dev/null | grep -E '^(port|passwordauthentication|permitrootlogin|pubkeyauthentication|kbdinteractiveauthentication|challengeresponseauthentication|usepam|maxauthtries|x11forwarding|allowtcpforwarding|clientaliveinterval|clientalivecountmax)[[:space:]]'"
  else
    append_note "${target}" "sshd not available"
  fi

  if has_command systemctl; then
    run_shell_capture "${target}" "ssh_service_state" \
      "systemctl is-active ssh 2>/dev/null; systemctl is-active sshd 2>/dev/null"
  else
    append_note "${target}" "systemctl not available"
  fi
}

write_services_evidence() {
  local target="${EVIDENCE_DIR}/services.txt"
  write_header "services" "${target}"

  if has_command systemctl; then
    run_capture "${target}" "running_services" systemctl list-units --type=service --state=running --no-pager
    run_capture "${target}" "enabled_service_files" systemctl list-unit-files --type=service --state=enabled --no-pager
  else
    append_note "${target}" "systemctl not available"
  fi

  if has_command ps; then
    run_capture "${target}" "process_overview_without_args" ps -eo pid,ppid,user,comm --sort=comm
  else
    append_note "${target}" "ps not available"
  fi
}

write_tls_evidence() {
  local target="${EVIDENCE_DIR}/tls.txt"
  write_header "tls" "${target}"

  if has_command certbot; then
    run_capture "${target}" "certbot_public_certificate_inventory" certbot certificates
  else
    append_note "${target}" "certbot not available"
  fi

  if has_command openssl; then
    run_shell_capture "${target}" "public_certificate_metadata" \
      "find /etc/letsencrypt/live /etc/ssl/certs -type f \\( -name '*.pem' -o -name '*.crt' \\) 2>/dev/null | head -n 30 | while read -r cert_file; do echo \"### CERT_FILE=\${cert_file}\"; openssl x509 -in \"\${cert_file}\" -noout -subject -issuer -dates -fingerprint -sha256 2>/dev/null || true; done"
  else
    append_note "${target}" "openssl not available"
  fi
}

write_updates_evidence() {
  local target="${EVIDENCE_DIR}/updates.txt"
  write_header "updates" "${target}"

  if has_command apt; then
    run_capture "${target}" "apt_upgradable_from_local_cache" apt list --upgradable
  else
    append_note "${target}" "apt not available"
  fi

  if has_command dpkg-query; then
    run_shell_capture "${target}" "selected_security_related_packages" \
      "dpkg-query -W 2>/dev/null | grep -Ei '^(openssh-server|ufw|nftables|iptables|fail2ban|apache2|nginx|certbot|python3-certbot|unattended-upgrades|borgbackup|restic|duplicity|rsync|rclone)[[:space:]]'"
  else
    append_note "${target}" "dpkg-query not available"
  fi
}

write_fail2ban_evidence() {
  local target="${EVIDENCE_DIR}/fail2ban.txt"
  write_header "fail2ban" "${target}"

  if has_command fail2ban-client; then
    run_capture "${target}" "fail2ban_status" fail2ban-client status
  else
    append_note "${target}" "fail2ban-client not available"
  fi

  if has_command systemctl; then
    run_shell_capture "${target}" "fail2ban_service_state" \
      "systemctl is-active fail2ban 2>/dev/null"
  else
    append_note "${target}" "systemctl not available"
  fi
}

write_backup_evidence() {
  local target="${EVIDENCE_DIR}/backup.txt"
  write_header "backup" "${target}"

  if has_command systemctl; then
    run_capture "${target}" "timers" systemctl list-timers --all --no-pager
  else
    append_note "${target}" "systemctl not available"
  fi

  if has_command crontab; then
    run_capture "${target}" "current_user_crontab" crontab -l
  else
    append_note "${target}" "crontab not available"
  fi

  run_shell_capture "${target}" "cron_directory_names" \
    "find /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly -maxdepth 1 -type f -printf '%p\n' 2>/dev/null | sort"

  if has_command dpkg-query; then
    run_shell_capture "${target}" "backup_related_packages" \
      "dpkg-query -W 2>/dev/null | grep -Ei '(borg|restic|duplicity|rsync|rclone|backup|snapshot)'"
  else
    append_note "${target}" "dpkg-query not available"
  fi
}

write_auth_log_summary() {
  local target="${EVIDENCE_DIR}/auth_log_summary.txt"
  write_header "auth_log_summary" "${target}"

  {
    printf 'This file contains a short summary only. It is not a full authentication log export.\n'
    printf '\n'
  } >> "${target}"

  if has_command journalctl; then
    run_shell_capture "${target}" "ssh_failed_password_count" \
      "(journalctl -u ssh --since '7 days ago' --no-pager 2>/dev/null; journalctl -u sshd --since '7 days ago' --no-pager 2>/dev/null) | grep -ci 'Failed password'"

    run_shell_capture "${target}" "ssh_invalid_user_count" \
      "(journalctl -u ssh --since '7 days ago' --no-pager 2>/dev/null; journalctl -u sshd --since '7 days ago' --no-pager 2>/dev/null) | grep -ci 'Invalid user'"

    run_shell_capture "${target}" "ssh_accepted_publickey_count" \
      "(journalctl -u ssh --since '7 days ago' --no-pager 2>/dev/null; journalctl -u sshd --since '7 days ago' --no-pager 2>/dev/null) | grep -ci 'Accepted publickey'"

    run_shell_capture "${target}" "ssh_accepted_password_count" \
      "(journalctl -u ssh --since '7 days ago' --no-pager 2>/dev/null; journalctl -u sshd --since '7 days ago' --no-pager 2>/dev/null) | grep -ci 'Accepted password'"

    run_shell_capture "${target}" "ssh_source_hint_summary_redacted" \
      "(journalctl -u ssh --since '7 days ago' --no-pager 2>/dev/null; journalctl -u sshd --since '7 days ago' --no-pager 2>/dev/null) | grep -E 'Failed password|Invalid user|Accepted publickey|Accepted password' | grep -Eo 'from ([0-9]{1,3}\\.){3}[0-9]{1,3}|from [0-9a-fA-F:]{3,}' | sort | uniq -c | sort -nr | head -n 20"
  else
    append_note "${target}" "journalctl not available"
  fi
}

detect_os_hint() {
  if [ -r /etc/os-release ]; then
    awk -F= '/^PRETTY_NAME=/{gsub(/"/, "", $2); print $2}' /etc/os-release 2>/dev/null | head -n 1
  else
    printf 'unknown'
  fi
}

detect_kernel_hint() {
  uname -r 2>/dev/null || printf 'unknown'
}

file_exists_nonempty() {
  local path="$1"
  if [ -s "${path}" ]; then
    printf 'true'
  else
    printf 'false'
  fi
}

text_contains() {
  local path="$1"
  local pattern="$2"

  if [ -s "${path}" ] && grep -Eiq "${pattern}" "${path}"; then
    printf 'true'
  else
    printf 'false'
  fi
}

write_manifest() {
  local target="${BUNDLE_ROOT}/manifest.json"

  cat > "${target}" <<EOF
{
  "product": "${PRODUCT_NAME}",
  "bundle_type": "${BUNDLE_TYPE}",
  "schema_version": "${SCHEMA_VERSION}",
  "collector_version": "${COLLECTOR_VERSION}",
  "created_at_utc": "${CREATED_AT_UTC}",
  "hostname_hash": "${HOSTNAME_HASH}",
  "os_hint": "$(detect_os_hint | redact_stream)",
  "evidence_files": [
    "evidence/system.txt",
    "evidence/ports.txt",
    "evidence/firewall.txt",
    "evidence/ssh.txt",
    "evidence/services.txt",
    "evidence/tls.txt",
    "evidence/updates.txt",
    "evidence/fail2ban.txt",
    "evidence/backup.txt",
    "evidence/auth_log_summary.txt"
  ],
  "privacy": {
    "no_private_keys": true,
    "no_env_files": true,
    "no_full_logs": true,
    "redaction_enabled": true
  }
}
EOF
}

write_summary() {
  local target="${BUNDLE_ROOT}/summary.json"
  local os_hint
  local kernel_hint

  os_hint="$(detect_os_hint | redact_stream)"
  kernel_hint="$(detect_kernel_hint | redact_stream)"

  cat > "${target}" <<EOF
{
  "schema_version": "${SCHEMA_VERSION}",
  "collector_version": "${COLLECTOR_VERSION}",
  "created_at_utc": "${CREATED_AT_UTC}",
  "system": {
    "os_hint": "${os_hint}",
    "kernel_hint": "${kernel_hint}"
  },
  "signals": {
    "system_evidence_present": $(file_exists_nonempty "${EVIDENCE_DIR}/system.txt"),
    "listening_ports_collected": $(file_exists_nonempty "${EVIDENCE_DIR}/ports.txt"),
    "ufw_present": $(text_contains "${EVIDENCE_DIR}/firewall.txt" "ufw_status|Status:"),
    "firewall_evidence_present": $(file_exists_nonempty "${EVIDENCE_DIR}/firewall.txt"),
    "ssh_effective_config_present": $(text_contains "${EVIDENCE_DIR}/ssh.txt" "passwordauthentication|permitrootlogin|pubkeyauthentication"),
    "services_evidence_present": $(file_exists_nonempty "${EVIDENCE_DIR}/services.txt"),
    "tls_evidence_present": $(text_contains "${EVIDENCE_DIR}/tls.txt" "subject=|issuer=|notBefore|notAfter|certbot"),
    "updates_evidence_present": $(file_exists_nonempty "${EVIDENCE_DIR}/updates.txt"),
    "fail2ban_present": $(text_contains "${EVIDENCE_DIR}/fail2ban.txt" "fail2ban|Jail list"),
    "backup_indicators_present": $(text_contains "${EVIDENCE_DIR}/backup.txt" "borg|restic|duplicity|rsync|rclone|backup|snapshot"),
    "auth_log_summary_present": $(file_exists_nonempty "${EVIDENCE_DIR}/auth_log_summary.txt")
  },
  "limitations": [
    "Read-only collector output only.",
    "No private keys, passwords, environment files or full logs were intentionally collected.",
    "Findings must be generated by the backend analyzer from this evidence bundle.",
    "Package inventory and update data depend on local system metadata available at collection time."
  ]
}
EOF
}

write_readme() {
  local target="${BUNDLE_ROOT}/README.txt"

  cat > "${target}" <<EOF
JBS VPS Evidence Bundle
=======================

Product: ${PRODUCT_NAME}
Bundle type: ${BUNDLE_TYPE}
Schema version: ${SCHEMA_VERSION}
Collector version: ${COLLECTOR_VERSION}
Created at UTC: ${CREATED_AT_UTC}

This bundle is intended for JBS VPS Check Deep Check analysis.

Safety model:
- The collector is designed for read-only evidence collection.
- It does not intentionally collect private keys, passwords, environment files or full logs.
- Authentication data is summarized, not exported as full raw logs.
- Potential secret-like values are redacted before being written into evidence files.
- The backend analyzer should generate findings only from evidence included in this bundle.

Expected files:
- manifest.json
- summary.json
- hashes.sha256
- evidence/system.txt
- evidence/ports.txt
- evidence/firewall.txt
- evidence/ssh.txt
- evidence/services.txt
- evidence/tls.txt
- evidence/updates.txt
- evidence/fail2ban.txt
- evidence/backup.txt
- evidence/auth_log_summary.txt
EOF
}

write_hashes() {
  local target="${BUNDLE_ROOT}/hashes.sha256"

  (
    cd "${BUNDLE_ROOT}" || exit 0
    find . -type f ! -name 'hashes.sha256' -print0 | sort -z | xargs -0 sha256sum
  ) > "${target}"
}


auto_upload_bundle() {
  if [ -z "${UPLOAD_URL}" ] || [ -z "${UPLOAD_TOKEN}" ]; then
    printf 'auto_upload=SKIPPED\n'
    return 0
  fi
  if ! command -v curl >/dev/null 2>&1; then
    printf 'auto_upload=FAILED curl_not_available\n'
    return 1
  fi
  printf 'auto_upload=START\n'
  if curl -fsS -X POST -F "upload_token=${UPLOAD_TOKEN}" -F "bundle=@${OUTPUT_FILE}" "${UPLOAD_URL}"; then
    printf '\nauto_upload=PASS\n'
  else
    printf '\nauto_upload=FAIL\n'
    return 1
  fi
}

create_bundle() {
  (
    cd "${WORKDIR}" || exit 1
    tar -czf "${OUTPUT_FILE}" "$(basename "${BUNDLE_ROOT}")"
  )
}

print_banner() {
  printf '===== JBS VPS Evidence Collector =====\n'
  printf 'mode=read_only_evidence_collection\n'
  printf 'collector_version=%s\n' "${COLLECTOR_VERSION}"
  printf 'output_file=%s\n' "${OUTPUT_FILE}"
  printf '\n'
}

main() {
  print_banner

  printf '[1/13] Collecting system evidence...\n'
  write_system_evidence

  printf '[2/13] Collecting port evidence...\n'
  write_ports_evidence

  printf '[3/13] Collecting firewall evidence...\n'
  write_firewall_evidence

  printf '[4/13] Collecting SSH evidence...\n'
  write_ssh_evidence

  printf '[5/13] Collecting services evidence...\n'
  write_services_evidence

  printf '[6/13] Collecting TLS evidence...\n'
  write_tls_evidence

  printf '[7/13] Collecting update evidence...\n'
  write_updates_evidence

  printf '[8/13] Collecting fail2ban evidence...\n'
  write_fail2ban_evidence

  printf '[9/13] Collecting backup indicators...\n'
  write_backup_evidence

  printf '[10/13] Collecting auth log summary...\n'
  write_auth_log_summary

  printf '[11/13] Writing manifest and summary...\n'
  write_manifest
  write_summary
  write_readme

  printf '[12/13] Writing hashes...\n'
  write_hashes

  printf '[13/13] Creating bundle...\n'
  create_bundle
  auto_upload_bundle

  printf '\n'
  printf 'DONE\n'
  printf 'Created bundle:\n'
  printf '%s\n' "${OUTPUT_FILE}"
  printf '\n'
  launcher_file="${PWD}/OTWORZ-JBS-VPS-CHECK.html"
  cat > "${launcher_file}" <<'JBS_UPLOAD_HTML'
<!doctype html><html lang="pl"><head><meta charset="utf-8"><meta http-equiv="refresh" content="0; url=https://www.johnnyserver.pl/customer/dashboard.html#deep-check"><link rel="canonical" href="https://www.johnnyserver.pl/customer/dashboard.html#deep-check"><title>JBS VPS Check — upload</title></head><body style="font-family:Arial,sans-serif;background:#050816;color:#e5f7ff;padding:32px"><h1>Otwórz JBS VPS Check</h1><p>Wybierz wygenerowany plik <strong>.jbscheck</strong> i kliknij „Prześlij i analizuj Deep Check”.</p><p><a href="https://www.johnnyserver.pl/customer/dashboard.html#deep-check">Otwórz panel Deep Check</a></p></body></html>
JBS_UPLOAD_HTML
  chmod 600 "${launcher_file}" 2>/dev/null || true
  printf 'Open upload helper:\n%s\n\n' "${launcher_file}"
  printf 'Upload this .jbscheck file to JBS VPS Check Deep Check.\n'
}

main "$@"
