#!/usr/bin/env bash

set -Eeuo pipefail
umask 077

SCRIPT_PATH=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")
ORIGINAL_ARGS=("$@")
AUDIT_ROOT=${Q190_AUDIT_ROOT:-/root/q190-nginx-readonly-audit}

usage() {
    cat <<'USAGE'
Usage:
  q190-nginx-resource-readonly.sh --static-validate
  q190-nginx-resource-readonly.sh run --node nginx01|nginx02
USAGE
}

die() {
    printf 'Q190_ERROR=%s\n' "$*" >&2
    return 1
}

need() {
    command -v "$1" >/dev/null 2>&1 || die "missing command: $1"
}

mode=${1:-}
node=
if [[ "$mode" == --static-validate && $# -eq 1 ]]; then
    target_host=LOCAL_STATIC_ONLY
    target_ip=LOCAL_STATIC_ONLY
elif [[ "$mode" == run && $# -eq 3 && ${2:-} == --node ]]; then
    node=$3
    case "$node" in
        nginx01) target_host=sqjcnginx01; target_ip=10.2.203.102 ;;
        nginx02) target_host=sqjcnginx02; target_ip=10.2.203.103 ;;
        *) die "unsupported node: $node"; exit 2 ;;
    esac
else
    usage
    exit 2
fi

run_id=${Q190_RUN_ID:-}
if [[ "$mode" == --static-validate ]]; then
    run_id=${run_id:-q190-static-validate}
    audit_log=/dev/null
else
    [[ "$run_id" =~ ^q190-[A-Za-z0-9._+-]+$ ]] || die 'invalid or missing Q190_RUN_ID'
    mkdir -p "$AUDIT_ROOT"
    chmod 0700 "$AUDIT_ROOT"
    audit_log="$AUDIT_ROOT/${run_id}-${node}.log"
fi

if [[ "$mode" == run && ${Q190_LOG_WRAPPED:-0} != 1 ]]; then
    export Q190_LOG_WRAPPED=1
    set +e
    "$SCRIPT_PATH" "${ORIGINAL_ARGS[@]}" 2>&1 | tee -a "$audit_log"
    wrapped_rc=${PIPESTATUS[0]}
    exit "$wrapped_rc"
fi

finish() {
    local rc=$?
    printf 'Q190_MODE_END=%s\n' "$mode"
    printf 'Q190_END_TIME=%s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')"
    printf 'Q190_TARGET_HOST=%s\n' "$target_host"
    printf 'Q190_TARGET_IP=%s\n' "$target_ip"
    printf 'Q190_TARGET_NODE=%s\n' "${node:-STATIC}"
    printf 'Q190_RC=%s\n' "$rc"
    printf 'Q190_ROLLBACK_STATE=NO_SYSTEM_CHANGE_TO_ROLL_BACK\n'
    if [[ "$rc" -eq 0 && "$mode" == --static-validate ]]; then
        printf 'Q190_JUDGMENT=LOCAL_STATIC_ONLY_REMOTE_NOT_EXECUTED\n'
    elif [[ "$rc" -eq 0 ]]; then
        printf 'Q190_JUDGMENT=NGINX_NODE_READONLY_PASS_NOT_Q194_OR_GLOBAL_RESOURCE_PASS\n'
    else
        printf 'Q190_JUDGMENT=FAIL_CLOSED\n'
    fi
    return "$rc"
}
trap finish EXIT

printf 'Q190_MODE_BEGIN=%s\n' "$mode"
printf 'Q190_START_TIME=%s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z')"
printf 'Q190_TARGET_HOST=%s\n' "$target_host"
printf 'Q190_TARGET_IP=%s\n' "$target_ip"
printf 'Q190_TARGET_NODE=%s\n' "${node:-STATIC}"
printf 'Q190_RUN_ID=%s\n' "$run_id"

