#!/usr/bin/env bash
# ring-request - Submit ring1/ring2 join requests and watch for replies
# Uses hppr CLI for all server operations

set -euo pipefail

SCRIPT_DIR="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=./_hppr-tool-resolve
source "$SCRIPT_DIR/_hppr-tool-resolve"
HPPR_BIN="$(hppr_resolve_tool HPPR_BIN hppr "$SCRIPT_DIR")" || exit 1

usage() {
    cat >&2 <<'EOF'
Usage: ring-request <type> [options]

Submit ring1 or ring2 join requests and watch for replies.

Commands:
  ring-request ring1 <name> -t <token> [options]
  ring-request ring2 <group> -k <key> [options]
  ring-request watch ring1 <name>
  ring-request watch ring2 <group> -k <key>

Ring1 Options:
  -t, --token <secret>       Desired secret token (required)
  -R, --rule <ops> <coord>   Requested ACL rule (repeatable)
  -e, --expire <tai>         Requested expiry timestamp
  -m, --message <text>       Introduction message
  -f, --message-file <file>  Read message from file
  --no-watch                 Submit only, don't wait for reply

Ring2 Options:
  -k, --key <privkey>        Signing key (required)
  -T, --tags <tags>          Requested tags (space-separated)
  -m, --message <text>       Introduction message
  -f, --message-file <file>  Read message from file
  --no-watch                 Submit only, don't wait for reply

Environment:
  HPPR_HOME                  Server address (default: tcp+127.0.0.1:4777)

Examples:
  ring-request ring1 alice -t "my-secret" -m "Please add me"
  ring-request ring1 bob -t "$TOKEN" -R "rwl //u/bob/" -R "r.l //public/"
  ring-request ring2 mygroup -k "$KEY" -T "developer"
  ring-request watch ring1 alice
EOF
}

