#!/usr/bin/env bash
# End-to-end smoke checks from Igor's laptop for ActivityWatch-Russian.

set -uo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SMOKE_ENV_FILE="${AW_SMOKE_ENV_FILE:-}"
for env_candidate in "$REPO_ROOT/private-config/runtime.env" "$HOME/.config/aw-contour-smoke.env" "$SMOKE_ENV_FILE"; do
  if [ -n "$env_candidate" ] && [ -f "$env_candidate" ]; then
    # Load local credentials and site-specific overrides without committing them.
    # Later files override earlier defaults.
    set -a
    . "$env_candidate"
    set +a
  fi
done

ANSIBLE_DIR="$REPO_ROOT/ansible"
INVENTORY="${AW_SMOKE_INVENTORY:-$ANSIBLE_DIR/inventory.ini}"
REMOTE_SCRIPT_SRC="$REPO_ROOT/scripts/aw-contour-smoke-gateway.sh"
REMOTE_SCRIPT_DST="${AW_SMOKE_REMOTE_SCRIPT:-/usr/local/sbin/aw-contour-smoke.sh}"
DEFAULT_REMOTE_RUST_SRC=""
for rust_candidate in \
  "${CARGO_TARGET_DIR:-}/release/aw-contour-smoke" \
  "$HOME/.cache/detmir-adk-rust-target/release/aw-contour-smoke" \
  "$REPO_ROOT/adk-rust/target/release/aw-contour-smoke"
do
  if [ -n "$rust_candidate" ] && [ -x "$rust_candidate" ]; then
    DEFAULT_REMOTE_RUST_SRC="$rust_candidate"
    break
  fi
done
REMOTE_RUST_SRC="${AW_SMOKE_REMOTE_RUST_SRC:-$DEFAULT_REMOTE_RUST_SRC}"
REMOTE_RUST_DST="${AW_SMOKE_REMOTE_RUST_BIN:-/usr/local/sbin/aw-contour-smoke}"
AW_SERVER="${AW_SMOKE_AW_SERVER:-http://10.10.10.13:5600}"
WORKTIME_API="${AW_SMOKE_WORKTIME_API:-http://10.10.10.13:5610}"
GRAFANA_URL="${AW_SMOKE_GRAFANA_URL:-http://10.10.10.11:3000}"
GRAFANA_USER="${GRAFANA_USER:-igor}"
GRAFANA_PASSWORD="${GRAFANA_PASSWORD:-}"
PROXMOX_HOST="${AW_SMOKE_PROXMOX_HOST:-10.10.10.2}"
AW_HOST="${AW_SMOKE_AW_HOST:-10.10.10.13}"
GRAFANA_HOST="${AW_SMOKE_GRAFANA_HOST:-10.10.10.11}"
WINDOWS_HOST="${AW_SMOKE_WINDOWS_HOST:-${AW_WINDOWS_HOST:-192.168.100.19}}"
AW_SOURCE_HOSTNAME="${AW_SMOKE_SOURCE_HOSTNAME:-${AW_LOGICAL_HOST_ID:-${AW_MONITORED_WINDOWS_HOSTNAME:-SHARKON2025}}}"
LOG_DIR="${AW_SMOKE_LOG_DIR:-$REPO_ROOT/output/smoke}"
RUN_REMOTE="${AW_SMOKE_RUN_REMOTE:-1}"
RUN_WINRM="${AW_SMOKE_RUN_WINRM:-1}"
RUN_SERVER_SYSTEMD="${AW_SMOKE_RUN_SERVER_SYSTEMD:-1}"
LOKI_ENABLED="${AW_SMOKE_LOKI_ENABLED:-0}"

NO_PROXY_REQUIRED="localhost,127.0.0.1,$PROXMOX_HOST,$AW_HOST,$GRAFANA_HOST,$WINDOWS_HOST,10.10.10.0/24,192.168.100.0/24"
if [ -n "${no_proxy:-}" ]; then
  export no_proxy="$no_proxy,$NO_PROXY_REQUIRED"
else
  export no_proxy="$NO_PROXY_REQUIRED"
fi
if [ -n "${NO_PROXY:-}" ]; then
  export NO_PROXY="$NO_PROXY,$NO_PROXY_REQUIRED"
