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

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FIXTURES="$HERE/fixtures/lot6"
MARKER=".soria-ir-final-project"
SAFETY="SIMULATION_ONLY_NO_LIVE_RESPONSE"
DECISION="RESPONSE_READY_WITH_ACTIONS"

die(){ echo "ERROR: $*" >&2; exit 1; }

safe_root(){
  local r="${1:-}"
  [[ "$r" = /* ]] || die "root must be absolute"
  case "$r" in
    /tmp/soria-ir-final-project-*|"$HOME"/soria-ir-final-project-*) ;;
    *) die "unsafe root: $r" ;;
  esac
}
marked(){ [[ -f "$1/$MARKER" ]] || die "missing safety marker"; }
file_count(){ find "$1" -type f | wc -l | tr -d ' '; }

validate_bundle(){
  local s="$1"
  [[ -d "$s" ]] || return 2
  [[ "$(file_count "$s")" -eq 24 ]] || return 3
  while IFS= read -r f; do grep -Fq 'SYNTHETIC' "$f" || return 4; done < <(find "$s" -type f | sort)

  awk -F, 'NR==1{next} {n++; for(i=1;i<=7;i++) if($i=="") exit 2; if($1!="CASE-IR-006"||$4!="MONITORING") exit 3} END{if(n!=1) exit 4}' "$s/context/case.csv" || return 10
  awk -F, 'NR>1{for(i=1;i<=5;i++) if($i=="") exit 2; a[$1]++} END{for(x in a) if(a[x]!=1) exit 3}' "$s/context/assets.csv" || return 11
  awk -F, '
    FILENAME==ARGV[1]&&FNR>1{a[$1]=1;next}
    FILENAME==ARGV[2]&&FNR>1{
      for(i=1;i<=7;i++) if($i=="") exit 2
      if(!a[$2]) exit 3
      if($6!="AVAILABLE"&&$6!="GAP") exit 4
      if($6=="AVAILABLE"&&$5!="HIGH"&&$5!="MEDIUM"&&$5!="LOW") exit 5
      if($6=="GAP"&&$5!="NONE") exit 6
      src[$1]=$6
    }
    END{if(length(src)<5) exit 7}
  ' "$s/context/assets.csv" "$s/telemetry/sources.csv" || return 12
  awk -F, '
    FILENAME==ARGV[1]&&FNR>1{src[$1]=$6;next}
    FILENAME==ARGV[2]&&FNR>1{
      for(i=1;i<=7;i++) if($i=="") exit 2
      if(!($2 in src)||src[$2]!="GAP") exit 3
    }
  ' "$s/telemetry/sources.csv" "$s/telemetry/gaps.csv" || return 13
  awk -F, '
    FILENAME==ARGV[1]&&FNR>1{src[$1]=$6;next}
    FILENAME==ARGV[2]&&FNR>1{
      for(i=1;i<=6;i++) if($i=="") exit 2
      if(!($2 in src)||src[$2]!="AVAILABLE"||$4!="true") exit 3
      ev[$1]=1
    }
    FILENAME==ARGV[3]&&FNR>1{
      for(i=1;i<=4;i++) if($i=="") exit 4
      if(!ev[$1]||$3!="SHA256"||length($2)!=64||$2 !~ /^[0-9a-f]+$/) exit 5
      h[$1]=1
    }
    END{for(e in ev) if(!h[e]) exit 6}
  ' "$s/telemetry/sources.csv" "$s/evidence/originals.csv" "$s/evidence/hashes.csv" || return 14
  awk -F, '
    FILENAME==ARGV[1]&&FNR>1{src[$1]=1;next}
    FILENAME==ARGV[2]&&FNR>1{a[$1]=1;next}
    FILENAME==ARGV[3]&&FNR>1{
      for(i=1;i<=8;i++) if($i=="") exit 2
      if(!src[$3]||!a[$4]) exit 3
      if(last!=""&&$2<last) exit 4
      last=$2
    }
  ' "$s/telemetry/sources.csv" "$s/context/assets.csv" "$s/timeline/events.csv" || return 15
  awk -F, '
    FILENAME==ARGV[1]&&FNR>1{src[$1]=$6;next}
    FILENAME==ARGV[2]&&FNR>1{
      for(i=1;i<=7;i++) if($i=="") exit 2
      if(!($4 in src)) exit 3
      if(src[$4]=="GAP"&&$5!="NOT_TESTABLE") exit 4
      if(src[$4]=="AVAILABLE"&&$5!="TESTED") exit 5
      det[$1]=1
    }
    FILENAME==ARGV[3]&&FNR>1{
      for(i=1;i<=6;i++) if($i=="") exit 6
      if(!det[$2]) exit 7
      if($3=="positive"&&$2!="DET-003"&&$4!="PASS") exit 8
      if($3=="negative"&&$4!="BLOCKED") exit 9
      if($2=="DET-003"&&$4!="NOT_TESTABLE") exit 10
      seen[$2]++
    }
    END{for(d in det) if(!seen[d]) exit 11}
  ' "$s/telemetry/sources.csv" "$s/detection/detections.csv" "$s/detection/test-results.csv" || return 16
  awk -F, '
    FILENAME==ARGV[1]&&FNR>1{d[$1]=1;next}
    FILENAME==ARGV[2]&&FNR>1{a[$1]=1;next}
    FILENAME==ARGV[3]&&FNR>1{
      for(i=1;i<=9;i++) if($i=="") exit 2
      if(!d[$2]||!a[$3]) exit 3
      if($6!="P1"&&$6!="P2"&&$6!="P3") exit 4
    }
  ' "$s/detection/detections.csv" "$s/context/assets.csv" "$s/triage/alerts.csv" || return 17
  awk -F, '
    FILENAME==ARGV[1]&&FNR>1{ev[$1]=1;next}
    FILENAME==ARGV[2]&&FNR>1{
      for(i=1;i<=6;i++) if($i=="") exit 2
      if($3!="SUPPORTED"&&$3!="REFUTED"&&$3!="INCONCLUSIVE") exit 3
      hyp[$1]=$3
      next
    }
    FILENAME==ARGV[3]&&FNR>1{
      for(i=1;i<=7;i++) if($i=="") exit 4
      if(!($2 in hyp)||!ev[$3]) exit 5
      if($6!="SUPPORTED"&&$6!="REFUTED") exit 6
      find[$2]++
    }
    END{
      for(h in hyp) if(hyp[h]=="SUPPORTED"&&!find[h]) exit 7
      for(h in hyp) if(hyp[h]=="INCONCLUSIVE"&&find[h]) exit 8
    }
  ' "$s/evidence/originals.csv" "$s/investigation/hypotheses.csv" "$s/investigation/findings.csv" || return 18
  awk -F, '
    FILENAME==ARGV[1]&&FNR>1{a[$1]=1;next}
    FILENAME==ARGV[2]&&FNR>1{f[$1]=1;next}
    FILENAME==ARGV[3]&&FNR>1{
      for(i=1;i<=9;i++) if($i=="") exit 2
      if(!a[$2]||!f[$5]) exit 3
      if($7!="APPROVED_FOR_SIMULATION"&&$7!="DEFERRED_PENDING_REAL_EVIDENCE") exit 4
      if($8!="NOT_EXECUTED") exit 5
    }
    FILENAME==ARGV[4]&&FNR>1{
      for(i=1;i<=9;i++) if($i=="") exit 6
      if($5==""||$6==""||$8!="PLANNED") exit 7
    }
  ' "$s/context/assets.csv" "$s/investigation/findings.csv" "$s/response/containment-options.csv" "$s/response/eradication-plan.csv" || return 19
  awk -F, '
    FILENAME==ARGV[1]&&FNR>1{a[$1]=1;next}
    FILENAME==ARGV[2]&&FNR>1{src[$1]=$6;next}
    FILENAME==ARGV[3]&&FNR>1{
      for(i=1;i<=6;i++) if($i=="") exit 2
      if(!a[$2]||!($4 in src)) exit 3
      if(src[$4]=="GAP"&&$5!="NOT_TESTABLE") exit 4
      if(src[$4]=="AVAILABLE"&&$5!="PASS") exit 5
      trust[$2]=$5
    }
    FILENAME==ARGV[4]&&FNR>1{
      for(i=1;i<=7;i++) if($i=="") exit 6
      if(!a[$2]) exit 7
      if($5=="PASS"&&trust[$2]!="PASS"&&$2!="AST-DB-01") exit 8
    }
    FILENAME==ARGV[5]&&FNR>1{
      for(i=1;i<=7;i++) if($i=="") exit 9
      if(!($2 in src)) exit 10
      if(src[$2]=="GAP"&&$5!="NOT_TESTABLE") exit 11
      if($5!="PASS"&&$5!="WATCH"&&$5!="NOT_TESTABLE") exit 12
    }
  ' "$s/context/assets.csv" "$s/telemetry/sources.csv" "$s/recovery/trusted-state.csv" "$s/recovery/recovery-tests.csv" "$s/recovery/post-monitoring.csv" || return 20
  awk -F, '
    FILENAME==ARGV[1]&&FNR>1{r[$1]=1;next}
    FILENAME==ARGV[2]&&FNR>1{
      for(i=1;i<=7;i++) if($i=="") exit 2
      if(!r[$2]||!r[$3]) exit 3
    }
    FILENAME==ARGV[3]&&FNR>1{
      for(i=1;i<=7;i++) if($i=="") exit 4
      if(!r[$4]) exit 5
      if($6=="EXTERNAL"&&$5=="SENT") exit 6
    }
  ' "$s/coordination/roles.csv" "$s/coordination/handoffs.csv" "$s/coordination/communications.csv" || return 21
  awk -F, 'NR>1{for(i=1;i<=7;i++) if($i=="") exit 2; if($6!="OPEN"&&$6!="PLANNED") exit 3}' "$s/learning/actions.csv" || return 22
  awk -F, '
    FILENAME==ARGV[1]&&FNR>1{
      for(i=1;i<=6;i++) if($i=="") exit 2
      if($3!="true"&&$3!="false") exit 3
      if($4=="REAL"&&$3=="true"&&$5!="REAL_EVIDENCE_REQUIRED") exit 4
      if($4=="SYNTHETIC"&&$3=="true"&&$5!="MET") exit 5
    }
    FILENAME==ARGV[2]&&FNR>1{for(i=1;i<=4;i++) if($i=="") exit 6}
    FILENAME==ARGV[3]&&FNR>1{
      for(i=1;i<=6;i++) if($i=="") exit 7
      if($5!="OPEN") exit 8
      real++
    }
    END{if(real<1) exit 9}
  ' "$s/assurance/closure-criteria.csv" "$s/assurance/proof-boundaries.csv" "$s/assurance/real-evidence-required.csv" || return 23
}

mutate_field(){
  local file="$1" row="$2" col="$3" value="$4"
  awk -F, -v OFS=, -v r="$row" -v c="$col" -v v="$value" 'NR==r{$c=v} {print}' "$file" > "$file.tmp"
  mv "$file.tmp" "$file"
}

run_negative(){
  local root="$1" id="$2" reason="$3" file="$4" row="$5" col="$6" value="$7" report="$8"
  local d="$root/negative/$id"
  rm -rf "$d"; mkdir -p "$d"; cp -a "$root/source/." "$d/"
  mutate_field "$d/$file" "$row" "$col" "$value"
  if validate_bundle "$d" >/dev/null 2>&1; then
    printf '%s,%s,%s,%s\n' "$id" "$reason" "validator-accepted-invalid-bundle" "FAILED" >> "$report"
    return 1
  fi
  printf '%s,%s,%s,%s\n' "$id" "$reason" "bundle-rejected-as-expected" "BLOCKED" >> "$report"
}

generate_outputs(){
  local r="$1"
  local s="$r/source"
  local o="$r/output"
  rm -rf "$o" "$r/negative"; mkdir -p "$o" "$r/negative"
  printf 'path,sha256\n' > "$o/source-manifest.csv"
  (cd "$s"; find . -type f -print | sort | sed 's#^\./##' | while read -r f; do printf '%s,%s\n' "$f" "$(sha256sum "$f" | awk '{print $1}')"; done) >> "$o/source-manifest.csv"
  cp "$s/telemetry/sources.csv" "$o/telemetry-coverage.csv"
  cp "$s/timeline/events.csv" "$o/timeline.csv"
  cp "$s/detection/detections.csv" "$o/detection-summary.csv"
  cp "$s/triage/alerts.csv" "$o/triage-summary.csv"
  cp "$s/investigation/hypotheses.csv" "$o/hypothesis-register.csv"
  cp "$s/investigation/findings.csv" "$o/evidence-register.csv"
  cp "$s/response/containment-options.csv" "$o/containment-decision.csv"
  cp "$s/response/eradication-plan.csv" "$o/eradication-review.csv"
  cp "$s/recovery/recovery-tests.csv" "$o/recovery-review.csv"
  cp "$s/recovery/post-monitoring.csv" "$o/monitoring-review.csv"
  cp "$s/coordination/handoffs.csv" "$o/coordination-review.csv"
  cp "$s/learning/actions.csv" "$o/learning-backlog.csv"
  cp "$s/assurance/closure-criteria.csv" "$o/closure-review.csv"

  printf 'case_id,expected_reason,observed_reason,result\n' > "$o/negative-test-report.csv"
  run_negative "$r" N01 case-owner-missing context/case.csv 2 5 "" "$o/negative-test-report.csv"
  run_negative "$r" N02 unknown-asset-reference telemetry/sources.csv 2 2 AST-UNKNOWN "$o/negative-test-report.csv"
  run_negative "$r" N03 source-provenance-missing telemetry/sources.csv 2 4 "" "$o/negative-test-report.csv"
  run_negative "$r" N04 evidence-hash-invalid evidence/hashes.csv 2 2 bad "$o/negative-test-report.csv"
  run_negative "$r" N05 timeline-out-of-order timeline/events.csv 3 2 2026-10-03T07:00:00Z "$o/negative-test-report.csv"
  run_negative "$r" N06 detection-version-missing detection/detections.csv 2 2 "" "$o/negative-test-report.csv"
  run_negative "$r" N07 mandatory-test-not-passing detection/test-results.csv 2 4 FAIL "$o/negative-test-report.csv"
  run_negative "$r" N08 alert-unknown-detection triage/alerts.csv 2 2 DET-UNKNOWN "$o/negative-test-report.csv"
  run_negative "$r" N09 supported-hypothesis-without-finding investigation/findings.csv 2 2 HYP-UNKNOWN "$o/negative-test-report.csv"
  run_negative "$r" N10 finding-without-evidence investigation/findings.csv 2 3 EV-UNKNOWN "$o/negative-test-report.csv"
  run_negative "$r" N11 containment-owner-missing response/containment-options.csv 2 6 "" "$o/negative-test-report.csv"
  run_negative "$r" N12 containment-marked-executed response/containment-options.csv 2 8 EXECUTED "$o/negative-test-report.csv"
  run_negative "$r" N13 eradication-rollback-missing response/eradication-plan.csv 2 6 "" "$o/negative-test-report.csv"
  run_negative "$r" N14 recovery-pass-without-trusted-state recovery/trusted-state.csv 2 5 NOT_TESTABLE "$o/negative-test-report.csv"
  run_negative "$r" N15 closure-despite-monitoring-gap recovery/post-monitoring.csv 4 5 PASS "$o/negative-test-report.csv"
  run_negative "$r" N16 external-message-sent coordination/communications.csv 4 5 SENT "$o/negative-test-report.csv"
  run_negative "$r" N17 retex-measure-missing learning/actions.csv 2 5 "" "$o/negative-test-report.csv"
  run_negative "$r" N18 forced-closure-with-real-evidence-missing assurance/closure-criteria.csv 5 5 MET "$o/negative-test-report.csv"

  {
    echo 'criterion,status,reason'
    echo 'synthetic-corpus,PASS,all-24-fixtures-coherent'
    echo 'negative-families,PASS,all-18-invalid-variants-blocked'
    echo 'live-response,NOT_EXECUTED,simulation-only'
    echo 'real-evidence,OPEN,authorized-observation-required'
    echo 'case-state,MONITORING,closure-criteria-not-met'
  } > "$o/decision-rationale.csv"
  {
    echo 'case_id=CASE-IR-006'
    echo 'case_state=MONITORING'
    echo "final_decision=$DECISION"
    echo "safety=$SAFETY"
  } > "$o/final-decision.txt"
  {
    echo '# SORIA Distribution — dossier final de réponse à incident'
    echo
    echo '> SYNTHETIC — training evidence only.'
    echo
    echo '## Situation'
    echo 'CASE-IR-006 remains MONITORING. Available synthetic evidence supports privileged-identity misuse and admin-segment reconnaissance; endpoint process validation remains unavailable.'
    echo
    echo '## Detection and investigation'
    echo 'Two tested detections and two triaged alerts are represented. HYP-003 remains INCONCLUSIVE because endpoint-edr is a declared gap.'
    echo
    echo '## Response'
    echo 'Containment options are decisions for simulation only. Every action remains NOT_EXECUTED. Eradication is PLANNED with exit criteria and rollback.'
    echo
    echo '## Recovery'
    echo 'Application, identity, network and data-path functional tests are represented as PASS. Endpoint health remains NOT_TESTABLE and post-incident monitoring contains WATCH/NOT_TESTABLE states.'
    echo
    echo '## Coordination'
    echo 'Internal handoffs are represented. External customer communication remains WITHHELD and requires an authorized human decision.'
    echo
    echo '## Improvement'
    echo 'RETEX actions remain OPEN or PLANNED with owners, due dates and measures.'
    echo
    echo '## Decision'
    echo "$DECISION"
    echo
    echo 'The dossier is structurally coherent for training, but real evidence and live authorized validation remain required before any real incident can be closed.'
  } > "$o/final-dossier.md"
  {
    echo '# Proof boundaries'
    echo '- CI proves deterministic coherence of synthetic fixtures only.'
    echo '- No live containment, eradication, recovery, credential action or external communication was executed.'
    echo '- Synthetic evidence is not a legal chain of custody.'
    echo '- Correlation or IOC context is not independent proof of compromise.'
    echo '- Real closure requires authorized observation and human ownership.'
  } > "$o/proof-boundaries.md"
  {
    echo 'case=CASE-IR-006'
    echo 'fixtures=24'
    echo 'negative_families=18'
    echo 'blocked=18'
    echo 'case_state=MONITORING'
    echo "decision=$DECISION"
    echo "safety=$SAFETY"
  } > "$o/validation-summary.txt"
}

cmd="${1:-}"
root="${2:-}"
safe_root "$root"
case "$cmd" in
  setup)
    [[ ! -e "$root" ]] || die "root already exists"
    mkdir -p "$root/source"
    cp -a "$FIXTURES/." "$root/source/"
    printf '%s\n' "$MARKER" > "$root/$MARKER"
    printf '%s\n' "$SAFETY" > "$root/.safety-boundary"
    validate_bundle "$root/source" || die "reference fixtures are invalid"
    echo "setup OK: $root"
    ;;
  status)
    marked "$root"
    [[ -f "$root/.safety-boundary" ]] || die "missing safety boundary"
    validate_bundle "$root/source" || die "source validation failed"
    echo "status=READY fixtures=24 safety=$SAFETY"
    ;;
  run)
    marked "$root"
    validate_bundle "$root/source" || die "source validation failed"
    generate_outputs "$root"
    echo "run OK: $DECISION"
    ;;
  validate)
    marked "$root"
    validate_bundle "$root/source" || die "source validation failed"
    [[ -d "$root/output" ]] || die "run output missing"
    while IFS=, read -r path digest; do
      [[ "$path" = path ]] && continue
      [[ -f "$root/source/$path" ]] || die "manifest missing source file: $path"
      [[ "$(sha256sum "$root/source/$path" | awk '{print $1}')" = "$digest" ]] || die "manifest digest mismatch: $path"
    done < "$root/output/source-manifest.csv"
    [[ "$(tail -n +2 "$root/output/negative-test-report.csv" | wc -l | tr -d ' ')" -eq 18 ]] || die "expected 18 negative families"
    [[ "$(awk -F, 'NR>1&&$4=="BLOCKED"{n++} END{print n+0}' "$root/output/negative-test-report.csv")" -eq 18 ]] || die "all negative families must be BLOCKED"
    grep -Fxq 'case_state=MONITORING' "$root/output/final-decision.txt"
    grep -Fxq "final_decision=$DECISION" "$root/output/final-decision.txt"
    grep -Fxq "safety=$SAFETY" "$root/output/final-decision.txt"
    grep -Fq 'HYP-003 remains INCONCLUSIVE' "$root/output/final-dossier.md"
    grep -Fq 'External customer communication remains WITHHELD' "$root/output/final-dossier.md"
    echo "validate PASS: $DECISION"
    ;;
  reset)
    marked "$root"
    rm -rf -- "$root"
    echo "reset OK"
    ;;
  *) die "usage: $0 {setup|status|run|validate|reset} <root>" ;;
esac