# Validate group/app name
validate_name() {
    local name="$1"
    local len
    len=$(printf '%s' "$name" | wc -c)
    [[ -n "$name" && ! "$name" =~ [/\{\}\|#] && "$name" != "." && "$name" != ".." && $len -le 56 ]]
}

# Validate TAI format
validate_tai() {
    [[ "$1" =~ ^[0-9]{10}:[0-9]{9}$ ]]
}

# Validate signing key format
validate_signing_key() {
    [[ "$1" =~ ^[A-Za-z0-9_~-]{43}$ ]] || [[ "$1" =~ ^\&\.[A-Za-z0-9_~-]{43}\.H3$ ]]
}

ring2_signer_from_key() {
    local group="$1" key="$2" key_for_signer
    if [[ "$key" =~ ^\&\.[A-Za-z0-9_~-]{43}\.H3$ ]]; then
        key_for_signer="$key"
    else
        key_for_signer="&.${key}.H3"
    fi
    echo "ring2:${group}|${key_for_signer}"
}

# Get public key from secret key
get_pubkey() {
    "$HPPR_BIN" key pubkey "$1"
}

# ============================================================================
# Ring1 Request
# ============================================================================

ring1_request() {
    local name="" token="" message="" message_file=""
    local -a rules=()
    local expire="" do_watch=1

    while [[ $# -gt 0 ]]; do
        case "$1" in
            -t|--token) token="$2"; shift 2 ;;
            -R|--rule)
                [[ $# -ge 2 ]] || { echo "ERROR: -R requires '<ops> <coord>' or '<ops>' '<coord>'" >&2; exit 1; }
                if [[ $# -ge 3 && "$3" == //* ]]; then
                    rules+=("$2 $3"); shift 3
                else
                    rules+=("$2"); shift 2
                fi ;;
            -e|--expire) expire="$2"; shift 2 ;;
            -m|--message) message="$2"; shift 2 ;;
            -f|--message-file) message_file="$2"; shift 2 ;;
            --no-watch) do_watch=0; shift ;;
            -h|--help) usage; exit 0 ;;
            -*) echo "Unknown option: $1" >&2; exit 1 ;;
            *)
                if [[ -z "$name" ]]; then name="$1"
                else echo "Unexpected argument: $1" >&2; exit 1
                fi
                shift ;;
        esac
    done

    # Validate required args
    [[ -n "$name" ]] || { echo "ERROR: ring1 name required" >&2; usage; exit 1; }
    [[ -n "$token" ]] || { echo "ERROR: -t/--token required" >&2; usage; exit 1; }
    validate_name "$name" || { echo "ERROR: Invalid ring1 name '$name'" >&2; exit 1; }

    # Derive Ring1-Secret-Token using server PHC parameters
    local derived_token
    derived_token=$("$HPPR_BIN" ring1 derive "$name" "$token")

    # Build headers
    local -a headers=()
    headers+=(-H "Ring1-Name: $name")
    headers+=(-H "Ring1-Secret-Token: $derived_token")
    for rule in "${rules[@]}"; do
        headers+=(-H "Ring1-Rule: $rule")
    done
    if [[ -n "$expire" ]]; then
        validate_tai "$expire" || { echo "ERROR: Invalid TAI format '$expire'" >&2; exit 1; }
        headers+=(-H "Ring1-Expire: $expire")
    fi

    # Reply coordinate for watching
    local reply_coord="//repo/admin/request/join/$name/reply/"
    local watch_pid=""

    # Start watch BEFORE submitting request to avoid race condition
    if [[ $do_watch -eq 1 ]]; then
        # Watch with provisional token (070)
        HPPR_SIGNER="ring1:$name|?" "$HPPR_BIN" watch "$reply_coord" > >(
            while IFS= read -r event; do
                if [[ "$event" == FATAL* ]]; then
                    echo "$event" >&2
                    exit 1
                fi
                if [[ "$event" == "+ "* && "$event" == *"/reply/"* ]]; then
                    # Got a reply - fetch and parse it
                    local reply_headers reply_data status
                    reply_headers=$(HPPR_SIGNER="ring1:$name|?" "$HPPR_BIN" headers "${reply_coord}|" 2>/dev/null || true)
                    status=$(echo "$reply_headers" | sed -n 's/^Request-Status: //p')
                    reply_data=$(HPPR_SIGNER="ring1:$name|?" "$HPPR_BIN" data "${reply_coord}|" 2>/dev/null || true)

                    if [[ -n "$status" ]]; then
                        echo "[$status] $reply_data"
                        [[ "$status" == "approved" ]] && exit 0
                        exit 1
                    fi
                fi
            done
        ) 2>&1 &
        watch_pid=$!
        # Give watch time to connect
        sleep 0.2
    fi

    # Submit request as anyone (090 workflow)
    # Use mkpac (local) + store since pac requires server auth
    local hash
    if [[ -n "$message" ]]; then
        hash=$(echo "$message" | "$HPPR_BIN" mkpac plex "//repo/admin/request/join/$name" "${headers[@]}" | "$HPPR_BIN" store)
    elif [[ -n "$message_file" && "$message_file" != "/dev/null" ]]; then
        hash=$("$HPPR_BIN" mkpac plex "//repo/admin/request/join/$name" "${headers[@]}" < "$message_file" | "$HPPR_BIN" store)
    else
        hash=$("$HPPR_BIN" mkpac plex "//repo/admin/request/join/$name" "${headers[@]}" < /dev/null | "$HPPR_BIN" store)
    fi

    local plex_hash
    plex_hash=$(echo "$hash" | grep "^P\." | head -1)
    echo "Request submitted: $plex_hash"

    # Wait for watch process if watching
    if [[ $do_watch -eq 1 && -n "$watch_pid" ]]; then
        echo "Watching for reply..."
        wait "$watch_pid" || exit $?
    fi
}

# ============================================================================
# Ring2 Request
# ============================================================================

