#!/usr/bin/env bash
set -Eeuo pipefail
set +x
umask 077

readonly LABEL=Q193_REDIS_RESOURCE_READONLY
readonly SCRIPT_PATH="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)/$(basename -- "${BASH_SOURCE[0]}")"
readonly AUDIT_ROOT=/var/backups/zhct-q193/redis
readonly EXPECTED_LOCK_OWNER=REDIS_Q193

mode=${1:-}
node=
target_host=LOCAL_STATIC_ONLY
target_ip=LOCAL_STATIC_ONLY
original_args=("$@")

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

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

finish() {
    local rc=$?
    trap - EXIT
    printf 'END|%s|NODE=%s|HOST=%s|IP=%s|RC=%s\n' \
        "$LABEL" "${node:-STATIC}" "$target_host" "$target_ip" "$rc"
    if test "$rc" -eq 0 && test "$mode" = --static-validate; then
        echo 'Q193_JUDGMENT=LOCAL_STATIC_ONLY_REMOTE_NOT_EXECUTED'
    elif test "$rc" -eq 0; then
        echo 'Q193_JUDGMENT=ONE_REDIS_NODE_RESOURCE_READONLY_PASS_NOT_BOTH_NODES_OR_HA'
    else
        echo 'Q193_JUDGMENT=FAIL_CLOSED'
    fi
    echo 'Q193_ROLLBACK_STATE=NO_SYSTEM_CHANGE_TO_ROLL_BACK'
    exit "$rc"
}
trap finish EXIT

parse_args() {
    case "$mode" in
        --static-validate)
            test "$#" -eq 1 || die 'static validation accepts no extra arguments'
            ;;
        run)
            test "$#" -eq 3 || die 'run requires --node redis01|redis02'
            test "${2:-}" = --node || die 'run requires --node'
            node=${3:-}
            case "$node" in
                redis01)
                    target_host=sqjcredis01
                    target_ip=10.2.203.112
                    ;;
                redis02)
                    target_host=sqjcredis02
                    target_ip=10.2.203.113
                    ;;
                *)
                    die "unsupported node: $node"
                    ;;
            esac
            ;;
        *)
            die 'usage: q193-redis-resource-readonly.sh --static-validate | run --node redis01|redis02'
            ;;
    esac
}

dependency_values_gate() {
    local dep132=$1 sha132=$2 dep160=$3 sha160=$4 dep192=$5 sha192=$6
    local external=$7 external_ref=$8 lock=$9 owner=${10}
    test "$dep132" = PASS &&
        [[ "$sha132" =~ ^[0-9a-f]{64}$ ]] &&
        test "$dep160" = PASS &&
        [[ "$sha160" =~ ^[0-9a-f]{64}$ ]] &&
        test "$dep192" = PASS &&
        [[ "$sha192" =~ ^[0-9a-f]{64}$ ]] &&
        test "$external" = PASS &&
        test -n "$external_ref" &&
        test "$lock" = 1 &&
        test "$owner" = "$EXPECTED_LOCK_OWNER"
}

dependency_lock_gate() {
    dependency_values_gate \
        "${Q193_DEP_Q132:-}" "${Q193_Q132_PASS_SHA256:-}" \
        "${Q193_DEP_Q160:-}" "${Q193_Q160_PASS_SHA256:-}" \
        "${Q193_DEP_Q192:-}" "${Q193_Q192_PASS_SHA256:-}" \
        "${Q193_EXTERNAL_VM_RESOURCE_CONFIRMATION:-}" \
        "${Q193_EXTERNAL_VM_RESOURCE_CONFIRMATION_REF:-}" \
        "${Q193_REMOTE_LOCK_GRANTED:-}" "${Q193_REMOTE_LOCK_OWNER:-}" ||
        die 'Q132/Q160/Q192/external VM confirmation/REDIS_Q193 lock gate is incomplete'
    [[ "${Q193_RUN_ID:-}" =~ ^q193-[A-Za-z0-9._+-]+$ ]] ||
        die 'invalid or missing Q193_RUN_ID'
    echo 'Q193_DEPENDENCY_LOCK_GATE=PASS'
}

identity_values_gate() {
    local actual_host=$1 actual_ips=$2 expected_host=$3 expected_ip=$4
    test "$actual_host" = "$expected_host" &&
        grep -Fxq "$expected_ip" <<<"$actual_ips"
}

