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

ACTION="${1:-setup}"
ROOT="${2:-/tmp/soria-cyber-vulnerability-resilience-${USER:-student}}"
MARKER=".soria-cyber-vulnerability-resilience"

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

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

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

rows(){ awk 'END { print (NR > 0 ? NR - 1 : 0) }' "$1"; }

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

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

  cat > "$ROOT/refs/severity-scale.csv" <<'CSV'
label,min_score,max_score
LOW,0.1,3.9
MEDIUM,4.0,6.9
HIGH,7.0,8.9
CRITICAL,9.0,10.0
CSV

  cat > "$ROOT/input/assets.csv" <<'CSV'
asset_id,name,mission,criticality,exposure,owner,rto_hours,rpo_hours
AST-101,ERP commandes,Traiter commandes et facturation,critical,internal,Responsable ventes,8,4
AST-102,Portail client,Recevoir commandes et demandes,high,internet,Responsable digital,4,1
AST-103,Service identité,Authentifier utilisateurs et administrateurs,critical,internet,Responsable IT,2,1
AST-104,Serveur fichiers,Partager documents internes,high,internal,Responsable IT,12,8
AST-105,Postes finance,Préparer paiements et reporting,high,internal,Responsable finance,8,4
AST-106,Site vitrine,Présenter offres publiques,moderate,internet,Responsable communication,24,24
AST-107,Dépôt sauvegardes,Restaurer données et configurations,critical,restricted,Responsable continuité,4,1
AST-108,Coeur réseau,Relier sites et services,critical,restricted,Responsable infrastructure,1,not_applicable
CSV

  cat > "$ROOT/input/vulnerabilities.csv" <<'CSV'
vulnerability_id,cve_reference,asset_id,cvss_base,known_exploited,exploit_status,patch_available,compensating_control,age_days,evidence
VULN-101,CVE-DEMO-0001,AST-103,8.8,yes,active,yes,none,5,ADV-101
VULN-102,CVE-DEMO-0002,AST-104,9.8,no,none,yes,network_isolation,40,ADV-102
VULN-103,CVE-DEMO-0003,AST-102,7.5,yes,active,yes,none,2,ADV-103
VULN-104,NONE,AST-105,6.5,no,poc,no,application_allowlisting,120,REV-104
VULN-105,CVE-DEMO-0005,AST-106,9.1,no,poc,yes,waf_rule,15,ADV-105
VULN-106,CVE-DEMO-0006,AST-101,5.4,no,none,yes,segmentation,60,ADV-106
VULN-107,NONE,AST-107,7.2,no,none,no,offline_copy,90,REV-107
VULN-108,CVE-DEMO-0008,AST-108,8.1,no,active,no,access_restriction,10,ADV-108
CSV

  cat > "$ROOT/input/backup-plans.csv" <<'CSV'
backup_id,asset_id,copies,media_types,offsite,offline_or_immutable,encrypted,monitored,last_success_hours,restore_test_days,scope
BKP-101,AST-101,3,2,yes,yes,yes,yes,8,20,data_and_config
BKP-102,AST-103,2,1,no,no,yes,yes,6,45,config_only
BKP-103,AST-104,3,2,yes,yes,yes,no,28,120,data
BKP-104,AST-105,2,2,yes,no,yes,yes,12,50,user_data
BKP-105,AST-107,3,2,yes,yes,yes,yes,4,15,backup_catalog
CSV

  cat > "$ROOT/input/restore-tests.csv" <<'CSV'
test_id,backup_id,clean_environment,checksum_verified,application_started,business_validation,actual_rto_hours,target_rto_hours,recovered_age_hours,target_rpo_hours
REST-101,BKP-101,yes,yes,yes,yes,5,8,2,4
REST-102,BKP-102,yes,yes,yes,no,1,2,1,1
REST-103,BKP-103,yes,yes,yes,yes,16,12,4,8
REST-104,BKP-104,yes,yes,yes,yes,6,8,10,4
REST-105,BKP-105,yes,yes,yes,yes,3,4,1,1
CSV

  cat > "$ROOT/input/recovery-plan.csv" <<'CSV'