ring2_request() {
    local group="" key="" tags="" message="" message_file=""
    local do_watch=1

    while [[ $# -gt 0 ]]; do
        case "$1" in
            -k|--key) key="$2"; shift 2 ;;
            -T|--tags) tags="$2"; shift 2 ;;
            -m|--message) message="$2"; shift 2 ;;
            -f|--message-file) message_file="$2"; shift 2 ;;
            --no-watch) do_watch=0; shift ;;
            -h|--help) usage; exit 0 ;;
            -*) echo "Unknown option: $1" >&2; exit 1 ;;
            *)
                if [[ -z "$group" ]]; then group="$1"
                else echo "Unexpected argument: $1" >&2; exit 1
                fi
                shift ;;
        esac
    done

    # Validate required args
    [[ -n "$group" ]] || { echo "ERROR: group name required" >&2; usage; exit 1; }
    [[ -n "$key" ]] || { echo "ERROR: -k/--key required" >&2; usage; exit 1; }
    validate_name "$group" || { echo "ERROR: Invalid group name '$group'" >&2; exit 1; }
    validate_signing_key "$key" || { echo "ERROR: Invalid signing key" >&2; exit 1; }

    # Derive verification key
    local vkey
    vkey=$(get_pubkey "$key")
    local ring2_signer
    ring2_signer=$(ring2_signer_from_key "$group" "$key")

    # Build headers
    local -a headers=()
    if [[ -n "$tags" ]]; then
        headers+=(-H "Request-Tags: $tags")
    fi

    # Reply coordinate for watching
    local reply_coord="//$group/admin/request/join/$vkey/reply/"
    local watch_pid=""

    # Start watch BEFORE submitting request to avoid race condition
    if [[ $do_watch -eq 1 ]]; then
        # Watch as ring2 guest using signing key
        HPPR_SIGNER="$ring2_signer" "$HPPR_BIN" watch "$reply_coord" > >(
            while IFS= read -r event; do
                if [[ "$event" == FATAL* ]]; then
                    echo "$event" >&2
                    exit 1
                fi
                if [[ "$event" == "+ "* && "$event" == *"/reply/"* ]]; then
                    local reply_headers reply_data status
                    reply_headers=$(HPPR_SIGNER="$ring2_signer" "$HPPR_BIN" headers "${reply_coord}|" 2>/dev/null || true)
                    status=$(echo "$reply_headers" | sed -n 's/^Request-Status: //p')
                    reply_data=$(HPPR_SIGNER="$ring2_signer" "$HPPR_BIN" data "${reply_coord}|" 2>/dev/null || true)

                    if [[ -n "$status" ]]; then
                        echo "[$status] $reply_data"
                        [[ "$status" == "approved" ]] && exit 0
                        exit 1
                    fi
                fi
            done
        ) 2>&1 &
        watch_pid=$!
        # Give watch time to connect
        sleep 0.2
    fi

    # Submit request as sealed packet (080 ring2 guest workflow)
    local hash
    if [[ -n "$message" ]]; then
        hash=$(echo "$message" | "$HPPR_BIN" mkpac seal -k "$key" "//$group/admin/request/join" "${headers[@]}" | \
            HPPR_SIGNER="$ring2_signer" "$HPPR_BIN" store)
    elif [[ -n "$message_file" && "$message_file" != "/dev/null" ]]; then
        hash=$("$HPPR_BIN" mkpac seal -k "$key" "//$group/admin/request/join" "${headers[@]}" < "$message_file" | \
            HPPR_SIGNER="$ring2_signer" "$HPPR_BIN" store)
    else
        hash=$("$HPPR_BIN" mkpac seal -k "$key" "//$group/admin/request/join" "${headers[@]}" < /dev/null | \
            HPPR_SIGNER="$ring2_signer" "$HPPR_BIN" store)
    fi

    local seal_hash
    seal_hash=$(echo "$hash" | grep "^S\." | head -1)
    echo "Request submitted: $seal_hash"
    echo "Your verification-key: $vkey"

    # Wait for watch process if watching
    if [[ $do_watch -eq 1 && -n "$watch_pid" ]]; then
        echo "Watching for reply..."
        wait "$watch_pid" || exit $?
    fi
}