identity_gate() {
    local actual_host actual_ips
    test "$EUID" -eq 0 || die 'root is required'
    actual_host=$(hostname -s)
    actual_ips=$(
        ip -o -4 addr show scope global |
            awk '{split($4,a,"/"); print a[1]}'
    )
    identity_values_gate "$actual_host" "$actual_ips" "$target_host" "$target_ip" ||
        die 'hostname or IP mismatch'
    test -r /etc/zhct/node.env || die 'node.env is absent'
    grep -Fxq 'ROLE=redis' /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'
    echo 'Q193_IDENTITY_GATE=PASS'
}

static_validate() {
    local good_sha
    good_sha=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    bash -n "$SCRIPT_PATH"
    for marker in \
        Q193_DEP_Q132 Q193_Q132_PASS_SHA256 \
        Q193_DEP_Q160 Q193_Q160_PASS_SHA256 \
        Q193_DEP_Q192 Q193_Q192_PASS_SHA256 \
        Q193_EXTERNAL_VM_RESOURCE_CONFIRMATION \
        Q193_EXTERNAL_VM_RESOURCE_CONFIRMATION_REF \
        Q193_REMOTE_LOCK_GRANTED Q193_REMOTE_LOCK_OWNER \
        redis01 redis02 sqjcredis01 sqjcredis02 \
        RESOURCE_TARGET_VCPU RESOURCE_TARGET_MEMORY_NOMINAL_GIB \
        RESOURCE_TARGET_SYSTEM_DISK_NOMINAL_GB
    do
        grep -Fq "$marker" "$SCRIPT_PATH" || die "missing marker: $marker"
    done
    dependency_values_gate \
        PASS "$good_sha" PASS "$good_sha" PASS "$good_sha" \
        PASS external-confirmation-reference 1 REDIS_Q193 ||
        die 'positive dependency fixture failed'
    if dependency_values_gate \
        '' "$good_sha" PASS "$good_sha" PASS "$good_sha" \
        PASS external-confirmation-reference 1 REDIS_Q193
    then
        die 'missing Q132 fixture unexpectedly passed'
    fi
    if dependency_values_gate \
        PASS "$good_sha" '' "$good_sha" PASS "$good_sha" \
        PASS external-confirmation-reference 1 REDIS_Q193
    then
        die 'missing Q160 fixture unexpectedly passed'
    fi
    if dependency_values_gate \
        PASS "$good_sha" PASS "$good_sha" '' "$good_sha" \
        PASS external-confirmation-reference 1 REDIS_Q193
    then
        die 'missing Q192 fixture unexpectedly passed'
    fi
    if dependency_values_gate \
        PASS "$good_sha" PASS "$good_sha" PASS "$good_sha" \
        '' external-confirmation-reference 1 REDIS_Q193
    then
        die 'missing external confirmation fixture unexpectedly passed'
    fi
    if dependency_values_gate \
        PASS "$good_sha" PASS "$good_sha" PASS "$good_sha" \
        PASS external-confirmation-reference 0 REDIS_Q193
    then
        die 'missing lock fixture unexpectedly passed'
    fi
    if identity_values_gate wrong-host "$target_ip" "$target_host" "$target_ip"; then
        die 'wrong-host fixture unexpectedly passed'
    fi
    echo 'Q193_MISSING_DEPENDENCY_NEGATIVE_FIXTURES=PASS'
    echo 'Q193_WRONG_HOST_ZERO_PERSISTENT_WRITE_FIXTURE=PASS'
    echo 'Q193_LOCAL_STATIC_VALIDATE_PASS'
    echo 'Q193_REMOTE_STATUS=NOT_EXECUTED'
}

setup_audit_wrapper() {
    local audit_log
    if test "${Q193_LOG_WRAPPED:-0}" = 1; then
        return
    fi
    mkdir -p "$AUDIT_ROOT"
    chmod 0700 "$AUDIT_ROOT"
    audit_log="$AUDIT_ROOT/${Q193_RUN_ID}-${node}.log"
    export Q193_LOG_WRAPPED=1
    set +e
    "$SCRIPT_PATH" "${original_args[@]}" 2>&1 | tee -a "$audit_log"
    local wrapped_rc=${PIPESTATUS[0]}
    set -e
    exit "$wrapped_rc"
}