else
  export NO_PROXY="$no_proxy"
fi

OK_COUNT=0
WARN_COUNT=0
FAIL_COUNT=0
SKIP_COUNT=0

if [ -t 1 ]; then
  RED='\033[0;31m'
  GREEN='\033[0;32m'
  YELLOW='\033[1;33m'
  CYAN='\033[0;36m'
  NC='\033[0m'
else
  RED=''
  GREEN=''
  YELLOW=''
  CYAN=''
  NC=''
fi

usage() {
  cat <<USAGE
Usage: $(basename "$0") [--skip-remote] [--skip-winrm] [--skip-server-systemd]

Environment overrides:
  AW_SMOKE_AW_SERVER=http://10.10.10.13:5600
  AW_SMOKE_WORKTIME_API=http://10.10.10.13:5610
  AW_SMOKE_GRAFANA_URL=http://10.10.10.11:3000
  AW_SMOKE_SOURCE_HOSTNAME=SHARKON2025
  AW_SMOKE_LOKI_ENABLED=0|1
  AW_SMOKE_ENV_FILE=$HOME/.config/aw-contour-smoke.env
  AW_SMOKE_LOG_DIR=$REPO_ROOT/output/smoke
  GRAFANA_USER/GRAFANA_PASSWORD via env or a local env file
USAGE
}

while [ "$#" -gt 0 ]; do
  case "$1" in
    --skip-remote) RUN_REMOTE=0 ;;
    --skip-winrm) RUN_WINRM=0 ;;
    --skip-server-systemd) RUN_SERVER_SYSTEMD=0 ;;
    -h|--help) usage; exit 0 ;;
    *) echo "Unknown argument: $1" >&2; usage >&2; exit 64 ;;
  esac
  shift
done

pass() { OK_COUNT=$((OK_COUNT + 1)); printf "%b[OK]%b   %s\n" "$GREEN" "$NC" "$*"; }
warn() { WARN_COUNT=$((WARN_COUNT + 1)); printf "%b[WARN]%b %s\n" "$YELLOW" "$NC" "$*"; }
fail() { FAIL_COUNT=$((FAIL_COUNT + 1)); printf "%b[FAIL]%b %s\n" "$RED" "$NC" "$*"; }
skip() { SKIP_COUNT=$((SKIP_COUNT + 1)); printf "%b[SKIP]%b %s\n" "$YELLOW" "$NC" "$*"; }

section() {
  printf "\n%b== %s ==%b\n" "$CYAN" "$*" "$NC"
}

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

check_local_command() {
  local cmd="$1"
  if have "$cmd"; then
    pass "command available: $cmd"
  else
    fail "command missing: $cmd"
  fi
}

check_tcp() {
  local name="$1"
  local host="$2"
  local port="$3"
  if timeout 4 bash -c ":</dev/tcp/${host}/${port}" >/dev/null 2>&1; then
    pass "$name TCP ${host}:${port}"
  else
    fail "$name TCP ${host}:${port}"
  fi
}

check_ssh_banner() {
  local name="$1"
  local host="$2"
  local port="${3:-22}"
  local banner
  banner="$(
    timeout 5 bash -c "exec 3<>/dev/tcp/${host}/${port}; IFS= read -r line <&3; printf '%s' \"\$line\"" 2>/dev/null || true
  )"
  if printf "%s" "$banner" | grep -Eq '^SSH-[0-9]+\.[0-9]+'; then
    pass "$name SSH banner ${host}:${port} ${banner}"
  else
    fail "$name SSH banner ${host}:${port} unavailable"
  fi
}

check_http_code() {
  local name="$1"
  local url="$2"
  local expected="${3:-^2[0-9][0-9]$}"
  local tmp code
  tmp="$(mktemp)"
  code="$(curl -k -sS --connect-timeout 5 --max-time 20 -o "$tmp" -w '%{http_code}' "$url" 2>"$tmp.err")"
  if printf "%s" "$code" | grep -Eq "$expected"; then
    pass "$name HTTP $code $url"
  else
    fail "$name HTTP $code $url"
    sed 's/^/       /' "$tmp.err" "$tmp" 2>/dev/null | head -40
  fi
  rm -f "$tmp" "$tmp.err"
}

