#!/usr/bin/env bash
set -Eeuo pipefail

ACTION="${1:-setup}"
LAB_ROOT="${2:-/tmp/soria-glpi-foundations-${USER:-student}}"
MARKER_NAME=".soria-glpi-foundations"

fail() {
  printf 'ERROR: %s\n' "$*" >&2
  exit 1
}

resolve_safe_root() {
  case "$LAB_ROOT" in
    /tmp/soria-glpi-*|"${HOME}"/soria-glpi-*) ;;
    *) fail "refused lab root: $LAB_ROOT" ;;
  esac
  [ "$LAB_ROOT" != "/tmp" ] || fail "refused root: /tmp"
  [ "$LAB_ROOT" != "$HOME" ] || fail "refused root: $HOME"
}

require_marker() {
  [ -f "$LAB_ROOT/$MARKER_NAME" ] || fail "missing marker: $LAB_ROOT/$MARKER_NAME"
}

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

assert_unique_column() {
  local file="$1"
  local column="$2"
  local label="$3"
  awk -F, -v col="$column" -v label="$label" '
    NR == 1 { next }
    $col == "" { print "empty " label " at line " NR > "/dev/stderr"; exit 1 }
    seen[$col]++ { print "duplicate " label ": " $col > "/dev/stderr"; exit 1 }
  ' "$file" || fail "$label values are not unique"
}

setup_lab() {
  if [ -e "$LAB_ROOT" ]; then
    require_marker
    rm -rf "$LAB_ROOT/input" "$LAB_ROOT/output" "$LAB_ROOT/evidence"
    rm -f "$LAB_ROOT/README.txt"
  else
    mkdir -p "$LAB_ROOT"
    : > "$LAB_ROOT/$MARKER_NAME"
  fi

  mkdir -p "$LAB_ROOT/input" "$LAB_ROOT/output" "$LAB_ROOT/evidence"

  cat > "$LAB_ROOT/input/architecture.env" <<'DATA'
GLPI_FQDN=glpi.lab.soria.invalid
TLS_REQUIRED=true
WEBROOT=/var/www/glpi/public
CONFIG_DIR=/etc/glpi
DATA_DIR=/var/lib/glpi
LOG_DIR=/var/log/glpi
DB_HOST=db-glpi
DB_NAME=glpi
DB_USER=glpi_app
CRON_MODE=system
BACKUP_ENCRYPTED=true
DATA

  cat > "$LAB_ROOT/input/entities.csv" <<'DATA'
entity_id,parent_id,name,location_code
ROOT,,SORIA Distribution,
HQ,ROOT,Siège Orléans,ORL-HQ
AGENCY,ROOT,Agence Tours,TOU-AG
DATA

  cat > "$LAB_ROOT/input/locations.csv" <<'DATA'
location_code,entity_id,name
ORL-HQ,HQ,Siège Orléans
ORL-SRV,HQ,Salle serveurs Orléans
TOU-AG,AGENCY,Agence Tours
DATA

  cat > "$LAB_ROOT/input/groups.csv" <<'DATA'
group_code,entity_id,name,role
SUPPORT_N1,HQ,Support niveau 1,ticketing
INFRA,HQ,Équipe infrastructure,asset-and-ticket
LOCAL_TOURS,AGENCY,Support local Tours,ticketing
DATA

  cat > "$LAB_ROOT/input/profiles.csv" <<'DATA'
profile_code,name,scope,can_admin,can_manage_assets,can_manage_tickets,can_view_all_tickets
SUPERADMIN,Super-Admin,ROOT,true,true,true,true
TECH_N1,Technicien N1,ENTITY,false,true,true,true
ASSET_MANAGER,Gestionnaire de parc,ENTITY,false,true,false,false
REQUESTER,Demandeur,SELF,false,false,false,false
DATA

  cat > "$LAB_ROOT/input/users.csv" <<'DATA'
login,entity_id,profile_code,group_code,purpose
admin-breakglass,ROOT,SUPERADMIN,,administration exceptionnelle
alice.tech,HQ,TECH_N1,SUPPORT_N1,support quotidien
bob.asset,HQ,ASSET_MANAGER,INFRA,gestion du parc
carole.user,AGENCY,REQUESTER,,demandeuse
DATA

  cat > "$LAB_ROOT/input/service-catalog.csv" <<'DATA'
category_code,parent_code,name,default_group,service_type
INCIDENT,,Incident,SUPPORT_N1,incident
INCIDENT_NETWORK,INCIDENT,Réseau et connectivité,INFRA,incident
REQUEST,,Demande,SUPPORT_N1,request
REQUEST_ACCESS,REQUEST,Accès et habilitation,SUPPORT_N1,request
DATA

  cat > "$LAB_ROOT/input/deployment-checklist.md" <<'DATA'
# Préparation du déploiement GLPI

- [ ] Nom DNS réservé
- [ ] Certificat TLS prévu
- [ ] Base de données dédiée
- [ ] Compte applicatif sans privilèges d'administration
- [ ] Répertoires config, données et logs hors du document root
- [ ] Tâches automatiques exécutées par le planificateur système
- [ ] Sauvegardes chiffrées et test de restauration planifié
- [ ] Compte Super-Admin réservé aux opérations exceptionnelles
DATA

  cat > "$LAB_ROOT/README.txt" <<'DATA'
Commands:
  setup     create an isolated GLPI foundation design dossier
  status    inspect source files without changing them
  run       generate the entity tree, access matrix and readiness report
  validate  verify references, least privilege and deployment safeguards
  reset     delete this marker-protected laboratory directory

Proof boundary:
  This lab validates a deployment and governance dossier. It does not install,
  configure or claim to execute a real GLPI, database, mail or directory service.
DATA

  printf 'GLPI foundations laboratory ready: %s\n' "$LAB_ROOT"
}