parse_redis_bytes() {
    local raw=${1,,} number multiplier
    case "$raw" in
        *kb) number=${raw%kb}; multiplier=1000 ;;
        *mb) number=${raw%mb}; multiplier=1000000 ;;
        *gb) number=${raw%gb}; multiplier=1000000000 ;;
        *k) number=${raw%k}; multiplier=1024 ;;
        *m) number=${raw%m}; multiplier=1048576 ;;
        *g) number=${raw%g}; multiplier=1073741824 ;;
        *) number=$raw; multiplier=1 ;;
    esac
    [[ "$number" =~ ^[0-9]+$ ]] || die "unsupported maxmemory value syntax: $raw"
    printf '%s\n' "$((number * multiplier))"
}

resource_gate() {
    local vcpu mem_bytes max_disk_bytes root_bytes root_avail root_used_percent
    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}'
    )
    read -r root_bytes root_avail root_used_percent < <(
        df -B1 --output=size,avail,pcent / |
            awk 'NR==2 {gsub(/%/,"",$3); print $1, $2, $3}'
    )
    printf 'RESOURCE_TARGET_VCPU=4 ACTUAL=%s\n' "$vcpu"
    printf 'RESOURCE_TARGET_MEMORY_NOMINAL_GIB=8 ACTUAL_BYTES=%s\n' "$mem_bytes"
    printf 'RESOURCE_TARGET_SYSTEM_DISK_NOMINAL_GB=100 ACTUAL_MAX_DISK_BYTES=%s\n' "$max_disk_bytes"
    printf 'RESOURCE_ROOT_FS_BYTES=%s AVAILABLE_BYTES=%s USED_PERCENT=%s\n' \
        "$root_bytes" "$root_avail" "$root_used_percent"
    test "$vcpu" -eq 4 || die 'vCPU count differs from approved 4C target'
    test "$mem_bytes" -ge 7516192768 ||
        die 'memory is below the 7 GiB OS-visible floor for 8G target'
    test "$max_disk_bytes" -ge 99000000000 ||
        die 'system disk is below the 100 GB nominal target floor'
    test "$root_bytes" -ge 90000000000 ||
        die 'root filesystem was not expanded to the approved 100G-disk layout'
    test "$root_used_percent" -le 80 ||
        die 'root filesystem has less than the required 20 percent free space'
    echo 'Q193_RESOURCE_SIZE_GATE=PASS'
}

redis_persistence_memory_gate() {
    local cfg value bytes sum=0 mem_bytes reserve upper_bound
    mem_bytes=$(awk '/^MemTotal:/ {printf "%.0f\n", $2 * 1024}' /proc/meminfo)
    for cfg in /etc/redis/redis5.conf /etc/redis/redis7.conf; do
        test -f "$cfg" && test ! -L "$cfg" || die "missing regular Redis config: $cfg"
        grep -Eq '^[[:space:]]*appendonly[[:space:]]+yes([[:space:]]|$)' "$cfg" ||
            die "AOF is not enabled in $cfg"
        grep -Eq '^[[:space:]]*dbfilename[[:space:]]+[^[:space:]]+' "$cfg" ||
            die "RDB filename is absent in $cfg"
        if grep -Eq '^[[:space:]]*save[[:space:]]+""([[:space:]]|$)' "$cfg"; then
            die "RDB save is explicitly disabled in $cfg"
        fi
        if grep -Eq '^[[:space:]]*appendfsync[[:space:]]+' "$cfg"; then
            grep -Eq '^[[:space:]]*appendfsync[[:space:]]+everysec([[:space:]]|$)' "$cfg" ||
                die "appendfsync is not everysec in $cfg"
        fi
        value=$(
            awk '$1=="maxmemory" {value=$2} END {print value}' "$cfg"
        )
        test -n "$value" || die "explicit maxmemory is absent in $cfg"
        bytes=$(parse_redis_bytes "$value")
        test "$bytes" -gt 0 || die "maxmemory must be positive in $cfg"
        sum=$((sum + bytes))
        printf 'REDIS_CONFIG=%s AOF=yes RDB_FILENAME=present APPENDFSYNC=everysec_or_default MAXMEMORY_BYTES=%s\n' \
            "$cfg" "$bytes"
    done
    upper_bound=$((mem_bytes * 70 / 100))
    reserve=$((mem_bytes - sum))
    printf 'REDIS_MAXMEMORY_SUM_BYTES=%s UPPER_BOUND_70_PERCENT=%s RESERVE_BYTES=%s\n' \
        "$sum" "$upper_bound" "$reserve"
    test "$sum" -le "$upper_bound" ||
        die 'combined Redis maxmemory exceeds 70 percent of OS-visible memory'
    test "$reserve" -ge 2147483648 ||
        die 'less than 2 GiB remains for OS, fork/COW, replication, and Sentinel'
    echo 'Q193_AOF_RDB_MEMORY_CAPACITY_BASIS=PASS'
}