check_http_json_key() {
  local name="$1"
  local url="$2"
  local jq_filter="$3"
  local tmp
  tmp="$(mktemp)"
  if curl -k -fsS --connect-timeout 5 --max-time 20 "$url" -o "$tmp" 2>"$tmp.err" && jq -e "$jq_filter" "$tmp" >/dev/null 2>&1; then
    pass "$name JSON $jq_filter"
    jq -r "$jq_filter" "$tmp" 2>/dev/null | sed 's/^/       /' | head -5
  else
    fail "$name JSON $jq_filter"
    sed 's/^/       /' "$tmp.err" "$tmp" 2>/dev/null | head -40
  fi
  rm -f "$tmp" "$tmp.err"
}

check_http_json_key_basic_auth() {
  local name="$1"
  local url="$2"
  local jq_filter="$3"
  local tmp
  tmp="$(mktemp)"
  if curl -k -fsS -u "$GRAFANA_USER:$GRAFANA_PASSWORD" --connect-timeout 5 --max-time 20 "$url" -o "$tmp" 2>"$tmp.err" && jq -e "$jq_filter" "$tmp" >/dev/null 2>&1; then
    pass "$name JSON $jq_filter"
    jq -r "$jq_filter" "$tmp" 2>/dev/null | sed 's/^/       /' | head -5
  else
    fail "$name JSON $jq_filter"
    sed 's/^/       /' "$tmp.err" "$tmp" 2>/dev/null | head -40
  fi
  rm -f "$tmp" "$tmp.err"
}

check_http_code_basic_auth() {
  local name="$1"
  local url="$2"
  local expected="${3:-^2[0-9][0-9]$}"
  local tmp code
  tmp="$(mktemp)"
  code="$(curl -k -sS -u "$GRAFANA_USER:$GRAFANA_PASSWORD" --connect-timeout 5 --max-time 20 -o "$tmp" -w '%{http_code}' "$url" 2>"$tmp.err")"
  if printf "%s" "$code" | grep -Eq "$expected"; then
    pass "$name HTTP $code $url"
  else
    fail "$name HTTP $code $url"
    sed 's/^/       /' "$tmp.err" "$tmp" 2>/dev/null | head -40
  fi
  rm -f "$tmp" "$tmp.err"
}

check_grafana_loki_proxy() {
  local name="Grafana Loki datasource proxy"
  local tmp uid code body
  if [ "$LOKI_ENABLED" != "1" ]; then
    skip "$name skipped: Loki is intentionally disabled for the current DetMir resource profile"
    return
  fi
  tmp="$(mktemp)"
  if ! curl -k -fsS -u "$GRAFANA_USER:$GRAFANA_PASSWORD" --connect-timeout 5 --max-time 20 "$GRAFANA_URL/api/datasources" -o "$tmp" 2>"$tmp.err"; then
    fail "$name cannot list datasources"
    sed 's/^/       /' "$tmp.err" "$tmp" 2>/dev/null | head -40
    rm -f "$tmp" "$tmp.err"
    return
  fi
  uid="$(jq -r '.[] | select(.type == "loki") | .uid' "$tmp" 2>/dev/null | head -1)"
  if [ -z "$uid" ]; then
    fail "$name no loki datasource found"
    jq -r '.[] | "\(.name) \(.type) \(.uid)"' "$tmp" 2>/dev/null | sed 's/^/       /' | head -20
    rm -f "$tmp" "$tmp.err"
    return
  fi
  body="$(mktemp)"
  code="$(curl -k -sS -u "$GRAFANA_USER:$GRAFANA_PASSWORD" --connect-timeout 5 --max-time 20 -o "$body" -w '%{http_code}' "$GRAFANA_URL/api/datasources/proxy/uid/$uid/loki/api/v1/labels" 2>"$tmp.err")"
  if [ "$code" = "200" ]; then
    pass "$name HTTP $code uid=$uid"
    jq -r '.status // .data[0] // .' "$body" 2>/dev/null | sed 's/^/       /' | head -5
  else
    fail "$name HTTP $code uid=$uid"
    sed 's/^/       /' "$tmp.err" "$body" 2>/dev/null | head -40
  fi
  rm -f "$tmp" "$tmp.err" "$body"
}

