#!/bin/bash
set -u

#==============================================================================
# Copyright and license info is available in the LICENSE file included with
# the Server Deployment Package (SDP), and also available online:
# https://workshop.perforce.com/projects/p4sudo/view/main/LICENSE
#------------------------------------------------------------------------------
# p4sudo-help.sh — P4Sudo help interception script
#
# Called by p4broker as a filter script for the 'help' command.  Intercepts
# 'p4 help sudo' and 'p4 help sudo <subcommand>'; passes all other help
# topics through to p4d unchanged.
#
# May also be invoked directly with -h, -man, or -V.
#
# Deployment:   /p4/common/site/p4sudo/p4sudo-help.sh
# Config:       ${P4SUDO_CFG:-/p4/common/site/config/p4sudo.cfg}
# Broker rule:
#   command: ^(help)$
#   {
#       action  = filter;
#       execute = "/p4/common/site/p4sudo/p4sudo-help.sh";
#   }
#
# See doc/broker-rewrite-reference/README.md for the full broker protocol.

#==============================================================================
# Declarations and Environment

declare ThisScript=${0##*/}
declare Version=1.0.0
declare ThisUser=
declare ThisHost=${HOSTNAME%%.*}
declare -i Debug=${SDP_DEBUG:-0}
declare -i ErrorCount=0

declare SDPRoot=${SDP_ROOT:-/p4}
declare SDPCommon="$SDPRoot/common"
declare SDPCommonLib="$SDPCommon/lib"

# P4Sudo config file. Override via the P4SUDO_CFG environment variable.
declare P4SudoCfg="${P4SUDO_CFG:-/p4/common/site/config/p4sudo.cfg}"

# Broker context — populated from stdin in Main Program.
declare -a BrokerArgs=()
declare -i ArgCount=0

# Command registry — populated by load_commands().
declare -A CmdDesc
declare -A CmdUsage

#==============================================================================
# Local Functions

function msg    () { echo -e "$*"; }
function dbg    () { [[ "$Debug" -eq 0 ]] || msg "DEBUG: $*" >&2; }
function errmsg () { msg "\nError: ${1:-Unknown Error}\n"; ErrorCount+=1; }
function bail   () { errmsg "${1:-Unknown Error}"; exit "${2:-1}"; }

#------------------------------------------------------------------------------
# Function: load_commands
#
# Reads the [commands] section of P4SudoCfg and populates CmdDesc[] and
# CmdUsage[] for help text generation.
#------------------------------------------------------------------------------
function load_commands ()
{
   local section=
   local key=
   local val=
   local cmdName=
   local attr=

   [[ -f "$P4SudoCfg" ]] || return 0

   while IFS= read -r line || [[ -n "$line" ]]; do
      line="${line%%#*}"
      line="${line#"${line%%[![:space:]]*}"}"
      line="${line%"${line##*[![:space:]]}"}"
      [[ -z "$line" ]] && continue

      if [[ "$line" =~ ^\[([a-z]+)\]$ ]]; then
         section="${BASH_REMATCH[1]}"
         continue
      fi

      [[ "$section" == "commands" ]] || continue

      key="${line%%=*}"; key="${key%"${key##*[![:space:]]}"}"
      val="${line#*=}";  val="${val#"${val%%[![:space:]]*}"}"
      cmdName="${key%%.*}"
      attr="${key#*.}"
      case "$attr" in
         description) CmdDesc["$cmdName"]="$val";;
         usage)       CmdUsage["$cmdName"]="$val";;
      esac
   done < "$P4SudoCfg"
}

#==============================================================================
# Load SDP Library Functions.

if [[ -d "$SDPCommonLib" ]]; then
   # shellcheck disable=SC1090 disable=SC1091
   source "$SDPCommonLib/logging.lib" ||\
      bail "Failed to load bash lib [$SDPCommonLib/logging.lib]. Aborting."
   # shellcheck disable=SC1090 disable=SC1091
   source "$SDPCommonLib/run.lib" ||\
      bail "Failed to load bash lib [$SDPCommonLib/run.lib]. Aborting."
fi

# run.lib declares its own Version; restore this script's version.
declare Version=1.0.0

# Override terminate() from logging.lib: stdout is the broker protocol channel.
# shellcheck disable=SC2317
function terminate ()
{
   trap - EXIT SIGINT SIGTERM
   exit "$ErrorCount"
}

#------------------------------------------------------------------------------
# Function: usage (required function)
#
# Input:
# $1 - style: -h (short form) or -man (man-page form). Default: -h.
# $2 - usageErrorMessage: Optional; displayed before the usage text.
#------------------------------------------------------------------------------
function usage ()
{
   local style=${1:--h}
   local usageErrorMessage=${2:-Unset}

   if [[ "$usageErrorMessage" != Unset ]]; then
      msg "\n\nUsage Error:\n\n$usageErrorMessage\n\n"
   fi

   msg "USAGE for $ThisScript v$Version:

$ThisScript [-d|-D]

or

$ThisScript [-h|-man|-V]
"
   if [[ "$style" == -man ]]; then
      msg "
DESCRIPTION:
   $ThisScript is invoked by p4broker as a filter script for the 'help'
   command.  It intercepts 'p4 help sudo' and 'p4 help sudo <subcommand>',
   returning P4Sudo-specific help text.  All other 'p4 help' topics receive
   an 'action: PASS' response so that p4d handles them normally.

   It may also be run directly with -h, -man, or -V.

OPTIONS:
 -d   Enable debug output (to stderr).
 -D   Enable extreme debug mode (bash set -x).

HELP OPTIONS:
 -h    Display short help message.
 -man  Display man-style help message.
 -V    Display version info for this script.

ENVIRONMENT:
 P4SUDO_CFG
   Override the default p4sudo.cfg path
   (default: /p4/common/site/config/p4sudo.cfg).

 SDP_ROOT  Override the SDP root directory (default: /p4).

FILES:
 \${P4SUDO_CFG:-/p4/common/site/config/p4sudo.cfg}
   Command registry (description and usage text for each command).

SEE ALSO:
 p4sudo.sh(1), doc/broker-rewrite-reference/README.md
"
   fi

   exit 2
}

#==============================================================================
# Command Line Processing

declare -i ShiftArgs=0

set +u
while [[ $# -gt 0 ]]; do
   case $1 in
      (-h)           usage -h;;
      (-man|--help)  usage -man;;
      (-V|--version) msg "$ThisScript version $Version"; exit 0;;
      (-d)           Debug=1;;
      (-D)           Debug=1; set -x;;
      (*)            usage -h "Unknown arg ($1).";;
   esac

   shift; while [[ $ShiftArgs -gt 0 ]]; do
      [[ $# -eq 0 ]] && usage -h "Incorrect number of arguments."
      ShiftArgs=$ShiftArgs-1
      shift
   done
done
set -u

#==============================================================================
# Command Line Verification
# (No additional verification required for the options above.)

#==============================================================================
# Main Program

trap terminate EXIT SIGINT SIGTERM

ThisUser=$(id -n -u)
dbg "Starting $ThisScript v$Version as $ThisUser@$ThisHost"

#------------------------------------------------------------------------------
# Parse broker stdin.

while IFS= read -r line; do
   [[ -z "$line" ]] && break
   key="${line%%: *}"
   value="${line#*: }"
   case "$key" in
      argCount) ArgCount="$value";;
      Arg*)     BrokerArgs+=("$value");;
   esac
done

dbg "stdin: argCount=$ArgCount args=${BrokerArgs[*]+"${BrokerArgs[*]}"}"

#------------------------------------------------------------------------------
# If this is not a 'p4 help sudo' request, pass through to p4d.

if (( ArgCount == 0 )) || [[ "${BrokerArgs[0]:-}" != "sudo" ]]; then
   echo "action: PASS"
   exit 0
fi

#------------------------------------------------------------------------------
# Load command registry from p4sudo.cfg.

load_commands

#------------------------------------------------------------------------------
# Emit help text.

declare Subcmd="${BrokerArgs[1]:-}"

if [[ -z "$Subcmd" ]]; then
   # 'p4 help sudo' — list all registered commands.
   msg "p4 sudo -- Run a privileged or site-defined Perforce command."
   msg ""
   msg "    p4 sudo <subcommand> [args...]"
   msg ""
   msg "    P4Sudo lets authorized users run privileged or site-defined"
   msg "    Perforce operations without holding super-user access."
   msg "    Authorization is controlled by the site p4sudo.cfg policy file."
   msg ""
   msg "    For help on a specific subcommand:  p4 help sudo <subcommand>"
   msg ""

   if (( ${#CmdDesc[@]} > 0 )); then
      msg "    Available commands:"
      while IFS= read -r name; do
         printf '        %-20s %s\n' "$name" "${CmdDesc[$name]:-}"
      done < <(printf '%s\n' "${!CmdDesc[@]}" | sort)
      msg ""
   else
      msg "    (No commands registered in p4sudo.cfg.)"
      msg ""
   fi

else
   # 'p4 help sudo <subcmd>' — show detail for that command.
   declare desc="${CmdDesc[$Subcmd]:-}"
   declare usageText="${CmdUsage[$Subcmd]:-}"

   if [[ -z "$desc" && -z "$usageText" ]]; then
      msg "p4sudo: no help available for subcommand '$Subcmd'."
      msg "        Run 'p4 help sudo' to list available commands."
   else
      msg "p4 sudo $Subcmd -- ${desc:-}"
      msg ""
      if [[ -n "$usageText" ]]; then
         msg "    $usageText"
         msg ""
      fi
   fi
fi

exit "$ErrorCount"