path_snapshot() {
    local path
    for path in /data/redis5 /data/redis7 /var/log/redis5 /var/log/redis7; do
        test -d "$path" && test ! -L "$path" || die "required directory is absent or unsafe: $path"
        stat -c 'PATH=%n TYPE=%F OWNER=%U:%G MODE=%a' "$path"
        findmnt -T "$path"
        df -B1 -T "$path"
        du -sx -B1 "$path"
    done
    echo 'Q193_REDIS_PATH_CAPACITY_SNAPSHOT=PASS'
}

memory_kernel_gate() {
    test "$(sysctl -n vm.overcommit_memory)" = 1 ||
        die 'vm.overcommit_memory is not 1'
    grep -Fq '[never]' /sys/kernel/mm/transparent_hugepage/enabled ||
        die 'THP is not disabled'
    sysctl vm.overcommit_memory
    printf 'THP_ENABLED='
    sed -n '1p' /sys/kernel/mm/transparent_hugepage/enabled
    echo 'Q193_REDIS_KERNEL_MEMORY_GATE=PASS'
}

unit_listener_gate() {
    local unit enabled active
    for unit in redis5 redis7; do
        enabled=$(systemctl is-enabled "$unit.service" 2>/dev/null || true)
        active=$(systemctl is-active "$unit.service" 2>/dev/null || true)
        printf 'UNIT=%s ENABLED=%s ACTIVE=%s\n' "$unit.service" "$enabled" "$active"
        test "$enabled" = enabled || die "$unit.service is not enabled"
        test "$active" = active || die "$unit.service is not active"
    done
    for unit in sentinel5 sentinel7 orchestrator; do
        enabled=$(systemctl is-enabled "$unit.service" 2>/dev/null || true)
        active=$(systemctl is-active "$unit.service" 2>/dev/null || true)
        printf 'UNIT=%s ENABLED=%s ACTIVE=%s\n' "$unit.service" "$enabled" "$active"
        test "$enabled" = disabled || die "$unit.service is not disabled"
        test "$active" = inactive || die "$unit.service is not inactive"
    done
    ss -lntupH
    ss -lntH | awk '{print $4}' | grep -Fxq "$target_ip:6379" ||
        die 'Redis5 target listener is absent'
    ss -lntH | awk '{print $4}' | grep -Fxq "$target_ip:6387" ||
        die 'Redis7 target listener is absent'
    if ss -lntH | awk '{print $4}' |
        grep -Eq ':(26379|26387|3000|10008)$'
    then
        die 'Sentinel or Orchestrator listener is present before its task'
    fi
    if ip -o -4 addr show | grep -Eq '10\.2\.203\.(115|116|117)/'; then
        die 'VIP is present before Q-251'
    fi
    echo 'Q193_REDIS_UNIT_LISTENER_VIP_GATE=PASS'
}

resource_snapshot() {
    lscpu
    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
    echo 'Q193_RESOURCE_SNAPSHOT_CAPTURED=PASS'
}

run_readonly_gate() {
    dependency_lock_gate
    for command in \
        awk bash date df du findmnt free grep hostname id ip lscpu lsblk \
        nproc sed ss stat sysctl systemctl tee
    do
        need "$command"
    done
    identity_gate
    setup_audit_wrapper
    resource_gate
    path_snapshot
    memory_kernel_gate
    redis_persistence_memory_gate
    unit_listener_gate
    resource_snapshot
    echo 'Q193_ONE_REDIS_NODE_RESOURCE_READONLY_PASS'
    echo 'Q193_BOTH_REDIS_NODES_PASS=NOT_ASSERTED_BY_NODE_SCRIPT'
    echo 'Q193_REPLICATION_SENTINEL_ORCHESTRATOR_VIP_HA_PASS=NOT_ASSERTED'
}

parse_args "$@"
printf 'BEGIN|%s|NODE=%s|HOST=%s|IP=%s|TIME=%s\n' \
    "$LABEL" "${node:-STATIC}" "$target_host" "$target_ip" \
    "$(date '+%Y-%m-%dT%H:%M:%S%z')"

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