# ============================================================================
# Watch Command (resume watching)
# ============================================================================

watch_ring1() {
    local name="$1"
    [[ -n "$name" ]] || { echo "ERROR: ring1 name required" >&2; exit 1; }

    local reply_coord="//repo/admin/request/join/$name/reply/"

    echo "Watching for reply..."
    HPPR_SIGNER="ring1:$name|?" "$HPPR_BIN" watch "$reply_coord" | while IFS= read -r event; do
        if [[ "$event" == FATAL* ]]; then
            echo "$event" >&2
            exit 1
        fi
        if [[ "$event" == "+ "* || "$event" == *"/reply/"* ]]; then
            local reply_headers reply_data status
            reply_headers=$(HPPR_SIGNER="ring1:$name|?" "$HPPR_BIN" headers "${reply_coord}|" 2>/dev/null || true)
            status=$(echo "$reply_headers" | sed -n 's/^Request-Status: //p')
            reply_data=$(HPPR_SIGNER="ring1:$name|?" "$HPPR_BIN" data "${reply_coord}|" 2>/dev/null || true)

            if [[ -n "$status" ]]; then
                echo "[$status] $reply_data"
                [[ "$status" == "approved" ]] && exit 0
                exit 1
            fi
        fi
    done
}

watch_ring2() {
    local group="" key=""

    while [[ $# -gt 0 ]]; do
        case "$1" in
            -k|--key) key="$2"; shift 2 ;;
            *) [[ -z "$group" ]] && group="$1"; shift ;;
        esac
    done

    [[ -n "$group" ]] || { echo "ERROR: group required" >&2; exit 1; }
    [[ -n "$key" ]] || { echo "ERROR: -k/--key required" >&2; exit 1; }

    local vkey
    vkey=$(get_pubkey "$key")
    local ring2_signer
    ring2_signer=$(ring2_signer_from_key "$group" "$key")
    local reply_coord="//$group/admin/request/join/$vkey/reply/"

    echo "Watching for reply..."
    HPPR_SIGNER="$ring2_signer" "$HPPR_BIN" watch "$reply_coord" | while IFS= read -r event; do
        if [[ "$event" == FATAL* ]]; then
            echo "$event" >&2
            exit 1
        fi
        if [[ "$event" == "+ "* || "$event" == *"/reply/"* ]]; then
            local reply_headers reply_data status
            reply_headers=$(HPPR_SIGNER="$ring2_signer" "$HPPR_BIN" headers "${reply_coord}|" 2>/dev/null || true)
            status=$(echo "$reply_headers" | sed -n 's/^Request-Status: //p')
            reply_data=$(HPPR_SIGNER="$ring2_signer" "$HPPR_BIN" data "${reply_coord}|" 2>/dev/null || true)

            if [[ -n "$status" ]]; then
                echo "[$status] $reply_data"
                [[ "$status" == "approved" ]] && exit 0
                exit 1
            fi
        fi
    done
}

# ============================================================================
# Main
# ============================================================================

[[ $# -gt 0 ]] || { usage; exit 1; }

case "$1" in
    ring1)
        shift
        ring1_request "$@"
        ;;
    ring2)
        shift
        ring2_request "$@"
        ;;
    watch)
        [[ $# -ge 2 ]] || { echo "ERROR: watch requires ring1 or ring2" >&2; exit 1; }
        case "$2" in
            ring1)
                shift 2
                watch_ring1 "$@"
                ;;
            ring2)
                shift 2
                watch_ring2 "$@"
                ;;
            *)
                echo "Unknown type: $2 (use ring1 or ring2)" >&2
                exit 1
                ;;
        esac
        ;;
    -h|--help)
        usage; exit 0
        ;;
    *)
        echo "Unknown command: $1" >&2
        usage; exit 1
        ;;
esac