check_grafana_influx_health() {
  local name="Grafana InfluxDB-AW datasource health"
  local tmp
  tmp="$(mktemp)"
  if curl -k -fsS -u "$GRAFANA_USER:$GRAFANA_PASSWORD" --connect-timeout 5 --max-time 20 "$GRAFANA_URL/api/datasources/uid/influxdb_aw/health" -o "$tmp" 2>"$tmp.err" && jq -e '.status == "OK"' "$tmp" >/dev/null 2>&1; then
    pass "$name"
    jq -r '.message // .status // .' "$tmp" 2>/dev/null | sed 's/^/       /' | head -5
  else
    fail "$name"
    sed 's/^/       /' "$tmp.err" "$tmp" 2>/dev/null | head -40
  fi
  rm -f "$tmp" "$tmp.err"
}

check_grafana_aw_main_dashboard_queries() {
  local name="Grafana detmir-aw-main worktime panel queries"
  local tmp
  tmp="$(mktemp)"
  if curl -k -fsS -u "$GRAFANA_USER:$GRAFANA_PASSWORD" --connect-timeout 5 --max-time 20 "$GRAFANA_URL/api/dashboards/uid/detmir-aw-main" -o "$tmp" 2>"$tmp.err" && \
    jq -e '
      def query: (.targets[0].query // "");
      def has_panel($title; $checks):
        any(.dashboard.panels[]?; .title == $title and (query as $q | all($checks[]; $q | contains(.))));
      has_panel("Активность RDP по часам"; [
        "aw_rdp_worktime_hourly",
        "r.user_id !~ /\\$$/",
        "r.user_id !~ /�/",
        "group(columns: [\"_time\", \"user\"])",
        "max(column: \"_value\")"
      ]) and
      has_panel("Сегодня: активность по сотрудникам"; [
        "aw_rdp_worktime_daily",
        "group(columns: [\"report_date\", \"user\"])",
        "max(column: \"_value\")",
        "last()"
      ]) and
      has_panel("Все сотрудники: активное время по дням"; [
        "aw_rdp_worktime_summary_daily",
        "Все сотрудники, ч"
      ])
    ' "$tmp" >/dev/null 2>&1; then
    pass "$name"
  else
    fail "$name missing expected DetMir worktime/dedupe query contract"
    jq -r '.dashboard.panels[]? | select(.title == "Активность RDP по часам" or .title == "Сегодня: активность по сотрудникам" or .title == "Все сотрудники: активное время по дням") | "\(.title): " + ((.targets[0].query // "") | gsub("\n"; " "))' "$tmp" 2>/dev/null | sed 's/^/       /' | head -20
    sed 's/^/       /' "$tmp.err" 2>/dev/null | head -20
  fi
  rm -f "$tmp" "$tmp.err"
}

check_http_redirect() {
  local name="$1"
  local url="$2"
  local expected_code="${3:-^30[1278]$}"
  local expected_location="${4:-}"
  local tmp code location
  tmp="$(mktemp)"
  code="$(curl -k -sS -I --connect-timeout 5 --max-time 15 -o "$tmp" -w '%{http_code}' "$url" 2>"$tmp.err")"
  location="$(awk 'BEGIN{IGNORECASE=1} /^location:/ {sub(/\r$/,""); print $0}' "$tmp" | tail -1)"
  if printf "%s" "$code" | grep -Eq "$expected_code" && { [ -z "$expected_location" ] || printf "%s" "$location" | grep -Fq "$expected_location"; }; then
    pass "$name HTTP $code ${location:-$url}"
  else
    fail "$name HTTP $code ${location:-$url}"
    sed 's/^/       /' "$tmp.err" "$tmp" 2>/dev/null | head -40
  fi
  rm -f "$tmp" "$tmp.err"
}

ansible_shell() {
  local group="$1"
  local command="$2"
  ANSIBLE_NOCOLOR=1 ansible "$group" -i "$INVENTORY" -m shell -a "$command"
}

ansible_win_shell() {
  local group="$1"
  local command="$2"
  ANSIBLE_NOCOLOR=1 ansible "$group" -i "$INVENTORY" -m win_shell -a "$command"
}

check_ansible_shell() {
  local name="$1"
  local group="$2"
  local command="$3"
  local tmp
  tmp="$(mktemp)"
  if ansible_shell "$group" "$command" >"$tmp" 2>&1; then
    pass "$name"
    sed 's/^/       /' "$tmp" | head -80
  else
    fail "$name"
    sed 's/^/       /' "$tmp" | head -120
  fi
  rm -f "$tmp"
}

check_ansible_win_shell() {
  local name="$1"
  local group="$2"
  local command="$3"
  local tmp
  tmp="$(mktemp)"
  if ansible_win_shell "$group" "$command" >"$tmp" 2>&1; then
    pass "$name"
    sed 's/^/       /' "$tmp" | head -80
  else
    fail "$name"
    sed 's/^/       /' "$tmp" | head -120
  fi
  rm -f "$tmp"
}

check_ansible_win_shell_warn() {
  local name="$1"
  local group="$2"
  local command="$3"
  local tmp
  tmp="$(mktemp)"
  if ansible_win_shell "$group" "$command" >"$tmp" 2>&1; then
    pass "$name"
    sed 's/^/       /' "$tmp" | head -80
  else
    warn "$name returned non-zero"
    sed 's/^/       /' "$tmp" | head -120
  fi
  rm -f "$tmp"
}

check_ansible_module() {
  local name="$1"
  local group="$2"
  local module="$3"
  local args="${4:-}"
  local tmp
  tmp="$(mktemp)"
  if ANSIBLE_NOCOLOR=1 ansible "$group" -i "$INVENTORY" -m "$module" ${args:+-a "$args"} >"$tmp" 2>&1; then
    pass "$name"
    sed 's/^/       /' "$tmp" | head -80
  else
    fail "$name"
    sed 's/^/       /' "$tmp" | head -120
  fi
  rm -f "$tmp"
}

classify_bucket_age() {
  local bucket="$1"
  local age_sec="$2"
  case "$bucket" in
    aw-dlp-incidents|aw-dlp-review|aw-dlp-rules|aw-session-events)
      if [ "$age_sec" -lt 86400 ]; then
        printf "fresh"
      else
        printf "event-driven"
      fi
      ;;
    *)
      if [ "$age_sec" -lt 3600 ]; then
        printf "fresh"
      elif [ "$age_sec" -lt 86400 ]; then
        printf "stale"
      else
        printf "dead"
      fi
      ;;
  esac
}

