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

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

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

resolve_safe_root() {
  case "$LAB_ROOT" in
    /tmp/soria-cyber-*|"${HOME}"/soria-cyber-*) ;;
    *) 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"; }
csv_rows() { awk 'END { print (NR > 0 ? NR - 1 : 0) }' "$1"; }

assert_unique_column() {
  local file="$1" column="$2" label="$3"
  awk -F',' -v col="$column" -v label="$label" '
    NR == 1 { next }
    $col == "" { printf "empty %s at line %d\n", label, NR > "/dev/stderr"; exit 1 }
    seen[$col]++ { printf "duplicate %s: %s\n", label, $col > "/dev/stderr"; exit 1 }
  ' "$file" || fail "$label uniqueness validation 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/csf-functions.csv" <<'CSV'
function_code,name,purpose
GOVERN,Gouverner,Définir attentes responsabilités stratégie et surveillance du risque
IDENTIFY,Identifier,Comprendre actifs dépendances vulnérabilités et risques
PROTECT,Protéger,Réduire la vraisemblance ou les conséquences
DETECT,Détecter,Identifier rapidement événements et anomalies
RESPOND,Répondre,Coordonner analyse communication et confinement
RECOVER,Rétablir,Restaurer les capacités et améliorer la résilience
CSV

  cat > "$LAB_ROOT/refs/security-objectives.csv" <<'CSV'
objective_code,name,question
C,Confidentialité,Qui peut accéder ou connaître cette information
I,Intégrité,Comment garantir exactitude complétude et modification autorisée
A,Disponibilité,Quand le service ou la donnée doit rester accessible
CSV

  cat > "$LAB_ROOT/input/assets.csv" <<'CSV'
asset_id,name,type,owner,mission,dependency,confidentiality,integrity,availability
AST-001,ERP commandes,Application,Responsable ventes,Traiter les commandes clients,AST-002,high,high,high
AST-002,Base commandes,Donnée,Responsable IT,Conserver commandes factures et statuts,AST-008,high,high,high
AST-003,Messagerie collaborative,Service,Responsable IT,Communiquer avec clients et fournisseurs,AST-004,high,moderate,high
AST-004,Service identité,Service,Responsable IT,Authentifier utilisateurs et administrateurs,AST-008,high,high,high
AST-005,Dépôt sauvegardes,Infrastructure,Responsable continuité,Restaurer données et configurations,AST-008,high,high,high
AST-006,Portable finance,Équipement,Responsable finance,Préparer paiements et reporting,AST-004,high,high,moderate
AST-007,Site public,Application,Responsable communication,Présenter offres et recevoir contacts,AST-008,low,moderate,moderate
AST-008,Cœur réseau,Infrastructure,Responsable infrastructure,Relier siège agence serveurs et Internet,,moderate,high,high
CSV

  cat > "$LAB_ROOT/input/threats.csv" <<'CSV'
threat_id,name,category,description
THR-001,Rançongiciel,Malware,Chiffrement et possible exfiltration suivis d'une demande de rançon
THR-002,Hameçonnage,Ingénierie_sociale,Message trompeur visant une action ou un secret
THR-003,Abus_de_compte,Identité,Utilisation d'un compte légitime compromis ou excessif
THR-004,Déni_de_service,Disponibilité,Saturation volontaire ou accidentelle d'une capacité
THR-005,Perte_ou_vol,Physique,Disparition d'un équipement contenant des données
THR-006,Erreur_de_configuration,Erreur,Modification incorrecte affectant un service
THR-007,Défiguration,Application,Modification non autorisée du contenu public
CSV

  cat > "$LAB_ROOT/input/vulnerabilities.csv" <<'CSV'
vulnerability_id,asset_id,description,exposure,evidence
VUL-001,AST-001,Administration distante exposée sans restriction suffisante,external,ARCH-REV-001
VUL-002,AST-003,MFA non généralisée pour les comptes sensibles,external,IAM-REV-002
VUL-003,AST-005,Administration des sauvegardes partageant le même domaine de confiance,internal,BKP-REV-003
VUL-004,AST-007,Aucune capacité documentée d'absorption ou de délestage,external,WEB-REV-004
VUL-005,AST-006,Chiffrement du poste non vérifié,physical,ENDPOINT-REV-005
VUL-006,AST-008,Changement réseau sans revue indépendante,internal,NET-REV-006
VUL-007,AST-007,Compte de publication sans revue trimestrielle,external,WEB-REV-007
VUL-008,AST-002,Propriétaire métier du risque non désigné,internal,DATA-REV-008
CSV

  cat > "$LAB_ROOT/input/controls.csv" <<'CSV'
control_id,function_code,description,status,evidence
CTL-001,GOVERN,Registre des risques avec propriétaire et décision,implemented,RISK-REG-001
CTL-002,IDENTIFY,Inventaire des actifs et dépendances,implemented,ASSET-REG-001
CTL-003,PROTECT,MFA pour comptes sensibles,partial,IAM-PLAN-003
CTL-004,PROTECT,Sauvegarde isolée et testée,partial,BKP-PLAN-004
CTL-005,DETECT,Alertes sur authentifications anormales,planned,LOG-PLAN-005
CTL-006,RESPOND,Procédure de signalement et coordination,implemented,IR-PLAY-006
CTL-007,RECOVER,Priorités de restauration documentées,partial,BCP-PLAN-007
CTL-008,PROTECT,Chiffrement des postes sensibles,planned,ENDPOINT-PLAN-008
CTL-009,PROTECT,Revue de changement réseau,implemented,CHG-POL-009
CTL-010,DETECT,Contrôle d'intégrité du site public,planned,WEB-PLAN-010
CSV

  cat > "$LAB_ROOT/input/risk-scenarios.csv" <<'CSV'
risk_id,asset_id,threat_id,vulnerability_id,likelihood,impact_confidentiality,impact_integrity,impact_availability,current_controls,owner,treatment,approval,evidence
RISK-001,AST-001,THR-001,VUL-001,likely,severe,severe,critical,CTL-001+CTL-002+CTL-004,Responsable IT,REDUCE,yes,RISK-WS-001
RISK-002,AST-003,THR-002,VUL-002,likely,high,moderate,high,CTL-001+CTL-003+CTL-005+CTL-006,Responsable IT,REDUCE,yes,RISK-WS-002
RISK-003,AST-005,THR-003,VUL-003,possible,high,severe,severe,CTL-001+CTL-004+CTL-007,Responsable continuité,REDUCE,yes,RISK-WS-003
RISK-004,AST-007,THR-004,VUL-004,possible,low,low,moderate,CTL-001+CTL-002,Responsable communication,TRANSFER,yes,RISK-WS-004
RISK-005,AST-006,THR-005,VUL-005,unlikely,high,high,moderate,CTL-001+CTL-008,Responsable finance,REDUCE,yes,RISK-WS-005
RISK-006,AST-008,THR-006,VUL-006,possible,moderate,severe,severe,CTL-001+CTL-009,Responsable infrastructure,REDUCE,yes,RISK-WS-006
RISK-007,AST-007,THR-007,VUL-007,unlikely,low,moderate,low,CTL-001+CTL-010,Responsable communication,ACCEPT,no,RISK-WS-007
RISK-008,AST-002,THR-003,VUL-008,possible,high,severe,high,CTL-001+CTL-002,,REDUCE,yes,RISK-WS-008
CSV

  cat > "$LAB_ROOT/input/decision-policy.csv" <<'CSV'
rule_code,description
OWNER_REQUIRED,Tout risque possède un propriétaire nommé
EVIDENCE_REQUIRED,Toute évaluation possède une preuve ou un atelier référencé
ACCEPTANCE_APPROVAL,L'acceptation exige une approbation explicite
CRITICAL_REDUCE,Un risque critique ne peut pas être simplement accepté
CONTROL_REFERENCE,Chaque contrôle cité existe dans le catalogue
ASSET_DEPENDENCY,Chaque dépendance d'actif existe dans l'inventaire
CSV

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

Le laboratoire classe des données synthétiques et produit un registre de risques local. Il ne scanne aucun hôte, ne contacte aucun service, ne teste aucun mot de passe, n'exécute aucun code malveillant et ne prouve pas qu'un contrôle existe réellement dans une organisation.
MD

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

  cat > "$LAB_ROOT/README.txt" <<'TXT'
Commands:
  setup     create the synthetic risk-analysis dossier
  status    show source counts and generated outputs
  run       classify assets and evaluate risk scenarios
  validate  verify references decisions proof boundaries and checksums
  reset     delete this marker-protected lab root
TXT

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

show_status() {
  require_marker
  printf 'Assets: %s\n' "$(csv_rows "$LAB_ROOT/input/assets.csv")"
  printf 'Threats: %s\n' "$(csv_rows "$LAB_ROOT/input/threats.csv")"
  printf 'Vulnerabilities: %s\n' "$(csv_rows "$LAB_ROOT/input/vulnerabilities.csv")"
  printf 'Controls: %s\n' "$(csv_rows "$LAB_ROOT/input/controls.csv")"
  printf 'Risk scenarios: %s\n' "$(csv_rows "$LAB_ROOT/input/risk-scenarios.csv")"
  test -f "$LAB_ROOT/output/risk-register.csv" && printf 'Risk register: generated\n' || printf 'Risk register: not generated\n'
}

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

  assert_unique_column "$LAB_ROOT/input/assets.csv" 1 "asset id"
  assert_unique_column "$LAB_ROOT/input/threats.csv" 1 "threat id"
  assert_unique_column "$LAB_ROOT/input/vulnerabilities.csv" 1 "vulnerability id"
  assert_unique_column "$LAB_ROOT/input/controls.csv" 1 "control id"
  assert_unique_column "$LAB_ROOT/input/risk-scenarios.csv" 1 "risk id"

  awk -F',' '
    NR==1 { next }
    { asset[$1]=1; dependency[$1]=$6 }
    END {
      for (a in dependency) if (dependency[a]!="" && !(dependency[a] in asset)) {
        print "unknown asset dependency: " dependency[a] > "/dev/stderr"; exit 1
      }
    }
  ' "$LAB_ROOT/input/assets.csv" || fail "asset dependency validation failed"

  awk -F',' '
    FILENAME==ARGV[1] { if (FNR>1) asset[$1]=1; next }
    FILENAME==ARGV[2] { if (FNR>1) threat[$1]=1; next }
    FILENAME==ARGV[3] { if (FNR>1) vulnerability[$1]=$2; next }
    FILENAME==ARGV[4] { if (FNR>1) control[$1]=1; next }
    FNR==1 { next }
    {
      if (!($2 in asset)) { print "unknown asset: "$2 > "/dev/stderr"; exit 1 }
      if (!($3 in threat)) { print "unknown threat: "$3 > "/dev/stderr"; exit 1 }
      if (!($4 in vulnerability)) { print "unknown vulnerability: "$4 > "/dev/stderr"; exit 1 }
      if (vulnerability[$4]!=$2) { print "vulnerability asset mismatch: "$4 > "/dev/stderr"; exit 1 }
      n=split($9, refs, "+")
      for (i=1;i<=n;i++) if (!(refs[i] in control)) { print "unknown control: "refs[i] > "/dev/stderr"; exit 1 }
    }
  ' "$LAB_ROOT/input/assets.csv" "$LAB_ROOT/input/threats.csv" "$LAB_ROOT/input/vulnerabilities.csv" "$LAB_ROOT/input/controls.csv" "$LAB_ROOT/input/risk-scenarios.csv" || fail "risk reference validation failed"

  awk -F',' -v OFS=',' '
    function sev(v) {
      if (v=="low") return 1
      if (v=="moderate") return 2
      if (v=="high") return 3
      if (v=="severe") return 4
      if (v=="critical") return 5
      return 0
    }
    function label(v) {
      if (v>=5) return "CRITICAL"
      if (v==4) return "SEVERE"
      if (v==3) return "HIGH"
      if (v==2) return "MODERATE"
      return "LOW"
    }
    NR==1 { print "asset_id,name,owner,mission,criticality,dominant_objectives"; next }
    {
      c=sev($7); i=sev($8); a=sev($9); max=c; if(i>max)max=i; if(a>max)max=a
      dominant=""
      if(c==max) dominant="C"
      if(i==max) dominant=(dominant==""?"I":dominant"+I")
      if(a==max) dominant=(dominant==""?"A":dominant"+A")
      print $1,$2,$4,$5,label(max),dominant
    }
  ' "$LAB_ROOT/input/assets.csv" > "$LAB_ROOT/output/asset-criticality.csv"

  awk -F',' -v OFS=',' '
    function likelihood(v) {
      if(v=="rare") return 1
      if(v=="unlikely") return 2
      if(v=="possible") return 3
      if(v=="likely") return 4
      if(v=="almost_certain") return 5
      return 0
    }
    function impact(v) {
      if(v=="low") return 1
      if(v=="moderate") return 2
      if(v=="high") return 3
      if(v=="severe") return 4
      if(v=="critical") return 5
      return 0
    }
    function rating(score) {
      if(score>=15) return "CRITICAL"
      if(score>=9) return "HIGH"
      if(score>=4) return "MEDIUM"
      return "LOW"
    }
    NR==1 { print "risk_id,asset_id,threat_id,vulnerability_id,likelihood_score,impact_score,risk_score,rating,owner,treatment,decision,reasons"; next }
    {
      l=likelihood($5); c=impact($6); i=impact($7); a=impact($8); max=c; if(i>max)max=i; if(a>max)max=a
      score=l*max; level=rating(score); decision="MONITOR"; reasons="NONE"
      if(level=="CRITICAL") decision="REDUCE_NOW"
      else if(level=="HIGH") decision="PLAN_PRIORITY_CONTROL"
      else if(level=="MEDIUM") decision="PLAN_CONTROL"
      if($10=="") { decision="BLOCK_NEEDS_OWNER"; reasons="OWNER_MISSING" }
      if($13=="") { decision="BLOCK_NEEDS_EVIDENCE"; reasons=(reasons=="NONE"?"EVIDENCE_MISSING":reasons"+EVIDENCE_MISSING") }
      if($11=="ACCEPT" && $12!="yes") { decision="BLOCK_ACCEPTANCE_APPROVAL"; reasons=(reasons=="NONE"?"APPROVAL_MISSING":reasons"+APPROVAL_MISSING") }
      if(level=="CRITICAL" && $11=="ACCEPT") { decision="BLOCK_CRITICAL_ACCEPTANCE"; reasons=(reasons=="NONE"?"CRITICAL_RISK":reasons"+CRITICAL_RISK") }
      print $1,$2,$3,$4,l,max,score,level,$10,$11,decision,reasons
    }
  ' "$LAB_ROOT/input/risk-scenarios.csv" > "$LAB_ROOT/output/risk-register.csv"

  awk -F',' -v OFS=',' '
    NR==1 { print "risk_id,priority,owner,treatment,required_action,evidence"; next }
    {
      action="Review and monitor"
      if($11=="REDUCE_NOW") action="Define immediate safeguards and due date"
      else if($11=="PLAN_PRIORITY_CONTROL") action="Approve priority risk treatment plan"
      else if($11=="PLAN_CONTROL") action="Schedule proportionate control"
      else if($11=="BLOCK_NEEDS_OWNER") action="Assign accountable risk owner"
      else if($11=="BLOCK_ACCEPTANCE_APPROVAL") action="Obtain explicit acceptance approval or reduce risk"
      else if($11 ~ /^BLOCK_/) action="Resolve governance blocker"
      print $1,$8,$9,$10,action,"Treatment ticket plus verification evidence"
    }
  ' "$LAB_ROOT/output/risk-register.csv" > "$LAB_ROOT/output/treatment-plan.csv"

  awk -F',' -v OFS=',' '
    NR==FNR { if(FNR>1) { known[$1]=1; name[$1]=$2 } next }
    FNR>1 { count[$2]++; if($4=="implemented") implemented[$2]++; if($4=="partial") partial[$2]++; if($4=="planned") planned[$2]++ }
    END {
      print "function_code,name,total_controls,implemented,partial,planned"
      for (f in known) print f,name[f],count[f]+0,implemented[f]+0,partial[f]+0,planned[f]+0
    }
  ' "$LAB_ROOT/refs/csf-functions.csv" "$LAB_ROOT/input/controls.csv" > "$LAB_ROOT/output/csf-coverage.unsorted"
  {
    head -n 1 "$LAB_ROOT/output/csf-coverage.unsorted"
    tail -n +2 "$LAB_ROOT/output/csf-coverage.unsorted" | sort
  } > "$LAB_ROOT/output/csf-coverage.csv"
  rm -f "$LAB_ROOT/output/csf-coverage.unsorted"

  critical=$(awk -F',' 'NR>1 && $8=="CRITICAL" {c++} END{print c+0}' "$LAB_ROOT/output/risk-register.csv")
  high=$(awk -F',' 'NR>1 && $8=="HIGH" {c++} END{print c+0}' "$LAB_ROOT/output/risk-register.csv")
  blockers=$(awk -F',' 'NR>1 && $11 ~ /^BLOCK_/ {c++} END{print c+0}' "$LAB_ROOT/output/risk-register.csv")

  {
    printf '# Synthèse du registre de risques\n\n'
    printf -- '- actifs évalués : %s\n' "$(csv_rows "$LAB_ROOT/input/assets.csv")"
    printf -- '- scénarios évalués : %s\n' "$(csv_rows "$LAB_ROOT/input/risk-scenarios.csv")"
    printf -- '- risques critiques : %s\n' "$critical"
    printf -- '- risques élevés : %s\n' "$high"
    printf -- '- décisions bloquées par gouvernance : %s\n\n' "$blockers"
    printf 'Le score sert à prioriser une discussion documentée. Il ne remplace ni le jugement métier ni une méthode complète comme EBIOS Risk Manager.\n\n'
    cat "$LAB_ROOT/input/proof-boundary.md"
  } > "$LAB_ROOT/evidence/risk-summary.md"

  (
    cd "$LAB_ROOT"
    sha256sum refs/*.csv input/*.csv input/*.md > evidence/source-checksums-after.sha256
    sha256sum output/*.csv evidence/risk-summary.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 fixtures changed during run"

  printf 'Reference asset and risk evaluation completed.\n'
}

validate_reference() {
  require_marker
  for file in \
    output/asset-criticality.csv output/risk-register.csv output/treatment-plan.csv \
    output/csf-coverage.csv evidence/risk-summary.md \
    evidence/source-checksums-before.sha256 evidence/source-checksums-after.sha256 \
    evidence/output-checksums.sha256 evidence/source-immutability.diff
  do
    [ -f "$LAB_ROOT/$file" ] || fail "missing output: $file"
  done

  [ "$(csv_rows "$LAB_ROOT/input/assets.csv")" -eq 8 ] || fail "expected 8 assets"
  [ "$(csv_rows "$LAB_ROOT/input/risk-scenarios.csv")" -eq 8 ] || fail "expected 8 risk scenarios"
  [ "$(csv_rows "$LAB_ROOT/input/controls.csv")" -eq 10 ] || fail "expected 10 controls"
  [ "$(csv_rows "$LAB_ROOT/output/asset-criticality.csv")" -eq 8 ] || fail "expected 8 classified assets"
  [ "$(csv_rows "$LAB_ROOT/output/risk-register.csv")" -eq 8 ] || fail "expected 8 evaluated risks"
  [ "$(csv_rows "$LAB_ROOT/output/csf-coverage.csv")" -eq 6 ] || fail "expected six CSF functions"

  grep -Fq 'AST-004,Service identité,Responsable IT,Authentifier utilisateurs et administrateurs,HIGH,C+I+A' "$LAB_ROOT/output/asset-criticality.csv" || fail "identity asset criticality missing"
  grep -Fq 'RISK-001,AST-001,THR-001,VUL-001,4,5,20,CRITICAL,Responsable IT,REDUCE,REDUCE_NOW,NONE' "$LAB_ROOT/output/risk-register.csv" || fail "critical ransomware risk missing"
  grep -Fq 'RISK-002,AST-003,THR-002,VUL-002,4,3,12,HIGH,Responsable IT,REDUCE,PLAN_PRIORITY_CONTROL,NONE' "$LAB_ROOT/output/risk-register.csv" || fail "phishing risk missing"
  grep -Fq 'RISK-007,AST-007,THR-007,VUL-007,2,2,4,MEDIUM,Responsable communication,ACCEPT,BLOCK_ACCEPTANCE_APPROVAL,APPROVAL_MISSING' "$LAB_ROOT/output/risk-register.csv" || fail "unapproved acceptance blocker missing"
  grep -Fq 'RISK-008,AST-002,THR-003,VUL-008,3,4,12,HIGH,,REDUCE,BLOCK_NEEDS_OWNER,OWNER_MISSING' "$LAB_ROOT/output/risk-register.csv" || fail "missing owner blocker missing"
  [ "$(awk -F',' 'NR>1 && $8=="CRITICAL" {c++} END{print c+0}' "$LAB_ROOT/output/risk-register.csv")" -eq 1 ] || fail "expected one critical risk"
  [ "$(awk -F',' 'NR>1 && $11 ~ /^BLOCK_/ {c++} END{print c+0}' "$LAB_ROOT/output/risk-register.csv")" -eq 2 ] || fail "expected two governance blockers"
  grep -Fq 'GOVERN,Gouverner,1,1,0,0' "$LAB_ROOT/output/csf-coverage.csv" || fail "Govern coverage missing"
  grep -Fq 'RECOVER,Rétablir,1,0,1,0' "$LAB_ROOT/output/csf-coverage.csv" || fail "Recover coverage missing"
  grep -Fq 'ne scanne aucun hôte' "$LAB_ROOT/evidence/risk-summary.md" || fail "proof boundary missing"

  [ ! -s "$LAB_ROOT/evidence/source-immutability.diff" ] || fail "source immutability diff is not empty"
  (
    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='source-checksums-*' --exclude='output-checksums.sha256' '(password[[:space:]]*=|token[[:space:]]*=|private[_-]?key|https?://[^/[:space:]]+@)' "$LAB_ROOT" >/dev/null || fail "credential-like value detected"

  printf 'Validation successful: assets, CIA impacts, risk scenarios, treatments, blockers and CSF coverage are correct.\n'
}

reset_lab() {
  require_marker
  rm -rf "$LAB_ROOT"
  printf 'Cybersecurity risk 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