static_validate() {
    bash -n "$SCRIPT_PATH"
    for required in --static-validate run nginx01 nginx02 Q190_DEP_Q160 Q190_EXTERNAL_VM_RESOURCE_CONFIRMATION; do
        grep -Fq -- "$required" "$SCRIPT_PATH" || die "missing marker: $required"
    done
    printf 'Q190_LOCAL_STATIC_VALIDATE_PASS\n'
    printf 'Q190_REMOTE_STATUS=NOT_EXECUTED\n'
}

dependency_lock_gate() {
    [[ ${Q190_DEP_Q160:-} == PASS ]] || die 'Q-160 PASS is not asserted'
    [[ ${Q190_EXTERNAL_VM_RESOURCE_CONFIRMATION:-} == PASS ]] ||
        die 'customer or virtualization resource confirmation is absent'
    [[ ${Q190_REMOTE_LOCK_GRANTED:-} == 1 ]] || die 'remote lock is not asserted'
    [[ ${Q190_REMOTE_LOCK_OWNER:-} == NGINX_Q190 ]] || die 'remote lock owner mismatch'
    printf 'Q190_DEPENDENCY_LOCK_GATE=PASS\n'
}

identity_gate() {
    [[ $(id -u) -eq 0 ]] || die 'root is required'
    [[ "$(hostname -s)" == "$target_host" ]] || die 'hostname mismatch'
    ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1 |
        grep -Fxq "$target_ip" || die 'target IP mismatch'
    [[ -r /etc/zhct/node.env ]] || die 'node.env is absent'
    grep -Fxq 'ROLE=nginx' /etc/zhct/node.env || die 'role mismatch'
    grep -Fxq "NODE_KEY=$node" /etc/zhct/node.env || die 'node key mismatch'
    grep -Fxq "NODE_IP=$target_ip" /etc/zhct/node.env || die 'node IP mismatch'
    printf 'Q190_IDENTITY_GATE=PASS\n'
}

resource_gate() {
    local vcpu mem_bytes max_disk_bytes root_bytes
    vcpu=$(nproc)
    mem_bytes=$(awk '/^MemTotal:/ {printf "%.0f\n", $2 * 1024}' /proc/meminfo)
    max_disk_bytes=$(lsblk -bdno TYPE,SIZE |
        awk '$1=="disk" && $2>max {max=$2} END {printf "%.0f\n", max+0}')
    root_bytes=$(df -B1 --output=size / | awk 'NR==2 {print $1}')
    printf 'RESOURCE_VCPU=%s EXPECTED=4\n' "$vcpu"
    printf 'RESOURCE_MEM_BYTES=%s EXPECTED_NOMINAL_GIB=8\n' "$mem_bytes"
    printf 'RESOURCE_MAX_DISK_BYTES=%s EXPECTED_NOMINAL_GB=50\n' "$max_disk_bytes"
    printf 'RESOURCE_ROOT_FS_BYTES=%s\n' "$root_bytes"
    [[ "$vcpu" -eq 4 ]] || die 'vCPU count differs from approved 4C target'
    [[ "$mem_bytes" -ge 7516192768 ]] || die 'memory is below the 7 GiB OS-visible floor for 8G target'
    [[ "$max_disk_bytes" -ge 49000000000 ]] || die 'system disk is below the 50 GB nominal target floor'
    printf 'Q190_NGINX_RESOURCE_SIZE_GATE=PASS\n'
}

resource_snapshot() {
    printf 'Q190_LSCPU_BEGIN\n'
    lscpu
    printf 'Q190_LSCPU_END\n'
    free -b
    lsblk -b -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS
    findmnt -R /
    df -B1 -T
    if command -v pvs >/dev/null 2>&1; then pvs --units b --nosuffix; fi
    if command -v vgs >/dev/null 2>&1; then vgs --units b --nosuffix; fi
    if command -v lvs >/dev/null 2>&1; then lvs --units b --nosuffix; fi
    printf 'Q190_RESOURCE_SNAPSHOT_CAPTURED=PASS\n'
}