check_bucket_freshness() {
  local bucket="$1"
  local bucket_id="${bucket}_${AW_SOURCE_HOSTNAME}"
  local tmp last_ts last_id event_epoch now age_sec status meta_ts
  tmp="$(mktemp)"
  if ! curl -fsS --connect-timeout 5 --max-time 20 "$AW_SERVER/api/0/buckets/$bucket_id/events?limit=1" -o "$tmp" 2>"$tmp.err"; then
    fail "bucket $bucket_id query failed"
    sed 's/^/       /' "$tmp.err" | head -20
    rm -f "$tmp" "$tmp.err"
    return
  fi

  last_ts="$(jq -r '.[0].timestamp // empty' "$tmp" 2>/dev/null)"
  last_id="$(jq -r '.[0].id // 0' "$tmp" 2>/dev/null)"
  rm -f "$tmp" "$tmp.err"
  if [ "$bucket" = "aw-watcher-afk" ]; then
    meta_ts="$(curl -fsS --connect-timeout 5 --max-time 20 "$AW_SERVER/api/0/buckets/$bucket_id" 2>/dev/null | jq -r '.metadata.end // empty' 2>/dev/null || true)"
    if [ -n "$meta_ts" ]; then
      last_ts="$meta_ts"
    fi
  fi

  if [ -z "$last_ts" ]; then
    case "$bucket" in
      aw-dlp-incidents|aw-dlp-review|aw-dlp-rules|aw-session-events)
        warn "bucket $bucket_id empty/event-driven"
        ;;
      *)
        fail "bucket $bucket_id empty"
        ;;
    esac
    return
  fi

  event_epoch="$(date -d "$last_ts" +%s 2>/dev/null || printf "0")"
  now="$(date -u +%s)"
  if [ "$event_epoch" -le 0 ]; then
    warn "bucket $bucket_id has unparsable timestamp: $last_ts"
    return
  fi
  age_sec=$((now - event_epoch))
  status="$(classify_bucket_age "$bucket" "$age_sec")"
  case "$status" in
    fresh|event-driven)
      pass "bucket $bucket_id $status age=${age_sec}s id=$last_id"
      ;;
    stale)
      warn "bucket $bucket_id stale age=${age_sec}s id=$last_id"
      ;;
    *)
      fail "bucket $bucket_id dead age=${age_sec}s id=$last_id"
      ;;
  esac
}

