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

LAB_ROOT="${SORIA_LINUX_TEXT_LAB:-/tmp/soria-linux-text-${USER:-$(id -un)}}"
MARKER="$LAB_ROOT/.soria-linux-text-lab"

usage() {
  cat <<'USAGE'
Usage: module5-text-automation-lab.sh {setup|status|run|validate|reset}

The lab writes only below SORIA_LINUX_TEXT_LAB. The path must remain under
/tmp or the current user's HOME. It never uses sudo or modifies system files.
USAGE
}

is_safe_root() {
  case "$LAB_ROOT" in
    /tmp/soria-linux-text-*|"$HOME"/soria-linux-text-*) return 0 ;;
    *) return 1 ;;
  esac
}

require_safe_root() {
  is_safe_root || { printf 'Refusing unsafe lab path: %s\n' "$LAB_ROOT" >&2; exit 2; }
}

require_initialized() {
  [[ -f "$MARKER" ]] || { printf 'Lab not initialized: %s\n' "$LAB_ROOT" >&2; exit 1; }
}

setup_lab() {
  require_safe_root
  if [[ -e "$LAB_ROOT" && ! -f "$MARKER" ]]; then
    printf 'Refusing existing unmarked directory: %s\n' "$LAB_ROOT" >&2
    exit 2
  fi

  mkdir -p "$LAB_ROOT"/{bin,data,expected,logs,project,reports,tests,tmp}
  : > "$MARKER"

  cat > "$LAB_ROOT/data/access.log" <<'ACCESS'
2026-08-04T06:00:01Z method=GET path=/ status=200 bytes=1820 duration_ms=18 client=10.20.0.11
2026-08-04T06:00:03Z method=GET path=/courses/linux status=200 bytes=8420 duration_ms=43 client=10.20.0.12
2026-08-04T06:00:05Z method=POST path=/api/login status=401 bytes=210 duration_ms=71 client=10.20.0.55
2026-08-04T06:00:06Z method=POST path=/api/login status=401 bytes=210 duration_ms=68 client=10.20.0.55
2026-08-04T06:00:08Z method=POST path=/api/login status=200 bytes=384 duration_ms=92 client=10.20.0.55
2026-08-04T06:00:10Z method=GET path=/api/profile status=200 bytes=1240 duration_ms=37 client=10.20.0.55
2026-08-04T06:00:12Z method=GET path=/assets/app.js status=304 bytes=0 duration_ms=9 client=10.20.0.12
2026-08-04T06:00:15Z method=GET path=/api/courses status=500 bytes=98 duration_ms=310 client=10.20.0.13
2026-08-04T06:00:17Z method=GET path=/api/courses status=502 bytes=120 duration_ms=421 client=10.20.0.14
2026-08-04T06:00:20Z method=GET path=/health status=200 bytes=18 duration_ms=4 client=127.0.0.1
malformed line without structured fields
2026-08-04T06:00:25Z method=GET path=/api/courses status=200 bytes=4512 duration_ms=55 client=10.20.0.13
ACCESS

  cat > "$LAB_ROOT/data/auth.log" <<'AUTH'
Aug  4 06:00:05 srv01 sshd[1201]: Failed password for invalid user admin from 203.0.113.20 port 51100 ssh2
Aug  4 06:00:07 srv01 sshd[1204]: Failed password for invalid user admin from 203.0.113.20 port 51102 ssh2
Aug  4 06:00:11 srv01 sshd[1208]: Accepted publickey for deploy from 192.0.2.10 port 49220 ssh2
Aug  4 06:00:19 srv01 sudo:    ben : TTY=pts/0 ; PWD=/home/ben ; USER=root ; COMMAND=/usr/bin/systemctl status ssh
Aug  4 06:00:23 srv01 sshd[1212]: Failed password for root from 198.51.100.77 port 38122 ssh2
AUTH

  cat > "$LAB_ROOT/data/inventory.csv" <<'CSV'
host,role,os,environment,cpu,ram_mb,owner
srv01,web,Debian 13,production,4,8192,platform
srv02,api,Ubuntu 24.04,production,4,8192,platform
srv03,db,Rocky Linux 9,production,8,16384,data
srv04,worker,Debian 13,staging,2,4096,platform
srv05,monitoring,Ubuntu 24.04,production,4,8192,ops
srv06,backup,Rocky Linux 9,staging,2,4096,ops
CSV

  cat > "$LAB_ROOT/data/services.psv" <<'PSV'
service|owner|state|latency_ms|errors
frontend|web|ok|43|0
api|platform|degraded|310|2
postgresql|data|ok|18|0
monitoring|ops|ok|52|0
backup|ops|warning|180|1
PSV

  cat > "$LAB_ROOT/data/app.conf" <<'CONF'
# SORIA application configuration
APP_ENV = production
LOG_LEVEL = info
WORKERS = 4
FEATURE_LOGIN = true
BACKUP_RETENTION_DAYS = 14
CONF

  cat > "$LAB_ROOT/expected/status-counts.txt" <<'EXPECTED'
200 6
304 1
401 2
500 1
502 1
EXPECTED

  cat > "$LAB_ROOT/expected/report.txt" <<'EXPECTED'
SORIA Linux operational report
valid_requests=11
malformed_lines=1
http_2xx=6
http_3xx=1
http_4xx=2
http_5xx=2
bytes_total=17032
slow_requests=2
unique_clients=6
EXPECTED

  cat > "$LAB_ROOT/bin/report-starter.sh" <<'STARTER'
#!/usr/bin/env bash
set -euo pipefail

# TODO: accept INPUT and OUTPUT arguments, validate the input file, write through
# a temporary file, aggregate the structured access log and replace OUTPUT only
# after a successful run.

printf 'starter: implement the report generator\n' >&2
exit 2
STARTER

  cat > "$LAB_ROOT/bin/report-reference.sh" <<'REFERENCE'
#!/usr/bin/env bash
set -Eeuo pipefail

usage() {
  printf 'Usage: %s INPUT OUTPUT\n' "${0##*/}" >&2
}

cleanup() {
  if [[ -n "${tmp_file:-}" && -f "$tmp_file" ]]; then
    rm -f "$tmp_file"
  fi
}

on_error() {
  local rc=$?
  printf 'report generation failed at line %s with status %s\n' "$1" "$rc" >&2
  exit "$rc"
}

trap cleanup EXIT
trap 'on_error $LINENO' ERR

[[ $# -eq 2 ]] || { usage; exit 2; }
input=$1
output=$2

[[ -f "$input" && -r "$input" ]] || {
  printf 'input is not a readable file: %s\n' "$input" >&2
  exit 3
}

output_dir=${output%/*}
if [[ "$output_dir" == "$output" ]]; then
  output_dir=.
fi
mkdir -p "$output_dir"
tmp_file=$(mktemp "$output_dir/.soria-report.XXXXXX")

awk '
BEGIN {
  valid=0; malformed=0; bytes=0; slow=0;
  c2=0; c3=0; c4=0; c5=0;
}
{
  status=""; size=""; duration=""; client="";
  for (i=1; i<=NF; i++) {
    split($i, pair, "=");
    if (pair[1] == "status") status=pair[2];
    else if (pair[1] == "bytes") size=pair[2];
    else if (pair[1] == "duration_ms") duration=pair[2];
    else if (pair[1] == "client") client=pair[2];
  }

  if (status !~ /^[0-9][0-9][0-9]$/ ||
      size !~ /^[0-9]+$/ ||
      duration !~ /^[0-9]+$/ ||
      client == "") {
    malformed++;
    next;
  }

  valid++;
  bytes += size;
  clients[client]=1;
  if (duration >= 250) slow++;
  class=substr(status, 1, 1);
  if (class == "2") c2++;
  else if (class == "3") c3++;
  else if (class == "4") c4++;
  else if (class == "5") c5++;
}
END {
  unique=0;
  for (client in clients) unique++;
  print "SORIA Linux operational report";
  print "valid_requests=" valid;
  print "malformed_lines=" malformed;
  print "http_2xx=" c2;
  print "http_3xx=" c3;
  print "http_4xx=" c4;
  print "http_5xx=" c5;
  print "bytes_total=" bytes;
  print "slow_requests=" slow;
  print "unique_clients=" unique;
}
' "$input" > "$tmp_file"

chmod 0644 "$tmp_file"
mv "$tmp_file" "$output"
tmp_file=""
printf 'report written: %s\n' "$output"
REFERENCE

  cat > "$LAB_ROOT/tests/test-report.sh" <<'TEST'
#!/usr/bin/env bash
set -euo pipefail

lab_root=${1:?lab root required}
script=${2:-$lab_root/bin/report-reference.sh}
actual="$lab_root/tmp/report.actual"
error_log="$lab_root/tmp/report.error"
rm -f "$actual" "$error_log"

bash -n "$script"
"$script" "$lab_root/data/access.log" "$actual"
diff -u "$lab_root/expected/report.txt" "$actual"

if "$script" "$lab_root/data/missing.log" "$actual" > /dev/null 2> "$error_log"; then
  printf 'missing input unexpectedly succeeded\n' >&2
  exit 1
fi

grep -q 'input is not a readable file' "$error_log"
printf 'report tests passed\n'
TEST

  cat > "$LAB_ROOT/project/README.md" <<'PROJECT'
# Projet final — Exploitation d'un serveur Linux

Livrables attendus :

1. inventaire système et processus ;
2. contrôle des permissions d'un espace partagé ;
3. vérification d'un service systemd utilisateur ;
4. extraction des erreurs HTTP et événements SSH ;
5. rapport automatique produit par un script Bash robuste ;
6. journal d'exploitation avec preuves, incident volontaire et rollback.

Le projet doit rester dans ce laboratoire et ne doit pas modifier le bootloader,
les comptes système, les paquets ou les services système de la machine hôte.
PROJECT

  cat > "$LAB_ROOT/project/acceptance-checklist.md" <<'CHECKLIST'
# Recette technique

- [ ] Les commandes d'inventaire sont horodatées.
- [ ] Les droits et propriétaires sont justifiés.
- [ ] Les expressions rationnelles sont testées sur cas positif et négatif.
- [ ] Les lignes malformées sont comptées sans interrompre le rapport.
- [ ] Le script utilise un fichier temporaire et un remplacement final.
- [ ] Le code de sortie est non nul sur entrée absente.
- [ ] stdout et stderr sont séparés.
- [ ] Les tests automatiques passent.
- [ ] Le rollback et le nettoyage sont documentés.
CHECKLIST

  chmod 0755 "$LAB_ROOT/bin/"*.sh "$LAB_ROOT/tests/"*.sh
  chmod 0644 "$LAB_ROOT/data/"* "$LAB_ROOT/expected/"* "$LAB_ROOT/project/"*
  printf 'Lab ready: %s\n' "$LAB_ROOT"
}

show_status() {
  require_safe_root
  require_initialized
  printf 'Lab root: %s\n' "$LAB_ROOT"
  printf 'Data files:\n'
  find "$LAB_ROOT/data" -maxdepth 1 -type f -printf '  %f\n' | sort
  printf 'Scripts:\n'
  find "$LAB_ROOT/bin" "$LAB_ROOT/tests" -maxdepth 1 -type f -printf '  %f\n' | sort
  printf 'Project files:\n'
  find "$LAB_ROOT/project" -maxdepth 1 -type f -printf '  %f\n' | sort
}

run_report() {
  require_safe_root
  require_initialized
  "$LAB_ROOT/bin/report-reference.sh" \
    "$LAB_ROOT/data/access.log" \
    "$LAB_ROOT/reports/operational-report.txt"
  cat "$LAB_ROOT/reports/operational-report.txt"
}

validate_lab() {
  require_safe_root
  require_initialized
  bash -n "$LAB_ROOT/bin/report-starter.sh"
  bash -n "$LAB_ROOT/bin/report-reference.sh"
  bash -n "$LAB_ROOT/tests/test-report.sh"

  "$LAB_ROOT/tests/test-report.sh" "$LAB_ROOT"

  awk -F, 'NR > 1 { count[$4]++ } END { print "production=" count["production"]; print "staging=" count["staging"] }' \
    "$LAB_ROOT/data/inventory.csv" > "$LAB_ROOT/tmp/environment-counts.txt"
  grep -qx 'production=4' "$LAB_ROOT/tmp/environment-counts.txt"
  grep -qx 'staging=2' "$LAB_ROOT/tmp/environment-counts.txt"

  grep -Eo 'status=[0-9]{3}' "$LAB_ROOT/data/access.log" \
    | cut -d= -f2 \
    | sort \
    | uniq -c \
    | awk '{ print $2, $1 }' > "$LAB_ROOT/tmp/status-counts.txt"
  diff -u "$LAB_ROOT/expected/status-counts.txt" "$LAB_ROOT/tmp/status-counts.txt"

  printf 'Validation completed successfully.\n'
}

reset_lab() {
  require_safe_root
  require_initialized
  rm -rf --one-file-system "$LAB_ROOT"
  printf 'Lab removed: %s\n' "$LAB_ROOT"
}

case "${1:-}" in
  setup) setup_lab ;;
  status) show_status ;;
  run) run_report ;;
  validate) validate_lab ;;
  reset) reset_lab ;;
  *) usage; exit 2 ;;
esac