time_name_firewall_snapshot() {
    local failures=0 host ip_value
    timedatectl show -p Timezone -p NTP -p NTPSynchronized
    [[ "$(timedatectl show -p NTPSynchronized --value)" == yes ]] ||
        die 'NTP is not synchronized'
    if command -v chronyc >/dev/null 2>&1; then
        chronyc tracking
        chronyc sources -v
    fi
    printf 'Q190_NTP_READONLY_GATE=PASS\n'

    printf 'Q190_HOSTS_FILE_BEGIN\n'
    sed -n '1,240p' /etc/hosts
    printf 'Q190_HOSTS_FILE_END\n'
    printf 'Q190_RESOLV_CONF_BEGIN\n'
    sed -n '1,160p' /etc/resolv.conf
    printf 'Q190_RESOLV_CONF_END\n'

    while read -r host ip_value; do
        if getent hosts "$host" | awk -v expected="$ip_value" '$1==expected {ok=1} END {exit !ok}'; then
            printf 'NAME_RESOLUTION HOST=%s IP=%s RESULT=MATCH\n' "$host" "$ip_value"
        else
            printf 'NAME_RESOLUTION HOST=%s IP=%s RESULT=MISSING_OR_MISMATCH_DEFER_Q200\n' \
                "$host" "$ip_value"
            failures=$((failures + 1))
        fi
    done <<'HOSTS'
sqjcnginx01 10.2.203.102
sqjcnginx02 10.2.203.103
sqjcapp01 10.2.203.104
sqjcapp02 10.2.203.105
sqjcDB01 10.2.208.106
sqjcDB02 10.2.208.107
sqjcmqtt01 10.2.203.108
sqjcmqtt02 10.2.203.109
sqjcmqtt03 10.2.203.110
sqjcredis01 10.2.203.112
sqjcredis02 10.2.203.113
HOSTS
    printf 'Q190_NAME_RESOLUTION_DEFER_Q200_COUNT=%s\n' "$failures"

    ip -o -4 addr show
    ip route show table all
    if command -v firewall-cmd >/dev/null 2>&1; then
        firewall-cmd --state || true
        firewall-cmd --get-active-zones || true
        firewall-cmd --list-all-zones || true
    else
        printf 'FIREWALL_CMD=ABSENT\n'
    fi
    printf 'Q190_NETWORK_FIREWALL_SNAPSHOT_CAPTURED_READONLY=PASS\n'
}

service_safety_boundary() {
    local unit enabled active
    for unit in nginx haproxy orchestrator sentinel5 sentinel7 keepalived; do
        enabled=$(systemctl is-enabled "$unit" 2>/dev/null || true)
        active=$(systemctl is-active "$unit" 2>/dev/null || true)
        printf 'UNIT=%s ENABLED=%s ACTIVE=%s\n' "$unit" "$enabled" "$active"
    done
    ss -lntup
    if ip -o -4 addr show | grep -Eq '10\.2\.203\.(115|116|117)/'; then
        die 'VIP is present before Q-251'
    fi
    printf 'Q190_VIP_ABSENT=PASS\n'
}

run_gate() {
    dependency_lock_gate
    for command in awk bash cut date df findmnt free getent grep hostname id ip lscpu lsblk nproc sed ss systemctl tee timedatectl; do
        need "$command"
    done
    identity_gate
    resource_gate
    resource_snapshot
    time_name_firewall_snapshot
    service_safety_boundary
    printf 'Q190_NGINX_NODE_READONLY_PASS\n'
    printf 'Q190_BOTH_NGINX_NODES_PASS=NOT_ASSERTED_BY_NODE_SCRIPT\n'
    printf 'Q191_Q192_Q193_Q194_OTHER_ROLE_RESOURCE_CONFIRMATION=REQUIRED\n'
    printf 'Q194_PASS=NOT_ASSERTED_BY_NGINX_Q190_SCRIPT\n'
}

case "$mode" in
    --static-validate) static_validate ;;
    run) run_gate ;;
esac