run_remote_proxmox_script() {
  if [ "$RUN_REMOTE" != "1" ]; then
    skip "remote 10.10.10.2 smoke skipped"
    return
  fi
  if ! have ansible; then
    fail "ansible unavailable; cannot deploy/run remote script"
    return
  fi
  if [ ! -f "$REMOTE_SCRIPT_SRC" ]; then
    fail "remote script source missing: $REMOTE_SCRIPT_SRC"
    return
  fi

  section "Deploy Remote Script To 10.10.10.2"
  if [ -x "$REMOTE_RUST_SRC" ]; then
    if ANSIBLE_NOCOLOR=1 ansible proxmox -i "$INVENTORY" -m copy -a "src=$REMOTE_RUST_SRC dest=$REMOTE_RUST_DST owner=root group=root mode=0755" >/tmp/aw-smoke-copy-rust.$$ 2>&1; then
      pass "remote Rust smoke deployed to $REMOTE_RUST_DST"
    else
      warn "remote Rust smoke deploy failed; shell fallback remains available"
      sed 's/^/       /' /tmp/aw-smoke-copy-rust.$$ | head -120
    fi
    rm -f /tmp/aw-smoke-copy-rust.$$
  else
    skip "remote Rust smoke binary not found: $REMOTE_RUST_SRC"
  fi

  if ANSIBLE_NOCOLOR=1 ansible proxmox -i "$INVENTORY" -m copy -a "src=$REMOTE_SCRIPT_SRC dest=$REMOTE_SCRIPT_DST owner=root group=root mode=0755" >/tmp/aw-smoke-copy.$$ 2>&1; then
    pass "remote script deployed to $REMOTE_SCRIPT_DST"
  else
    fail "remote script deploy failed"
    sed 's/^/       /' /tmp/aw-smoke-copy.$$ | head -120
    rm -f /tmp/aw-smoke-copy.$$
    return
  fi
  rm -f /tmp/aw-smoke-copy.$$

  section "Remote 10.10.10.2 Smoke"
  local tmp
  tmp="$(mktemp)"
  if ANSIBLE_NOCOLOR=1 ansible proxmox -i "$INVENTORY" -m shell -a "$REMOTE_SCRIPT_DST" >"$tmp" 2>&1; then
    pass "remote 10.10.10.2 smoke completed"
    sed 's/^/       /' "$tmp"
  else
    fail "remote 10.10.10.2 smoke failed"
    sed 's/^/       /' "$tmp"
  fi
  rm -f "$tmp"
}

mkdir -p "$LOG_DIR"
LOG_FILE="$LOG_DIR/aw-contour-smoke-$(date +%Y%m%d-%H%M%S).log"
exec > >(tee "$LOG_FILE") 2>&1

section "Run Context"
printf "repo=%s\ninventory=%s\nlog=%s\n" "$REPO_ROOT" "$INVENTORY" "$LOG_FILE" | sed 's/^/       /'
date -Is | sed 's/^/       /'

section "Local Prerequisites"
for cmd in bash curl jq timeout ansible ssh; do
  check_local_command "$cmd"
done
printf "       no_proxy=%s\n" "$no_proxy"

section "Laptop Network"
ip -br addr 2>/dev/null | sed 's/^/       /' || true
ip route get "$PROXMOX_HOST" 2>/dev/null | sed 's/^/       /' || warn "no route detail for $PROXMOX_HOST"
ip route get "$AW_HOST" 2>/dev/null | sed 's/^/       /' || warn "no route detail for $AW_HOST"

