#!/bin/bash
# =============================================================================
# security-password.sh -- Vault-Driven Credential Pipeline
# =============================================================================
# Category: security | Bird: falcon | Tier: 0 | Node-aware: yes
#
# Pipeline: REQUEST → VAULT CREATE → READY → DELETE → MOVE → PERMISSIONS → VERIFY → ACK
# Passwords NEVER printed to stdout, logs, or command line arguments.
# =============================================================================

TOOL_NAME="security-password"
TOOL_VERSION="3.0.0"
TOOL_TIER=0
TOOL_BIRD="falcon"
TOOL_LOG="/opt/binary/logs/tools/${TOOL_NAME}.log"

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [[ -f "${SCRIPT_DIR}/common.sh" ]]; then source "${SCRIPT_DIR}/common.sh"
elif [[ -f "${SCRIPT_DIR}/../common.sh" ]]; then source "${SCRIPT_DIR}/../common.sh"
else echo "FATAL: common.sh not found" >&2; exit 1; fi

export BIRD_ID="${TOOL_BIRD}"

# === PATHS ===
TOOLING_ENV="/etc/binary/tooling.env"
DB_SECRET="/opt/binary/web/a.Clean Earth.org/private/db.secret.php"
HMAC_KEY="/etc/binary/hmac.key"
VAULT_KEY="/etc/binary/vault.key"
ROTATION_LOG="/opt/binary/logs/security/rotation.log"

# === VAULT CONFIG ===
VAULT_NODE="${VAULT_NODE:-10.69.69.8}"        # Vault = .8
VAULT_PORT="${VAULT_PORT:-9922}"
VAULT_USER="${VAULT_USER:-automation}"
VAULT_SSH_KEY="/opt/binary/keys/automation"
VAULT_STAGING="/opt/binary/vault/staging"         # Vault writes here
LOCAL_STAGING="/opt/binary/data/.credential_stage" # Local staging area
CREDENTIAL_TARGETS=(db hmac vault)             # What we rotate

CHANGES_MADE=""
_change() { CHANGES_MADE="${CHANGES_MADE}  ${1}\n"; echo "  [CHANGED] ${1}"; }
_log_rotation() {
    mkdir -p "$(dirname "$ROTATION_LOG")" 2>/dev/null
    echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$ROTATION_LOG"
}

# =============================================================================
# STEP 1: REQUEST — Ask vault to create new credentials
# =============================================================================
_vault_request() {
    local target="${1:-all}"
    echo "  [1/6] Requesting vault to create credentials (target=${target})..."

    # Build the request payload
    local request_id
    request_id="rot-$(date +%s)-${NODE_ID}"
    local request_file="/tmp/.vault_request_${request_id}"

    cat > "$request_file" <<EOF
{
    "request_id": "${request_id}",
    "node": "${NODE_ID}",
    "target": "${target}",
    "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
    "requested_by": "$(whoami)"
}
EOF

    # Send request to vault via SCP (BatchMode=yes prevents password prompts)
    if [ -f "$VAULT_SSH_KEY" ]; then
        scp -i "$VAULT_SSH_KEY" -P "$VAULT_PORT" -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o BatchMode=yes \
            "$request_file" \
            "${VAULT_USER}@${VAULT_NODE}:${VAULT_STAGING}/requests/${request_id}.json" 2>/dev/null
        local rc=$?
        rm -f "$request_file"
        if [ $rc -eq 0 ]; then
            echo "  [1/6] Request sent: ${request_id}" >&2
            echo "$request_id"
            return 0
        fi
    fi

    # Fallback: vault unreachable, generate locally (self-vault mode)
    rm -f "$request_file"
    echo "  [1/6] Vault unreachable — self-vault mode" >&2
    echo "SELF-${request_id}"
    return 0
}

# =============================================================================
# STEP 2: VAULT CREATE — Vault generates credential files
#         (In self-vault mode, we generate locally but NEVER print)
# =============================================================================
_vault_create() {
    local request_id="$1" target="$2"
    echo "  [2/6] Creating credentials..."

    mkdir -p "$LOCAL_STAGING" 2>/dev/null
    chmod 700 "$LOCAL_STAGING"

    if [[ "$request_id" == SELF-* ]]; then
        # Self-vault: generate locally
        _self_generate "$target"
    else
        # Wait for vault to stage files, then pull them
        _vault_pull "$request_id" "$target"
    fi
}

