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

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

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"; }
csv_rows() { awk 'END { print (NR > 0 ? NR - 1 : 0) }' "$1"; }

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

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

  cat > "$LAB_ROOT/refs/groups.csv" <<'CSV'
group_code,name,role
SUPPORT_N1,Support N1,technical
INFRA,Infrastructure,technical
SECURITY,Sécurité,technical
MANAGERS,Managers,approval
CAB,Change Advisory Board,approval
CSV

  cat > "$LAB_ROOT/refs/categories.csv" <<'CSV'
category_code,parent_code,name,ticket_type,default_group,template_code,sla_code,kb_category
INCIDENT,,Incident,incident,SUPPORT_N1,TPL_INCIDENT,SLA_STANDARD,Support
INCIDENT_NETWORK,INCIDENT,Réseau et connectivité,incident,INFRA,TPL_NETWORK,SLA_CRITICAL,Réseau
INCIDENT_SECURITY,INCIDENT,Sécurité,incident,SECURITY,TPL_SECURITY,SLA_CRITICAL,Sécurité
REQUEST,,Demande,request,SUPPORT_N1,TPL_REQUEST,SLA_STANDARD,Services
REQUEST_ACCESS,REQUEST,Accès et habilitation,request,SUPPORT_N1,TPL_ACCESS,SLA_STANDARD,Accès
REQUEST_HARDWARE,REQUEST,Matériel,request,SUPPORT_N1,TPL_HARDWARE,SLA_STANDARD,Matériel
CSV

  cat > "$LAB_ROOT/refs/templates.csv" <<'CSV'
template_code,ticket_type,mandatory_fields,approval_required
TPL_INCIDENT,incident,requester|category|description|asset,no
TPL_NETWORK,incident,requester|category|description|asset|impact,no
TPL_SECURITY,incident,requester|category|description|asset|impact|evidence,no
TPL_REQUEST,request,requester|category|description,no
TPL_ACCESS,request,requester|category|description|target_user|business_owner,yes
TPL_HARDWARE,request,requester|category|description|target_user|cost_center,yes
CSV

  cat > "$LAB_ROOT/refs/priority-matrix.csv" <<'CSV'
impact,urgency,priority_code,priority_name
1,1,P1,Très basse
1,2,P1,Très basse
1,3,P2,Basse
1,4,P2,Basse
1,5,P3,Moyenne
2,1,P1,Très basse
2,2,P2,Basse
2,3,P2,Basse
2,4,P3,Moyenne
2,5,P3,Moyenne
3,1,P2,Basse
3,2,P2,Basse
3,3,P3,Moyenne
3,4,P4,Haute
3,5,P4,Haute
4,1,P2,Basse
4,2,P3,Moyenne
4,3,P4,Haute
4,4,P4,Haute
4,5,P5,Critique
5,1,P3,Moyenne
5,2,P3,Moyenne
5,3,P4,Haute
5,4,P5,Critique
5,5,P5,Critique
CSV

  cat > "$LAB_ROOT/refs/calendars.csv" <<'CSV'
calendar_code,name,working_minutes_per_day,working_days
BUSINESS_8X5,Heures ouvrées,600,MON-FRI
CONTINUOUS_24X7,Continu 24x7,1440,MON-SUN
CSV

  cat > "$LAB_ROOT/refs/service-levels.csv" <<'CSV'
service_code,kind,calendar_code,tto_minutes,ttr_minutes,pending_pauses
SLA_STANDARD,SLA,BUSINESS_8X5,240,960,yes
SLA_CRITICAL,SLA,CONTINUOUS_24X7,30,240,yes
OLA_N1,OLA,BUSINESS_8X5,60,480,yes
OLA_INFRA,OLA,CONTINUOUS_24X7,15,180,yes
CSV

  cat > "$LAB_ROOT/refs/transitions.csv" <<'CSV'