section "TCP Surface"
check_tcp "Proxmox SSH" "$PROXMOX_HOST" 22
check_tcp "Proxmox HTTP" "$PROXMOX_HOST" 80
check_tcp "Proxmox HTTPS" "$PROXMOX_HOST" 443
check_tcp "Proxmox GUI" "$PROXMOX_HOST" 8006
check_tcp "1C company API" "$PROXMOX_HOST" 8710
check_tcp "AW server" "$AW_HOST" 5600
check_tcp "AW worktime API" "$AW_HOST" 5610
check_tcp "Grafana" "$GRAFANA_HOST" 3000
check_tcp "Windows WinRM" "$WINDOWS_HOST" 5985
check_tcp "Windows SSH" "$WINDOWS_HOST" 22
check_ssh_banner "Windows SSH" "$WINDOWS_HOST" 22

section "ActivityWatch HTTP"
check_http_json_key "AW server info" "$AW_SERVER/api/0/info" '.version'
check_http_code "AW settings CORS" "$AW_SERVER/api/0/settings/" '^200$'
check_http_code "AW WebUI" "$AW_SERVER/" '^200$'
check_http_json_key "AW buckets list" "$AW_SERVER/api/0/buckets/" 'keys | length'

section "ActivityWatch Buckets"
for bucket in \
  aw-watcher-afk \
  aw-watcher-window \
  aw-worktime-sessions \
  aw-session-events \
  aw-dlp-endpoint-signals \
  aw-dlp-incidents \
  aw-dlp-review \
  aw-dlp-rules
do
  check_bucket_freshness "$bucket"
done

section "Worktime API"
check_http_json_key "worktime health" "$WORKTIME_API/health" '.status // .ok // .'
check_http_code "worktime today html" "$WORKTIME_API/reports/worktime/today?host=$AW_SOURCE_HOSTNAME&day=today&format=html" '^200$'
check_http_json_key "worktime today json" "$WORKTIME_API/reports/worktime/today?host=$AW_SOURCE_HOSTNAME&day=today" '.host // .report.host // .[0].host // .'
check_http_code "worktime management html" "$WORKTIME_API/reports/worktime/management?host=$AW_SOURCE_HOSTNAME&day=today&format=html" '^200$'

section "Gateway And 1C HTTP"
check_http_code "gateway healthz" "https://$PROXMOX_HOST/healthz" '^200$'
check_http_code "gateway proxmox protected" "https://$PROXMOX_HOST/go/proxmox-gui" '^401$'
check_http_code "gateway file1c brief protected" "https://$PROXMOX_HOST/go/file1c-brief" '^401$'
check_http_code "gateway file1c actions protected" "https://$PROXMOX_HOST/go/file1c-actions" '^401$'
check_http_code "1C /health" "http://$PROXMOX_HOST:8710/health" '^200$'
check_http_code "1C /api/health" "http://$PROXMOX_HOST:8710/api/health" '^200$'
check_http_code "1C manager brief" "http://$PROXMOX_HOST:8710/manager/brief" '^200$'
check_http_code "1C manager actions" "http://$PROXMOX_HOST:8710/manager/actions" '^200$'
check_http_code "1C manager recovery" "http://$PROXMOX_HOST:8710/manager/recovery" '^200$'
check_http_code "1C weekly digest" "http://$PROXMOX_HOST:8710/manager/digest/weekly" '^200$'

section "Grafana HTTP"
check_http_json_key "Grafana health" "$GRAFANA_URL/api/health" '.database // .version // .commit'
check_http_code "Grafana dashboards page" "$GRAFANA_URL/dashboards" '^200$|^302$'
check_http_code "Grafana pfSense dashboard" "$GRAFANA_URL/d/pfsense-loki-dashboard/pfsense-firewall-overview?orgId=1&from=now-1h&to=now&timezone=browser" '^200$|^302$'

if [ -n "${GRAFANA_USER:-}" ] && [ -n "${GRAFANA_PASSWORD:-}" ]; then
  section "Grafana Authenticated API"
  check_http_json_key_basic_auth "Grafana datasources" "$GRAFANA_URL/api/datasources" 'length'
  check_grafana_loki_proxy
  check_grafana_influx_health
  check_grafana_aw_main_dashboard_queries
else
  skip "Grafana authenticated datasource checks need GRAFANA_USER and GRAFANA_PASSWORD env"
fi