service_id,name,sequence,dependencies,target_rto_hours,target_rpo_hours,owner
SRV-101,Coeur réseau,1,,1,not_applicable,Responsable infrastructure
SRV-102,DNS interne,2,SRV-101,1,not_applicable,Responsable infrastructure
SRV-103,Service identité,3,SRV-101+SRV-102,2,1,Responsable IT
SRV-104,Base ERP,4,SRV-101,4,1,Responsable IT
SRV-105,ERP commandes,5,SRV-103+SRV-104,8,4,Responsable ventes
SRV-106,Portail client,3,SRV-103+SRV-105,4,1,Responsable digital
CSV

  cat > "$ROOT/input/proof-boundary.md" <<'MD'
# Frontière de preuve

Le laboratoire traite uniquement des données synthétiques. Les références `CVE-DEMO-*` ne sont pas des identifiants CVE réels. Le script ne scanne aucun système, ne consulte aucun catalogue en ligne, ne télécharge ni n'installe de correctif, ne sauvegarde aucune donnée réelle et ne restaure aucun service réel.
MD

  (
    cd "$ROOT"
    sha256sum refs/*.csv input/* > evidence/sources-before.sha256
  )

  cat > "$ROOT/README.txt" <<'TXT'
Commands:
  setup     create synthetic vulnerability and resilience records
  status    show fixture and output counts
  run       prioritize vulnerabilities and review backup/restore/recovery evidence
  validate  verify references, expected decisions and checksums
  reset     delete this marker-protected lab root
TXT

  printf 'Cybersecurity vulnerability and resilience lab ready: %s\n' "$ROOT"
}

show_status(){
  require_marker
  printf 'Assets: %s\n' "$(rows "$ROOT/input/assets.csv")"
  printf 'Vulnerabilities: %s\n' "$(rows "$ROOT/input/vulnerabilities.csv")"
  printf 'Backup plans: %s\n' "$(rows "$ROOT/input/backup-plans.csv")"
  printf 'Restore tests: %s\n' "$(rows "$ROOT/input/restore-tests.csv")"
  printf 'Recovery services: %s\n' "$(rows "$ROOT/input/recovery-plan.csv")"
  [ -f "$ROOT/output/vulnerability-priority.csv" ] && printf 'Outputs: generated\n' || printf 'Outputs: not generated\n'
}

run_lab(){
  require_marker
  unique_col "$ROOT/input/assets.csv" 1 "asset id"
  unique_col "$ROOT/input/vulnerabilities.csv" 1 "vulnerability id"
  unique_col "$ROOT/input/backup-plans.csv" 1 "backup id"
  unique_col "$ROOT/input/restore-tests.csv" 1 "restore test id"
  unique_col "$ROOT/input/recovery-plan.csv" 1 "service id"

  awk -F',' '
    FNR==NR { if(FNR>1) asset[$1]=1; next }
    FNR==1 { next }
    !($3 in asset) { print "unknown vulnerability asset: "$3 > "/dev/stderr"; exit 1 }
  ' "$ROOT/input/assets.csv" "$ROOT/input/vulnerabilities.csv" || fail "vulnerability asset reference failed"

  awk -F',' '
    FNR==NR { if(FNR>1) asset[$1]=1; next }
    FNR==1 { next }
    !($2 in asset) { print "unknown backup asset: "$2 > "/dev/stderr"; exit 1 }
  ' "$ROOT/input/assets.csv" "$ROOT/input/backup-plans.csv" || fail "backup asset reference failed"

  awk -F',' '
    FNR==NR { if(FNR>1) backup[$1]=1; next }
    FNR==1 { next }
    !($2 in backup) { print "unknown restore backup: "$2 > "/dev/stderr"; exit 1 }
  ' "$ROOT/input/backup-plans.csv" "$ROOT/input/restore-tests.csv" || fail "restore backup reference failed"

  awk -F',' -v OFS=',' '
    FILENAME==ARGV[1] { if(FNR>1){name[$1]=$2;crit[$1]=$4;exposure[$1]=$5} next }
    FNR==1 { print "vulnerability_id,cve_reference,asset_id,cvss_base,priority_score,priority,action,reasons"; next }
    {
      score=0; reasons=""
      if(crit[$3]=="critical"){score+=4;reasons="CRITICAL_ASSET"}
      else if(crit[$3]=="high"){score+=3;reasons="HIGH_ASSET"}
      else {score+=1;reasons="MODERATE_ASSET"}
      if(exposure[$3]=="internet"){score+=3;reasons=reasons"+INTERNET_EXPOSED"}
      else if(exposure[$3]=="restricted"){score+=1;reasons=reasons"+RESTRICTED_EXPOSURE"}
      if($5=="yes"){score+=5;reasons=reasons"+KNOWN_EXPLOITED"}
      if($6=="active"){score+=3;reasons=reasons"+ACTIVE_EXPLOIT"}
      else if($6=="poc"){score+=1;reasons=reasons"+PUBLIC_POC"}
      if($8=="none"){score+=2;reasons=reasons"+NO_COMPENSATING_CONTROL"}
      else {score-=1;reasons=reasons"+COMPENSATING_CONTROL"}
      if($9>=90){score+=1;reasons=reasons"+LONG_OPEN"}
      priority=(score>=13?"EMERGENCY":(score>=9?"HIGH":(score>=5?"MEDIUM":"LOW")))
      if(priority=="EMERGENCY" && $7=="yes") action="EMERGENCY_PATCH"
      else if(priority=="EMERGENCY") action="EMERGENCY_MITIGATE_AND_PLAN_FIX"
      else if(priority=="HIGH" && $7=="yes") action="EXPEDITED_PATCH"
      else if(priority=="HIGH") action="EXPEDITED_MITIGATION"
      else if($7=="yes") action="PLANNED_PATCH"
      else action="TRACK_AND_MITIGATE"
      print $1,$2,$3,$4,score,priority,action,reasons
    }
  ' "$ROOT/input/assets.csv" "$ROOT/input/vulnerabilities.csv" > "$ROOT/output/vulnerability-priority.csv"

  awk -F',' -v OFS=',' '
    NR==1 { print "backup_id,asset_id,decision,priority,reasons"; next }
    {
      decision="PASS_BASELINE"; priority="NORMAL"; reasons="COPIES_MEDIA_SCOPE_AND_TEST_EVIDENCE"
      if($3<3 || $4<2 || $5!="yes" || $6!="yes"){
        decision="BLOCK_RESILIENCE_GAP"; priority="HIGH"; reasons="INSUFFICIENT_SEPARATION_OR_COPIES"
      } else if($8!="yes"){
        decision="IMPROVE_MONITORING"; priority="HIGH"; reasons="BACKUP_SUCCESS_NOT_MONITORED"
      } else if($9>24){
        decision="INVESTIGATE_STALE_BACKUP"; priority="HIGH"; reasons="LAST_SUCCESS_TOO_OLD"
      } else if($10>90){
        decision="SCHEDULE_RESTORE_TEST"; priority="MEDIUM"; reasons="RESTORE_TEST_TOO_OLD"
      }
      print $1,$2,decision,priority,reasons
    }
  ' "$ROOT/input/backup-plans.csv" > "$ROOT/output/backup-review.csv"

  awk -F',' -v OFS=',' '
    NR==1 { print "test_id,backup_id,decision,reasons"; next }
    {
      decision="PASS_RESTORE_OBJECTIVES"; reasons="CLEAN_CHECKSUM_START_BUSINESS_RTO_RPO"
      if($3!="yes"){decision="BLOCK_UNCLEAN_ENVIRONMENT";reasons="RESTORE_ENVIRONMENT_NOT_CLEAN"}
      else if($4!="yes"){decision="BLOCK_INTEGRITY_NOT_VERIFIED";reasons="CHECKSUM_NOT_VERIFIED"}
      else if($5!="yes"){decision="FAIL_APPLICATION_START";reasons="APPLICATION_DID_NOT_START"}
      else if($6!="yes"){decision="BLOCK_BUSINESS_VALIDATION";reasons="BUSINESS_OWNER_NOT_VALIDATED"}
      else if($7>$8){decision="FAIL_RTO";reasons="ACTUAL_RTO_EXCEEDS_TARGET"}
      else if($9>$10){decision="FAIL_RPO";reasons="RECOVERED_DATA_TOO_OLD"}
      print $1,$2,decision,reasons
    }
  ' "$ROOT/input/restore-tests.csv" > "$ROOT/output/restore-results.csv"

  awk -F',' -v OFS=',' '
    NR==1 { next }
    { seq[$1]=$3; deps[$1]=$4; name[$1]=$2 }
    END {
      print "service_id,name,sequence,decision,reasons"
      for(id in seq){
        decision="PASS_DEPENDENCY_ORDER"; reasons="DEPENDENCIES_PRECEDE_SERVICE"
        n=split(deps[id],d,"+")
        for(i=1;i<=n;i++){
          if(d[i]=="") continue
          if(!(d[i] in seq)){decision="BLOCK_UNKNOWN_DEPENDENCY";reasons="UNKNOWN_"d[i];break}
          if(seq[d[i]]>=seq[id]){decision="BLOCK_DEPENDENCY_ORDER";reasons=d[i]"_NOT_BEFORE_"id;break}
        }
        print id,name[id],seq[id],decision,reasons
      }
    }
  ' "$ROOT/input/recovery-plan.csv" | { IFS= read -r header; printf '%s\n' "$header"; sort -t, -k3,3n -k1,1; } > "$ROOT/output/recovery-sequence.csv"

  {
    printf '# Synthèse lot 3\n\n'
    printf -- '- vulnérabilités urgence : %s\n' "$(awk -F',' 'NR>1&&$6=="EMERGENCY"{c++}END{print c+0}' "$ROOT/output/vulnerability-priority.csv")"
    printf -- '- plans de sauvegarde bloqués : %s\n' "$(awk -F',' 'NR>1&&$3=="BLOCK_RESILIENCE_GAP"{c++}END{print c+0}' "$ROOT/output/backup-review.csv")"
    printf -- '- tests de restauration en échec ou bloqués : %s\n' "$(awk -F',' 'NR>1&&$3!="PASS_RESTORE_OBJECTIVES"{c++}END{print c+0}' "$ROOT/output/restore-results.csv")"
    printf -- '- dépendances de reprise bloquées : %s\n\n' "$(awk -F',' 'NR>1&&$4!="PASS_DEPENDENCY_ORDER"{c++}END{print c+0}' "$ROOT/output/recovery-sequence.csv")"
    cat "$ROOT/input/proof-boundary.md"
  } > "$ROOT/evidence/summary.md"

  (
    cd "$ROOT"
    sha256sum refs/*.csv input/* > evidence/sources-after.sha256
    sha256sum output/*.csv evidence/summary.md > evidence/outputs.sha256
  )
  diff -u "$ROOT/evidence/sources-before.sha256" "$ROOT/evidence/sources-after.sha256" > "$ROOT/evidence/source.diff" || fail "source fixtures changed"
  printf 'Vulnerability prioritization and resilience review completed.\n'
}

validate_lab(){
  require_marker
  for f in \
    output/vulnerability-priority.csv \
    output/backup-review.csv \
    output/restore-results.csv \
    output/recovery-sequence.csv \
    evidence/summary.md \
    evidence/outputs.sha256 \
    evidence/source.diff
  do
    [ -f "$ROOT/$f" ] || fail "missing output: $f"
  done

  [ "$(rows "$ROOT/input/assets.csv")" -eq 8 ] || fail "expected 8 assets"
  [ "$(rows "$ROOT/input/vulnerabilities.csv")" -eq 8 ] || fail "expected 8 vulnerabilities"
  [ "$(rows "$ROOT/input/backup-plans.csv")" -eq 5 ] || fail "expected 5 backup plans"
  [ "$(rows "$ROOT/input/restore-tests.csv")" -eq 5 ] || fail "expected 5 restore tests"
  [ "$(rows "$ROOT/input/recovery-plan.csv")" -eq 6 ] || fail "expected 6 recovery services"

  grep -Fq 'VULN-101,CVE-DEMO-0001,AST-103,8.8,17,EMERGENCY,EMERGENCY_PATCH' "$ROOT/output/vulnerability-priority.csv" || fail "VULN-101 priority mismatch"
  grep -Fq 'VULN-102,CVE-DEMO-0002,AST-104,9.8,2,LOW,PLANNED_PATCH' "$ROOT/output/vulnerability-priority.csv" || fail "VULN-102 CVSS/risk distinction missing"
  grep -Fq 'VULN-103,CVE-DEMO-0003,AST-102,7.5,16,EMERGENCY,EMERGENCY_PATCH' "$ROOT/output/vulnerability-priority.csv" || fail "VULN-103 priority mismatch"
  grep -Fq 'VULN-108,CVE-DEMO-0008,AST-108,8.1,7,MEDIUM,TRACK_AND_MITIGATE' "$ROOT/output/vulnerability-priority.csv" || fail "VULN-108 mitigation mismatch"
  [ "$(awk -F',' 'NR>1&&$6=="EMERGENCY"{c++}END{print c+0}' "$ROOT/output/vulnerability-priority.csv")" -eq 2 ] || fail "expected 2 emergency vulnerabilities"

  grep -Fq 'BKP-101,AST-101,PASS_BASELINE' "$ROOT/output/backup-review.csv" || fail "BKP-101 baseline missing"
  grep -Fq 'BKP-102,AST-103,BLOCK_RESILIENCE_GAP' "$ROOT/output/backup-review.csv" || fail "BKP-102 blocker missing"
  grep -Fq 'BKP-103,AST-104,IMPROVE_MONITORING' "$ROOT/output/backup-review.csv" || fail "BKP-103 monitoring gap missing"
  [ "$(awk -F',' 'NR>1&&$3=="BLOCK_RESILIENCE_GAP"{c++}END{print c+0}' "$ROOT/output/backup-review.csv")" -eq 2 ] || fail "expected 2 backup resilience blockers"

  grep -Fq 'REST-101,BKP-101,PASS_RESTORE_OBJECTIVES' "$ROOT/output/restore-results.csv" || fail "REST-101 pass missing"
  grep -Fq 'REST-102,BKP-102,BLOCK_BUSINESS_VALIDATION' "$ROOT/output/restore-results.csv" || fail "REST-102 business validation blocker missing"
  grep -Fq 'REST-103,BKP-103,FAIL_RTO' "$ROOT/output/restore-results.csv" || fail "REST-103 RTO failure missing"
  grep -Fq 'REST-104,BKP-104,FAIL_RPO' "$ROOT/output/restore-results.csv" || fail "REST-104 RPO failure missing"
  [ "$(awk -F',' 'NR>1&&$3!="PASS_RESTORE_OBJECTIVES"{c++}END{print c+0}' "$ROOT/output/restore-results.csv")" -eq 3 ] || fail "expected 3 restore failures or blockers"

  grep -Fq 'SRV-106,Portail client,3,BLOCK_DEPENDENCY_ORDER' "$ROOT/output/recovery-sequence.csv" || fail "recovery dependency blocker missing"
  [ "$(awk -F',' 'NR>1&&$4!="PASS_DEPENDENCY_ORDER"{c++}END{print c+0}' "$ROOT/output/recovery-sequence.csv")" -eq 1 ] || fail "expected 1 recovery dependency blocker"

  [ ! -s "$ROOT/evidence/source.diff" ] || fail "source diff not empty"
  (
    cd "$ROOT"
    sha256sum -c evidence/sources-after.sha256 >/dev/null
    sha256sum -c evidence/outputs.sha256 >/dev/null
  ) || fail "checksum validation failed"

  printf 'Validation successful: vulnerability priority, backups, restores and recovery dependencies are correct.\n'
}

reset_lab(){
  require_marker
  rm -rf "$ROOT"
  printf 'Cybersecurity vulnerability and resilience lab removed: %s\n' "$ROOT"
}

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