from_status,to_status,allowed
NEW,ASSIGNED,yes
NEW,PENDING,yes
ASSIGNED,PLANNED,yes
ASSIGNED,PENDING,yes
ASSIGNED,SOLVED,yes
PLANNED,PENDING,yes
PLANNED,SOLVED,yes
PENDING,ASSIGNED,yes
PENDING,PLANNED,yes
SOLVED,CLOSED,yes
SOLVED,ASSIGNED,yes
CSV

  cat > "$LAB_ROOT/refs/approval-steps.csv" <<'CSV'
step_code,name,min_percent
MANAGER_100,Manager approval,100
CAB_66,CAB qualified majority,66
CSV

  cat > "$LAB_ROOT/input/tickets.csv" <<'CSV'
ticket_id,ticket_type,category_code,requester,impact,urgency,opened_minute,assigned_minute,resolved_minute,pending_minutes,current_status,asset_id,title
TCK-001,incident,INCIDENT_NETWORK,alice,5,5,0,12,190,0,SOLVED,ORL-NET-001,Coupure réseau siège
TCK-002,request,REQUEST_ACCESS,bob,2,3,30,90,700,120,SOLVED,,Accès ERP pour nouvelle recrue
TCK-003,incident,INCIDENT_SECURITY,carol,4,5,60,78,250,20,SOLVED,ORL-PC-017,Alerte poste suspect
TCK-004,incident,INCIDENT_NETWORK,daniel,3,4,100,180,1100,300,SOLVED,TOU-NET-001,Latence récurrente agence
TCK-005,request,REQUEST_HARDWARE,emma,2,2,150,300,1300,240,SOLVED,,Demande ordinateur portable
TCK-006,incident,INCIDENT,frank,1,2,200,260,420,0,CLOSED,ORL-PRN-003,Bourrage imprimante
CSV

  cat > "$LAB_ROOT/input/events.csv" <<'CSV'
ticket_id,sequence,from_status,to_status,actor_group,event_minute,evidence
TCK-001,1,NEW,ASSIGNED,INFRA,12,assignation automatique catégorie
TCK-001,2,ASSIGNED,SOLVED,INFRA,190,configuration port corrigée
TCK-002,1,NEW,PENDING,SUPPORT_N1,45,validation manager demandée
TCK-002,2,PENDING,ASSIGNED,SUPPORT_N1,165,validation accordée
TCK-002,3,ASSIGNED,SOLVED,SUPPORT_N1,700,compte créé et testé
TCK-003,1,NEW,ASSIGNED,SECURITY,78,prise en charge sécurité
TCK-003,2,ASSIGNED,PENDING,SECURITY,120,isolement utilisateur confirmé
TCK-003,3,PENDING,ASSIGNED,SECURITY,140,analyse reprise
TCK-003,4,ASSIGNED,SOLVED,SECURITY,250,poste assaini
TCK-004,1,NEW,ASSIGNED,INFRA,180,affectation infrastructure
TCK-004,2,ASSIGNED,PENDING,INFRA,400,attente opérateur
TCK-004,3,PENDING,PLANNED,INFRA,700,fenêtre intervention
TCK-004,4,PLANNED,SOLVED,INFRA,1100,changement QoS appliqué
TCK-005,1,NEW,PENDING,SUPPORT_N1,200,validation budget demandée
TCK-005,2,PENDING,ASSIGNED,SUPPORT_N1,440,validation accordée
TCK-005,3,ASSIGNED,SOLVED,SUPPORT_N1,1300,matériel remis
TCK-006,1,NEW,ASSIGNED,SUPPORT_N1,260,technicien affecté
TCK-006,2,ASSIGNED,SOLVED,SUPPORT_N1,420,bourrage retiré
TCK-006,3,SOLVED,CLOSED,SUPPORT_N1,500,solution acceptée
CSV

  cat > "$LAB_ROOT/input/approvals.csv" <<'CSV'
object_id,object_type,step_code,recipient_group,requested,approved,rejected,status
TCK-002,ticket,MANAGER_100,MANAGERS,1,1,0,GRANTED
TCK-005,ticket,MANAGER_100,MANAGERS,1,1,0,GRANTED
CHG-001,change,CAB_66,CAB,3,2,1,GRANTED
CSV

  cat > "$LAB_ROOT/input/problems.csv" <<'CSV'