if [ "$RUN_SERVER_SYSTEMD" = "1" ] && have ansible; then
  section "AW Server Systemd"
  check_ansible_shell "AW server core units" aw_server 'systemctl is-active activitywatch-server aw-worktime-api aw-worktime-ui-bridge.timer aw-rus-healthd.timer aw-worktime-influx-exporter.timer aw-dlp-influx-exporter.timer aw-worktime-autoheal.timer'
  check_ansible_shell "AW server failed units" aw_server 'failed=$(systemctl --failed --no-legend | awk "{print \$1}" | grep -E "activitywatch|aw-|influx|grafana|prometheus|loki" || true); test -z "$failed" && echo "no AW-related failed units" || { echo "$failed"; exit 1; }'
  check_ansible_shell "AW server local health script" aw_server 'test -x /opt/activitywatch/health-check.sh && /opt/activitywatch/health-check.sh || test -x /usr/local/bin/health-check.sh && /usr/local/bin/health-check.sh || echo "health-check script not installed"'
else
  skip "AW server systemd checks skipped"
fi

if [ "$RUN_WINRM" = "1" ] && have ansible; then
  section "Windows WinRM And Collectors"
  check_ansible_module "Windows win_ping" aw_windows win_ping
  check_ansible_win_shell "Windows sessions" aw_windows '$psi = [System.Diagnostics.ProcessStartInfo]::new(); $psi.FileName = "$env:SystemRoot\System32\query.exe"; $psi.Arguments = "user"; $psi.UseShellExecute = $false; $psi.RedirectStandardOutput = $true; $psi.RedirectStandardError = $true; $psi.StandardOutputEncoding = [System.Text.Encoding]::GetEncoding(866); $psi.StandardErrorEncoding = [System.Text.Encoding]::GetEncoding(866); $p = [System.Diagnostics.Process]::Start($psi); $out = $p.StandardOutput.ReadToEnd(); $err = $p.StandardError.ReadToEnd(); $p.WaitForExit(); [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); $out; if ($err) { $err }; if ($out -match "USERNAME|ПОЛЬЗОВАТЕЛЬ|администратор|Администратор") { exit 0 } else { exit $p.ExitCode }'
  check_ansible_win_shell_warn "Windows collector processes" aw_windows '$p = Get-Process aw-watcher-afk,aw-watcher-window -ErrorAction SilentlyContinue; if ($p) { $p | Select-Object Name,Id,SessionId,StartTime | Format-Table -AutoSize } else { "no aw-watcher-afk/window process visible to this WinRM session" }'
  check_ansible_win_shell "Windows ActivityWatch tasks" aw_windows 'schtasks /Query /TN "ActivityWatch Recovery" /FO LIST /V; Get-Content -Raw "C:\ProgramData\AWatch-rus\deployment-config.json" | ConvertFrom-Json | Select-Object -ExpandProperty userTasks | Select-Object LaunchTaskName,UserId | Format-Table -AutoSize'
else
  skip "Windows WinRM checks skipped"
fi

run_remote_proxmox_script

section "Existing Repo Checks"
if [ -x "$REPO_ROOT/check-aw-data.sh" ]; then
  if "$REPO_ROOT/check-aw-data.sh"; then pass "check-aw-data.sh completed"; else fail "check-aw-data.sh failed"; fi
else
  skip "check-aw-data.sh missing"
fi
if [ -x "$REPO_ROOT/check-aw-full.sh" ]; then
  if AW_SMOKE_AW_SERVER="$AW_SERVER" AW_SMOKE_SOURCE_HOSTNAME="$AW_SOURCE_HOSTNAME" AW_SMOKE_WINDOWS_HOST="$WINDOWS_HOST" "$REPO_ROOT/check-aw-full.sh"; then pass "check-aw-full.sh completed"; else fail "check-aw-full.sh failed"; fi
else
  skip "check-aw-full.sh missing"
fi

section "Summary"
printf "OK=%s WARN=%s FAIL=%s SKIP=%s\n" "$OK_COUNT" "$WARN_COUNT" "$FAIL_COUNT" "$SKIP_COUNT"
printf "Log: %s\n" "$LOG_FILE"

if [ "$FAIL_COUNT" -gt 0 ]; then
  exit 2
fi
exit 0