_self_generate() {
    local target="$1"

    if [[ "$target" == "all" || "$target" == "db" ]]; then
        local new_pass
        new_pass=$(openssl rand -hex 32 2>/dev/null)

        # Generate db.secret.php
        cat > "${LOCAL_STAGING}/db.secret.php" <<PHPEOF
<?php
return [
    'host' => '127.0.0.1',
    'name' => 's3rv1c3_admin',
    'user' => 'ops365',
    'pass' => '${new_pass}',
    'port' => 3306,
    'charset' => 'utf8mb4',
];
PHPEOF

        # Generate tooling.env
        echo "TOOLING_DB_PASS=${new_pass}" > "${LOCAL_STAGING}/tooling.env"

        # Generate SQL rotation file — rotates BOTH localhost (socket) and 127.0.0.1 (TCP)
        cat > "${LOCAL_STAGING}/rotate.sql" <<EOSQL
CREATE USER IF NOT EXISTS 'ops365'@'localhost' IDENTIFIED BY '${new_pass}';
CREATE USER IF NOT EXISTS 'ops365'@'127.0.0.1' IDENTIFIED BY '${new_pass}';
ALTER USER 'ops365'@'127.0.0.1' IDENTIFIED BY '${new_pass}';
ALTER USER 'ops365'@'localhost' IDENTIFIED BY '${new_pass}';
GRANT ALL ON s3rv1c3_admin.* TO 'ops365'@'127.0.0.1';
GRANT ALL ON s3rv1c3_admin.* TO 'ops365'@'localhost';
FLUSH PRIVILEGES;
EOSQL

        # Generate temp my.cnf for verification (protocol=TCP forces 127.0.0.1 not socket)
        cat > "${LOCAL_STAGING}/verify.cnf" <<MYCNF
[client]
user=ops365
password=${new_pass}
host=127.0.0.1
protocol=TCP
MYCNF

        # Wipe the variable immediately
        new_pass="REDACTED"; unset new_pass
        _change "staged: db credentials"
    fi

    if [[ "$target" == "all" || "$target" == "hmac" ]]; then
        openssl rand -hex 32 > "${LOCAL_STAGING}/hmac.key" 2>/dev/null
        _change "staged: hmac key"
    fi

    if [[ "$target" == "all" || "$target" == "vault" ]]; then
        openssl rand -hex 32 > "${LOCAL_STAGING}/vault.key" 2>/dev/null
        _change "staged: vault key"
    fi

    # Lock down staging
    chmod 600 "${LOCAL_STAGING}"/* 2>/dev/null
}

_vault_pull() {
    local request_id="$1" target="$2"
    local retries=0 max_retries=10

    while [ $retries -lt $max_retries ]; do
        # Check if vault has staged the ready signal
        local ready
        ready=$(ssh -i "$VAULT_SSH_KEY" -p "$VAULT_PORT" -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o BatchMode=yes \
            "${VAULT_USER}@${VAULT_NODE}" \
            "test -f '${VAULT_STAGING}/ready/${request_id}.ready' && echo YES" 2>/dev/null)

        if [ "$ready" = "YES" ]; then
            echo "  [2/6] Vault signaled READY"
            # Pull staged files
            scp -i "$VAULT_SSH_KEY" -P "$VAULT_PORT" -o StrictHostKeyChecking=no -o BatchMode=yes \
                "${VAULT_USER}@${VAULT_NODE}:${VAULT_STAGING}/files/${request_id}/*" \
                "${LOCAL_STAGING}/" 2>/dev/null
            chmod 600 "${LOCAL_STAGING}"/* 2>/dev/null
            _change "pulled from vault: ${request_id}"
            return 0
        fi

        retries=$((retries + 1))
        sleep 1
    done

    echo "  [2/6] Vault timeout — falling back to self-vault"
    _self_generate "$target"
}

# =============================================================================
# STEP 3: INSTALL_ATOMIC — Apply new credentials BESIDE old (rollback safe)
# =============================================================================
_credential_install() {
    local target="$1"
    echo "  [3/7] Installing new credentials (atomic, beside old)..."

    if [[ "$target" == "all" || "$target" == "db" ]]; then
        # Apply SQL rotation — MariaDB now accepts BOTH old and new password
        if [ -f "${LOCAL_STAGING}/rotate.sql" ] && command -v mysql &>/dev/null && systemctl is-active mariadb &>/dev/null; then
            mysql < "${LOCAL_STAGING}/rotate.sql" 2>/dev/null
            _change "MariaDB: ops365 password rotated via SQL"
            rm -f "${LOCAL_STAGING}/rotate.sql"
        fi

        # Install new config files (overwrites old — but MariaDB still has both)
        [ -f "${LOCAL_STAGING}/db.secret.php" ] && mv -f "${LOCAL_STAGING}/db.secret.php" "$DB_SECRET" && _change "installed: db.secret.php"
        [ -f "${LOCAL_STAGING}/tooling.env" ] && mv -f "${LOCAL_STAGING}/tooling.env" "$TOOLING_ENV" && _change "installed: tooling.env"
    fi

    if [[ "$target" == "all" || "$target" == "hmac" ]]; then
        [ -f "${LOCAL_STAGING}/hmac.key" ] && mv -f "${LOCAL_STAGING}/hmac.key" "$HMAC_KEY" && _change "installed: hmac.key"
    fi

    if [[ "$target" == "all" || "$target" == "vault" ]]; then
        [ -f "${LOCAL_STAGING}/vault.key" ] && mv -f "${LOCAL_STAGING}/vault.key" "$VAULT_KEY" && _change "installed: vault.key"
    fi
}

# =============================================================================
# STEP 4: PERMISSIONS — Fix ownership
# =============================================================================
_credential_permissions() {
    echo "  [4/7] Setting permissions..."
    [ -f "$DB_SECRET" ] && { chmod 640 "$DB_SECRET"; chown root:nginx "$DB_SECRET" 2>/dev/null || chown root:www-data "$DB_SECRET" 2>/dev/null; _change "perms: db.secret.php → 640 root:nginx"; }
    [ -f "$TOOLING_ENV" ] && { chmod 600 "$TOOLING_ENV"; chown root:root "$TOOLING_ENV"; _change "perms: tooling.env → 600 root:root"; }
    [ -f "$HMAC_KEY" ] && { chmod 600 "$HMAC_KEY"; chown root:root "$HMAC_KEY"; _change "perms: hmac.key → 600 root:root"; }
    [ -f "$VAULT_KEY" ] && { chmod 600 "$VAULT_KEY"; chown root:root "$VAULT_KEY"; _change "perms: vault.key → 600 root:root"; }
}

# =============================================================================
# STEP 5: VERIFY — Test new credentials live
# =============================================================================
_credential_verify() {
    local request_id="$1"
    echo "  [5/7] Verifying..."
    VERIFY_PASSED=true

    if [ -f "${LOCAL_STAGING}/verify.cnf" ] && command -v mysql &>/dev/null; then
        if mysql --defaults-file="${LOCAL_STAGING}/verify.cnf" -e "SELECT 1" &>/dev/null; then
            _change "VERIFY: DB connection OK"
        else
            echo "  [ERROR] DB connection FAILED — rollback possible"
            _change "VERIFY: DB FAILED"
            VERIFY_PASSED=false
        fi
        rm -f "${LOCAL_STAGING}/verify.cnf"
    elif [ -f "$DB_SECRET" ] && command -v php &>/dev/null; then
        local result
        result=$(php -r '
            $c = require "'"$DB_SECRET"'";
            try { new PDO("mysql:host=".$c["host"].";port=".$c["port"].";dbname=".$c["name"], $c["user"], $c["pass"]); echo "OK"; }
            catch (Exception $e) { echo "FAIL"; }
        ' 2>/dev/null)
        [ "$result" = "OK" ] && _change "VERIFY: DB OK (PHP)" || { VERIFY_PASSED=false; echo "  [ERROR] DB FAILED (PHP)"; }
    fi

    [ -f "$HMAC_KEY" ] && _change "VERIFY: hmac.key present" || echo "  [SKIP] hmac.key not on this node"
    [ -f "$VAULT_KEY" ] && _change "VERIFY: vault.key present" || echo "  [SKIP] vault.key not on this node"
}

# =============================================================================
# STEP 6: ACK — Tell vault we're done
# =============================================================================
_credential_ack() {
    local request_id="$1" target="$2"
    echo "  [6/7] ACK..."

    if [[ "$request_id" != SELF-* ]] && [ -f "$VAULT_SSH_KEY" ]; then
        ssh -i "$VAULT_SSH_KEY" -p "$VAULT_PORT" -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o BatchMode=yes \
            "${VAULT_USER}@${VAULT_NODE}" \
            "echo '{\"node\":\"${NODE_ID}\",\"status\":\"complete\",\"verified\":${VERIFY_PASSED},\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}' > '${VAULT_STAGING}/ack/${request_id}.ack'" 2>/dev/null
        _change "ACK sent to vault"
    else
        _change "ACK: self-vault (local)"
    fi

    _log_rotation "ROTATE target=$target node=${NODE_ID} request=${request_id} verified=${VERIFY_PASSED}"
}

# =============================================================================
# STEP 7: REVOKE_OLD — Clean up only AFTER verify passes
# =============================================================================
_credential_revoke_old() {
    echo "  [7/7] Revoking old credentials..."

    if [ "$VERIFY_PASSED" = "true" ]; then
        rm -rf "$LOCAL_STAGING" 2>/dev/null
        _change "REVOKE: staging cleaned (verify passed)"
    else
        echo "  [WARN] Verify FAILED — keeping staging for rollback"
        echo "  [WARN] Staging dir: ${LOCAL_STAGING}"
        _change "REVOKE: SKIPPED (verify failed — rollback available)"
    fi
}

# =============================================================================
# PIPELINE ORCHESTRATOR
# Safe flow: REQUEST → CREATE → INSTALL_ATOMIC → PERMS → VERIFY → ACK → REVOKE
# =============================================================================
_pipeline() {
    local target="${1:-all}"
    CHANGES_MADE=""
    VERIFY_PASSED=true

    echo "=== Credential Pipeline: ${NODE_ID} ==="
    echo "  Target: ${target}"
    echo "  Flow: REQUEST → CREATE → INSTALL → PERMS → VERIFY → ACK → REVOKE"
    echo ""

    # 1. REQUEST
    local request_id
    request_id=$(_vault_request "$target")

    # 2. CREATE_SECRET (vault generates or self-generate)
    _vault_create "$request_id" "$target"

    # 3. INSTALL_ATOMIC (new beside old — MariaDB has both passwords now)
    _credential_install "$target"

    # 4. PERMISSIONS
    _credential_permissions

    # 5. VERIFY (test new credentials live)
    _credential_verify "$request_id"

    # 6. ACK (tell vault)
    _credential_ack "$request_id" "$target"

    # 7. REVOKE_OLD (only if verify passed)
    _credential_revoke_old

    echo ""
    echo "=== Pipeline Complete ==="
    echo "Changes:"
    echo -e "$CHANGES_MADE"
}

# =============================================================================
# STANDARD ACTIONS
# =============================================================================
_do_send_impl() {
    local action="${1:---check}"
    case "$action" in
        --check)
            CHANGES_MADE=""
            echo "=== Credential Audit: ${NODE_ID} ==="
            echo ""
            for f in "$DB_SECRET" "$TOOLING_ENV" "$HMAC_KEY" "$VAULT_KEY"; do
                if [ -f "$f" ]; then
                    echo "  $(basename $f): exists ($(stat -c '%a %U:%G' "$f" 2>/dev/null))"
                else
                    echo "  $(basename $f): NOT FOUND"
                fi
            done
            # DB verify
            if [ -f "$DB_SECRET" ] && command -v php &>/dev/null; then
                local r
                r=$(php -r '
                    $c = require "'"$DB_SECRET"'";
                    try { new PDO("mysql:host=".$c["host"].";dbname=".$c["name"], $c["user"], $c["pass"]); echo "OK"; }
                    catch (Exception $e) { echo "FAIL"; }
                ' 2>/dev/null)
                echo ""; echo "  DB connection: ${r}"
            fi
            echo ""
            if [ -f "$ROTATION_LOG" ]; then
                echo "  Last 5 rotations:"
                tail -5 "$ROTATION_LOG" | sed 's/^/    /'
            fi
            ;;
        --rotate-db)   _pipeline "db" ;;
        --rotate-hmac) _pipeline "hmac" ;;
        --rotate-vault) _pipeline "vault" ;;
        --rotate-all)  _pipeline "all" ;;
        --init)
            echo "=== Credential Init: ${NODE_ID} ==="
            mkdir -p /etc/binary /opt/binary/logs/security 2>/dev/null
            [ ! -f "$HMAC_KEY" ] && { openssl rand -hex 32 > "$HMAC_KEY" 2>/dev/null; chmod 600 "$HMAC_KEY"; echo "  [INIT] hmac.key"; }
            [ ! -f "$VAULT_KEY" ] && { openssl rand -hex 32 > "$VAULT_KEY" 2>/dev/null; chmod 600 "$VAULT_KEY"; echo "  [INIT] vault.key"; }
            ;;
    esac
    tool_audit "send" "action=${action} node=${NODE_ID}"
}

_do_status_impl() {
    local db_ok="false" hmac_ok="false" vault_ok="false" env_ok="false" db_conn="false"
    [ -f "$DB_SECRET" ] && db_ok="true"
    [ -f "$HMAC_KEY" ] && hmac_ok="true"
    [ -f "$VAULT_KEY" ] && vault_ok="true"
    [ -f "$TOOLING_ENV" ] && env_ok="true"
    if [ "$db_ok" = "true" ] && command -v php &>/dev/null; then
        local r
        r=$(php -r '$c=require "'"$DB_SECRET"'"; try{new PDO("mysql:host=".$c["host"].";dbname=".$c["name"],$c["user"],$c["pass"]);echo "OK";}catch(Exception $e){echo "FAIL";}' 2>/dev/null)
        [ "$r" = "OK" ] && db_conn="true"
    fi
    local status="healthy"
    [ "$db_ok" = "false" ] && [ -d "$(dirname "$DB_SECRET")" ] && status="degraded"
    [ "$db_conn" = "false" ] && [ "$db_ok" = "true" ] && status="misaligned"
    cat <<EOF
{"tool":"${TOOL_NAME}","node":"${NODE_ID}","status":"${status}","db_secret":${db_ok},"db_connected":${db_conn},"hmac_key":${hmac_ok},"vault_key":${vault_ok},"tooling_env":${env_ok},"checked_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
EOF
}

_do_digest_impl() { _do_send_impl --check; }
_do_flush_impl() {
    [ -f "$ROTATION_LOG" ] && { cp "$ROTATION_LOG" "${ROTATION_LOG}.$(date +%Y%m%d)" 2>/dev/null; truncate -s 0 "$ROTATION_LOG"; echo "  [CHANGED] Rotation log archived"; }
    [ -f "$TOOL_LOG" ] && truncate -s 0 "$TOOL_LOG"
    rm -rf "$LOCAL_STAGING" 2>/dev/null
}

tool_help() {
    echo "${TOOL_NAME} v${TOOL_VERSION} -- Vault-Driven Credential Pipeline"
    echo ""
    echo "Pipeline: REQUEST → VAULT CREATE → READY → DELETE → MOVE → PERMISSIONS → VERIFY → ACK"
    echo ""
    echo "Actions:"
    echo "  --send --check          Audit credentials + verify DB"
    echo "  --send --rotate-db      Rotate DB password (vault pipeline)"
    echo "  --send --rotate-hmac    Rotate HMAC key (vault pipeline)"
    echo "  --send --rotate-vault   Rotate Vault key (vault pipeline)"
    echo "  --send --rotate-all     Full rotation (vault pipeline)"
    echo "  --send --init           Initialize missing key files"
    echo "  --status                JSON pill"
    echo "  --digest                Audit report"
    echo "  --flush                 Archive rotation log + clean staging"
    echo ""
    echo "Vault: ${VAULT_NODE}:${VAULT_PORT}"
    echo "Self-vault: auto-fallback if vault unreachable"
}

do_send()   { tool_wrap "send"   _do_send_impl "$@"; }
do_status() { tool_wrap "status" _do_status_impl "$@"; }
do_digest() { tool_wrap "digest" _do_digest_impl "$@"; }
do_flush()  { tool_wrap "flush"  _do_flush_impl "$@"; }

tool_init; trap 'tool_complete' EXIT
case "${1:---help}" in
    --send) shift; do_send "$@" ;; --status) shift; do_status "$@" ;;
    --digest) shift; do_digest "$@" ;; --flush) shift; do_flush "$@" ;;
    --help) tool_help ;; *) handle_standard_command "$@" ;;
esac