problem_id,title,symptoms,root_cause,workaround,linked_tickets
PRB-001,Latence WAN Tours,Débit instable|pertes paquets,File QoS incorrecte,Prioriser trafic métier,TCK-004
CSV

  cat > "$LAB_ROOT/input/changes.csv" <<'CSV'
change_id,title,linked_problem,linked_tickets,approval_step,deployment_plan,backup_plan,checklist,status
CHG-001,Correction QoS WAN Tours,PRB-001,TCK-004,CAB_66,Appliquer politique QoS versionnée,Exporter configuration routeur,Test débit|rollback testé|supervision active,CLOSED
CSV

  cat > "$LAB_ROOT/input/solutions.csv" <<'CSV'
ticket_id,solution_type,summary,requester_approval,kb_candidate,kb_category
TCK-001,PERMANENT,Correction configuration port,ACCEPTED,yes,Réseau
TCK-002,FULFILLED,Compte ERP créé selon validation,ACCEPTED,yes,Accès
TCK-003,PERMANENT,Poste isolé puis assaini,ACCEPTED,no,Sécurité
TCK-004,PERMANENT,Politique QoS corrigée via CHG-001,ACCEPTED,yes,Réseau
TCK-005,FULFILLED,Portable attribué et inventorié,ACCEPTED,no,Matériel
TCK-006,PERMANENT,Bourrage retiré et test impression,ACCEPTED,yes,Support
CSV

  (
    cd "$LAB_ROOT"
    sha256sum refs/*.csv input/*.csv > evidence/source-checksums-before.sha256
  )

  cat > "$LAB_ROOT/README.txt" <<'TXT'
Commands: setup, status, run, validate, reset
This offline lab validates service-desk data and decisions. It never contacts GLPI.
TXT
  printf 'GLPI service desk laboratory ready: %s\n' "$LAB_ROOT"
}

show_status() {
  require_marker
  printf 'Tickets: %s\n' "$(csv_rows "$LAB_ROOT/input/tickets.csv")"
  printf 'Events: %s\n' "$(csv_rows "$LAB_ROOT/input/events.csv")"
  printf 'Approvals: %s\n' "$(csv_rows "$LAB_ROOT/input/approvals.csv")"
  [ -f "$LAB_ROOT/output/ticket-evaluation.csv" ] && printf 'Evaluated tickets: %s\n' "$(csv_rows "$LAB_ROOT/output/ticket-evaluation.csv")" || printf 'Evaluated tickets: not generated\n'
}

run_reference() {
  require_marker
  mkdir -p "$LAB_ROOT/output" "$LAB_ROOT/evidence"

  for spec in \
    "groups.csv:1:group code" \
    "categories.csv:1:category code" \
    "templates.csv:1:template code" \
    "calendars.csv:1:calendar code" \
    "service-levels.csv:1:service level code" \
    "approval-steps.csv:1:approval step code"; do
    IFS=: read -r file col label <<< "$spec"
    assert_unique_column "$LAB_ROOT/refs/$file" "$col" "$label"
  done
  assert_unique_column "$LAB_ROOT/input/tickets.csv" 1 "ticket id"

  awk -F',' '
    NR==FNR { if(FNR>1) groups[$1]=1; next }
    FNR==1 { next }
    !($5 in groups) { print "unknown default group " $5 > "/dev/stderr"; exit 1 }
  ' "$LAB_ROOT/refs/groups.csv" "$LAB_ROOT/refs/categories.csv" || fail "category group validation failed"

  awk -F',' '
    NR==FNR { if(FNR>1) templates[$1]=$2; next }
    FNR==1 { next }
    !($6 in templates) { print "unknown template " $6 > "/dev/stderr"; exit 1 }
    templates[$6] != $4 { print "template type mismatch " $1 > "/dev/stderr"; exit 1 }
  ' "$LAB_ROOT/refs/templates.csv" "$LAB_ROOT/refs/categories.csv" || fail "category template validation failed"

  awk -F',' '
    NR==FNR { if(FNR>1) calendars[$1]=1; next }
    FNR==1 { next }
    !($3 in calendars) { print "unknown calendar " $3 > "/dev/stderr"; exit 1 }
    $2 != "SLA" && $2 != "OLA" { print "invalid service kind " $2 > "/dev/stderr"; exit 1 }
    $4+0 <= 0 || $5+0 <= 0 { print "invalid service times " $1 > "/dev/stderr"; exit 1 }
    $6 != "yes" { print "pending pause must be explicit " $1 > "/dev/stderr"; exit 1 }
  ' "$LAB_ROOT/refs/calendars.csv" "$LAB_ROOT/refs/service-levels.csv" || fail "service-level validation failed"

  awk -F',' '
    NR==FNR { if(FNR>1) allowed[$1 SUBSEP $2]=$3; next }
    FNR==1 { next }
    allowed[$3 SUBSEP $4] != "yes" { print "invalid transition " $1 ": " $3 " -> " $4 > "/dev/stderr"; exit 1 }
    $7 == "" { print "missing event evidence " $1 > "/dev/stderr"; exit 1 }
  ' "$LAB_ROOT/refs/transitions.csv" "$LAB_ROOT/input/events.csv" || fail "lifecycle validation failed"

  awk -F',' -v OFS=',' \
    -v cats="$LAB_ROOT/refs/categories.csv" \
    -v matrix="$LAB_ROOT/refs/priority-matrix.csv" \
    -v levels="$LAB_ROOT/refs/service-levels.csv" \
    -v output="$LAB_ROOT/output/ticket-evaluation.csv" \
    -v escalations="$LAB_ROOT/output/escalations.csv" '
    function load(file, kind, line,n) {
      n=0
      while((getline line < file)>0){ n++; if(n==1)continue; split(line,f,",")
        if(kind=="cat") { ctype[f[1]]=f[4]; cgroup[f[1]]=f[5]; ctemplate[f[1]]=f[6]; csla[f[1]]=f[7] }
        else if(kind=="matrix") { pcode[f[1] SUBSEP f[2]]=f[3]; pname[f[1] SUBSEP f[2]]=f[4] }
        else if(kind=="level") { skind[f[1]]=f[2]; scal[f[1]]=f[3]; stto[f[1]]=f[4]; sttr[f[1]]=f[5]; spause[f[1]]=f[6] }
      } close(file)
    }
    BEGIN {
      load(cats,"cat"); load(matrix,"matrix"); load(levels,"level")
      print "ticket_id,ticket_type,category_code,assigned_group,template_code,priority_code,priority_name,sla_code,tto_elapsed,tto_target,tto_status,ttr_elapsed,ttr_target,ttr_status,pending_minutes,current_status" > output
      print "ticket_id,trigger,action,reason" > escalations
    }
    NR==1{next}
    {
      id=$1; type=$2; cat=$3; impact=$5+0; urgency=$6+0; opened=$7+0; assigned=$8+0; resolved=$9+0; pending=$10+0; status=$11
      if(!(cat in ctype)){ print "unknown category " cat > "/dev/stderr"; exit 1 }
      if(ctype[cat] != type){ print "ticket/category type mismatch " id > "/dev/stderr"; exit 1 }
      key=impact SUBSEP urgency
      if(!(key in pcode)){ print "missing priority matrix cell " impact "/" urgency > "/dev/stderr"; exit 1 }
      sla=csla[cat]
      tto=assigned-opened
      ttr=resolved-opened
      if(spause[sla]=="yes") ttr-=pending
      tto_state=(tto<=stto[sla]?"PASS":"BREACH")
      ttr_state=(ttr<=sttr[sla]?"PASS":"BREACH")
      print id,type,cat,cgroup[cat],ctemplate[cat],pcode[key],pname[key],sla,tto,stto[sla],tto_state,ttr,sttr[sla],ttr_state,pending,status > output
      if(tto_state=="BREACH") print id,"TTO_BREACH","ASSIGN_TO_MANAGER","ticket not owned before target" > escalations
      if(ttr_state=="BREACH") print id,"TTR_BREACH","ESCALATE_AND_NOTIFY","resolution exceeded target excluding pending" > escalations
      else if(ttr >= int(sttr[sla]*0.75)) print id,"TTR_75_PERCENT","WARN_ASSIGNED_GROUP","resolution approaching target" > escalations
    }
  ' "$LAB_ROOT/input/tickets.csv" || fail "ticket evaluation failed"

  awk -F',' -v OFS=',' \
    -v steps="$LAB_ROOT/refs/approval-steps.csv" \
    -v output="$LAB_ROOT/output/approval-evaluation.csv" '
    BEGIN { n=0; while((getline line < steps)>0){n++;if(n==1)continue;split(line,f,",");minp[f[1]]=f[3]} close(steps); print "object_id,object_type,step_code,approved_percent,required_percent,computed_status,declared_status" > output }
    NR==1{next}
    {
      if(!($3 in minp)){print "unknown approval step " $3 > "/dev/stderr";exit 1}
      requested=$5+0; approved=$6+0; rejected=$7+0
      if(requested<=0 || approved+rejected>requested){print "invalid approval counts " $1 > "/dev/stderr";exit 1}
      percent=int((approved*100)/requested)
      computed=(percent>=minp[$3]?"GRANTED":(rejected>requested-approved?"REJECTED":"PENDING"))
      print $1,$2,$3,percent,minp[$3],computed,$8 > output
      if(computed!=$8){print "approval status mismatch " $1 > "/dev/stderr";exit 1}
    }
  ' "$LAB_ROOT/input/approvals.csv" || fail "approval evaluation failed"

  awk -F',' -v OFS=',' \
    -v solutions="$LAB_ROOT/input/solutions.csv" \
    -v output="$LAB_ROOT/output/knowledge-candidates.csv" '
    BEGIN { print "ticket_id,kb_category,title,publication_gate" > output }
    NR==1{next}
    { ticket[$1]=$0; title[$1]=$13; state[$1]=$11 }
    END {
      n=0
      while((getline line < solutions)>0){n++;if(n==1)continue;split(line,f,",")
        if(!(f[1] in ticket)){print "solution without ticket " f[1] > "/dev/stderr"; exit 1}
        if(f[4]!="ACCEPTED"){print "unaccepted solution " f[1] > "/dev/stderr";exit 1}
        if(f[5]=="yes") print f[1],f[6],title[f[1]],"REVIEW_BEFORE_PUBLICATION" > output
      }
      close(solutions)
    }
  ' "$LAB_ROOT/input/tickets.csv" || fail "solution and KB validation failed"

  awk -F',' '
    NR==FNR { if(FNR>1) tickets[$1]=1; next }
    FNR==1{next}
    {
      split($6,linked,"|")
      for(i in linked) if(!(linked[i] in tickets)){print "problem unknown ticket " linked[i] > "/dev/stderr"; exit 1}
      if($3=="" || $4=="" || $5==""){print "incomplete problem analysis " $1 > "/dev/stderr";exit 1}
    }
  ' "$LAB_ROOT/input/tickets.csv" "$LAB_ROOT/input/problems.csv" || fail "problem validation failed"

  awk -F',' '
    NR==FNR { if(FNR>1) problems[$1]=1; next }
    FNR==1{next}
    !($3 in problems){print "change unknown problem " $3 > "/dev/stderr";exit 1}
    $6=="" || $7=="" || $8=="" {print "incomplete change plan " $1 > "/dev/stderr";exit 1}
  ' "$LAB_ROOT/input/problems.csv" "$LAB_ROOT/input/changes.csv" || fail "change validation failed"

  {
    printf '# Rapport service desk et SLA\n\n'
    printf -- '- tickets : %s\n' "$(csv_rows "$LAB_ROOT/output/ticket-evaluation.csv")"
    printf -- '- escalades : %s\n' "$(csv_rows "$LAB_ROOT/output/escalations.csv")"
    printf -- '- validations : %s\n' "$(csv_rows "$LAB_ROOT/output/approval-evaluation.csv")"
    printf -- '- candidats KB : %s\n' "$(csv_rows "$LAB_ROOT/output/knowledge-candidates.csv")"
    printf '\nLes temps du laboratoire sont des minutes ouvrées abstraites. Ils valident la logique, pas un calendrier GLPI réel.\n'
  } > "$LAB_ROOT/evidence/service-desk-report.md"

  (
    cd "$LAB_ROOT"
    sha256sum refs/*.csv input/*.csv > evidence/source-checksums-after.sha256
    sha256sum output/*.csv evidence/service-desk-report.md > evidence/output-checksums.sha256
  )
  diff -u "$LAB_ROOT/evidence/source-checksums-before.sha256" "$LAB_ROOT/evidence/source-checksums-after.sha256" > "$LAB_ROOT/evidence/source-immutability.diff" || fail "source data changed"
  printf 'Offline service desk evaluation completed.\n'
}

validate_reference() {
  require_marker
  for file in ticket-evaluation.csv escalations.csv approval-evaluation.csv knowledge-candidates.csv; do [ -f "$LAB_ROOT/output/$file" ] || fail "missing output $file"; done
  [ "$(csv_rows "$LAB_ROOT/output/ticket-evaluation.csv")" -eq 6 ] || fail "expected 6 evaluated tickets"
  [ "$(csv_rows "$LAB_ROOT/output/approval-evaluation.csv")" -eq 3 ] || fail "expected 3 approval evaluations"
  [ "$(csv_rows "$LAB_ROOT/output/knowledge-candidates.csv")" -eq 4 ] || fail "expected 4 KB candidates"
  grep -Fq 'TCK-001,incident,INCIDENT_NETWORK,INFRA,TPL_NETWORK,P5,Critique,SLA_CRITICAL,12,30,PASS,190,240,PASS' "$LAB_ROOT/output/ticket-evaluation.csv" || fail "critical incident evaluation missing"
  grep -Fq 'TCK-002,request,REQUEST_ACCESS,SUPPORT_N1,TPL_ACCESS,P2,Basse,SLA_STANDARD,60,240,PASS,550,960,PASS,120,SOLVED' "$LAB_ROOT/output/ticket-evaluation.csv" || fail "pending pause calculation missing"
  grep -Fq 'TCK-004,TTR_BREACH,ESCALATE_AND_NOTIFY' "$LAB_ROOT/output/escalations.csv" || fail "expected SLA breach missing"
  grep -Fq 'CHG-001,change,CAB_66,66,66,GRANTED,GRANTED' "$LAB_ROOT/output/approval-evaluation.csv" || fail "CAB approval missing"
  grep -Fq 'TCK-004,Réseau,Latence récurrente agence,REVIEW_BEFORE_PUBLICATION' "$LAB_ROOT/output/knowledge-candidates.csv" || fail "KB candidate missing"
  [ ! -s "$LAB_ROOT/evidence/source-immutability.diff" ] || fail "sources changed"
  (
    cd "$LAB_ROOT"
    sha256sum -c evidence/source-checksums-after.sha256 >/dev/null
    sha256sum -c evidence/output-checksums.sha256 >/dev/null
  ) || fail "checksum verification failed"
  ! grep -RniE --exclude='*.sh' --exclude='*checksums*' '(password[[:space:]]*=|token[[:space:]]*=|private[_-]?key|https?://[^/[:space:]]+@)' "$LAB_ROOT" >/dev/null || fail "credential-like value detected"
  grep -Fq 'minutes ouvrées abstraites' "$LAB_ROOT/evidence/service-desk-report.md" || fail "proof boundary missing"
  printf 'Validation successful: classification, priority, lifecycle, SLA, escalation, approvals, problem/change links and KB candidates are correct.\n'
}

reset_lab() { require_marker; rm -rf "$LAB_ROOT"; printf 'GLPI service desk 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