show_status() {
  require_marker
  printf 'Lab root: %s\n' "$LAB_ROOT"
  printf 'Input files:\n'
  find "$LAB_ROOT/input" -maxdepth 1 -type f -printf '  %f\n' | sort
  printf 'Entities: %s\n' "$(awk 'END { print NR - 1 }' "$LAB_ROOT/input/entities.csv")"
  printf 'Locations: %s\n' "$(awk 'END { print NR - 1 }' "$LAB_ROOT/input/locations.csv")"
  printf 'Profiles: %s\n' "$(awk 'END { print NR - 1 }' "$LAB_ROOT/input/profiles.csv")"
  printf 'Users: %s\n' "$(awk 'END { print NR - 1 }' "$LAB_ROOT/input/users.csv")"
  printf 'Service categories: %s\n' "$(awk 'END { print NR - 1 }' "$LAB_ROOT/input/service-catalog.csv")"
}

run_reference() {
  require_marker
  require_command awk
  require_command sha256sum

  local input="$LAB_ROOT/input"
  local output="$LAB_ROOT/output"
  local evidence="$LAB_ROOT/evidence"

  rm -f "$output"/* "$evidence"/*

  awk -F, '
    NR == 1 { next }
    $2 == "" { printf "%s | %s\n", $1, $3; next }
    { printf "  %s -> %s | %s\n", $2, $1, $3 }
  ' "$input/entities.csv" > "$output/entity-tree.txt"

  awk -F, 'BEGIN { OFS="," }
    NR == 1 { print "profile_code","scope","admin","assets","tickets","all_tickets"; next }
    { print $1,$3,$4,$5,$6,$7 }
  ' "$input/profiles.csv" > "$output/access-matrix.csv"

  entities_count="$(awk 'END { print NR - 1 }' "$input/entities.csv")"
  locations_count="$(awk 'END { print NR - 1 }' "$input/locations.csv")"
  groups_count="$(awk 'END { print NR - 1 }' "$input/groups.csv")"
  profiles_count="$(awk 'END { print NR - 1 }' "$input/profiles.csv")"
  users_count="$(awk 'END { print NR - 1 }' "$input/users.csv")"
  categories_count="$(awk 'END { print NR - 1 }' "$input/service-catalog.csv")"

  {
    printf 'GLPI foundation readiness report\n'
    printf 'entities=%s\n' "$entities_count"
    printf 'locations=%s\n' "$locations_count"
    printf 'groups=%s\n' "$groups_count"
    printf 'profiles=%s\n' "$profiles_count"
    printf 'users=%s\n' "$users_count"
    printf 'service_categories=%s\n' "$categories_count"
    printf 'tls_required=true\n'
    printf 'application_data_outside_webroot=true\n'
    printf 'system_scheduler=true\n'
    printf 'encrypted_backup=true\n'
    printf 'proof_scope=design-dossier-only\n'
  } > "$output/readiness-report.txt"

  sha256sum "$input"/* "$output"/* > "$evidence/checksums.sha256"
  printf 'Reference GLPI foundation dossier generated.\n'
}

validate_reference() {
  require_marker
  require_command awk
  require_command grep
  require_command sha256sum

  local input="$LAB_ROOT/input"
  local output="$LAB_ROOT/output"
  local evidence="$LAB_ROOT/evidence"
  local key value webroot config_dir data_dir log_dir

  for file in architecture.env entities.csv locations.csv groups.csv profiles.csv users.csv service-catalog.csv deployment-checklist.md; do
    [ -s "$input/$file" ] || fail "missing or empty input: $file"
  done
  for file in entity-tree.txt access-matrix.csv readiness-report.txt; do
    [ -s "$output/$file" ] || fail "missing or empty output: $file"
  done
  [ -s "$evidence/checksums.sha256" ] || fail "missing checksum evidence"

  assert_unique_column "$input/entities.csv" 1 entity_id
  assert_unique_column "$input/locations.csv" 1 location_code
  assert_unique_column "$input/groups.csv" 1 group_code
  assert_unique_column "$input/profiles.csv" 1 profile_code
  assert_unique_column "$input/users.csv" 1 login
  assert_unique_column "$input/service-catalog.csv" 1 category_code

  awk -F, '
    NR == FNR && NR > 1 { entity[$1] = 1; next }
    FNR == 1 { next }
    $2 == "" && $1 != "ROOT" { print "non-root entity without parent: " $1 > "/dev/stderr"; exit 1 }
    $2 != "" && !($2 in entity) { print "unknown parent entity: " $2 > "/dev/stderr"; exit 1 }
  ' "$input/entities.csv" "$input/entities.csv" || fail "entity hierarchy is invalid"

  awk -F, '
    NR == FNR && NR > 1 { entity[$1] = 1; next }
    FNR == 1 { next }
    !($2 in entity) { print "location references unknown entity: " $2 > "/dev/stderr"; exit 1 }
  ' "$input/entities.csv" "$input/locations.csv" || fail "location references are invalid"

  awk -F, '
    ARGIND == 1 && FNR > 1 { entity[$1] = 1; next }
    ARGIND == 2 && FNR > 1 { profile[$1] = 1; next }
    ARGIND == 3 && FNR > 1 { group[$1] = 1; next }
    ARGIND == 4 && FNR > 1 {
      if (!($2 in entity)) { print "user references unknown entity: " $2 > "/dev/stderr"; exit 1 }
      if (!($3 in profile)) { print "user references unknown profile: " $3 > "/dev/stderr"; exit 1 }
      if ($4 != "" && !($4 in group)) { print "user references unknown group: " $4 > "/dev/stderr"; exit 1 }
    }
  ' "$input/entities.csv" "$input/profiles.csv" "$input/groups.csv" "$input/users.csv" || fail "user references are invalid"

  awk -F, '
    NR == 1 { next }
    $1 == "REQUESTER" && ($4 != "false" || $5 != "false" || $6 != "false" || $7 != "false") {
      print "requester profile violates least privilege" > "/dev/stderr"; exit 1
    }
    $1 == "SUPERADMIN" && $3 != "ROOT" {
      print "super-admin scope must be ROOT" > "/dev/stderr"; exit 1
    }
  ' "$input/profiles.csv" || fail "profile policy is invalid"

  [ "$(awk -F, 'NR > 1 && $3 == "SUPERADMIN" { count++ } END { print count + 0 }' "$input/users.csv")" -eq 1 ] \
    || fail "exactly one break-glass Super-Admin account is required"
  grep -Fq 'admin-breakglass,ROOT,SUPERADMIN,,administration exceptionnelle' "$input/users.csv" \
    || fail "break-glass administrator is not documented"

  while IFS='=' read -r key value; do
    case "$key" in
      WEBROOT) webroot="$value" ;;
      CONFIG_DIR) config_dir="$value" ;;
      DATA_DIR) data_dir="$value" ;;
      LOG_DIR) log_dir="$value" ;;
    esac
  done < "$input/architecture.env"

  grep -Fxq 'TLS_REQUIRED=true' "$input/architecture.env" || fail "TLS is not mandatory"
  grep -Fxq 'CRON_MODE=system' "$input/architecture.env" || fail "system scheduler is not selected"
  grep -Fxq 'BACKUP_ENCRYPTED=true' "$input/architecture.env" || fail "encrypted backup is not required"
  grep -Eq '^GLPI_FQDN=[a-z0-9.-]+$' "$input/architecture.env" || fail "invalid GLPI FQDN"
  grep -Eq '^DB_USER=[a-zA-Z0-9_]+$' "$input/architecture.env" || fail "invalid database account"

  for directory in "$config_dir" "$data_dir" "$log_dir"; do
    [ -n "$directory" ] || fail "empty protected directory"
    case "$directory" in
      "$webroot"|"$webroot"/*) fail "protected data directory is inside the webroot: $directory" ;;
    esac
  done
  [ "$config_dir" != "$data_dir" ] && [ "$config_dir" != "$log_dir" ] && [ "$data_dir" != "$log_dir" ] \
    || fail "config, data and log directories must be distinct"

  grep -Fxq 'proof_scope=design-dossier-only' "$output/readiness-report.txt" \
    || fail "proof boundary is missing"
  grep -Fq 'ROOT | SORIA Distribution' "$output/entity-tree.txt" || fail "root entity missing from tree"
  grep -Fq 'ROOT -> HQ | Siège Orléans' "$output/entity-tree.txt" || fail "HQ entity missing from tree"
  grep -Fq 'REQUESTER,SELF,false,false,false,false' "$output/access-matrix.csv" \
    || fail "requester matrix is incorrect"

  if grep -RniE '(password|passwd|secret|token|private[_ -]?key)[[:space:]]*[:=][[:space:]]*[^[:space:]]+' \
      "$input" "$output" "$evidence" --exclude='checksums.sha256'; then
    fail "credential-like value found in lab dossier"
  fi

  (cd "$LAB_ROOT" && sha256sum -c evidence/checksums.sha256 >/dev/null) \
    || fail "checksum verification failed"

  printf 'Validation successful: GLPI architecture, organization, profiles and deployment safeguards are coherent.\n'
}

reset_lab() {
  require_marker
  rm -rf "$LAB_ROOT"
  printf 'GLPI foundations laboratory removed: %s\n' "$LAB_ROOT"
}

resolve_safe_root

case "$ACTION" in
  setup) setup_lab ;;
  status) show_status ;;
  run) run_reference ;;
  validate) validate_reference ;;
  reset) reset_lab ;;
  *) fail "unknown action: $ACTION" ;;
esac
