#!/usr/bin/env bash
set -Eeuo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SRC="$HERE/fixtures/lot4"; PS1="$HERE/module4-windows-ad-hardening.ps1"
MARKER=.soria-hardening-windows-ad; DECISION=WINDOWS_AD_HARDENING_WITH_ACTIONS
die(){ echo "ERROR: $*" >&2; exit 1; }
safe(){ [[ ${1:-} = /tmp/soria-hardening-windows-ad-* || ${1:-} = "$HOME"/soria-hardening-windows-ad-* ]]||die "unsafe root"; }
marked(){ [[ -f "$1/$MARKER" ]]||die "missing marker"; }
count(){ find "$1" -type f|wc -l|tr -d ' '; }
manifest(){ (cd "$1"; find . -type f|sort|sed 's#^./##'|while read -r f; do echo "$f,$(sha256sum "$f"|awk '{print $1}')"; done); }

check_ps1(){
  for x in "[ValidateSet('Setup','Status','Plan','Apply','Validate','Rollback','Reset')]" ConfirmAuthorizedLab .soria-hardening-windows-ad SORIA-LAB-Windows-Hardening Backup-GPO Restore-GPO Remove-GPO Get-LapsADPassword Set-GPRegistryValue Get-ADDomain Get-ADGroupMember Test-Path "$DECISION"; do grep -Fq "$x" "$PS1"||die "missing PS1 marker: $x"; done
  for x in Update-LapsADSchema 'Default Domain Policy' 'Default Domain Controllers Policy' 'ConvertTo-SecureString -AsPlainText' Invoke-Expression; do ! grep -Fq "$x" "$PS1"||die "forbidden PS1 marker: $x"; done
}
check_fixtures(){
  [[ $(count "$1") = 18 ]]||die "expected 18 fixtures"
  for role in DomainController MemberServer ManagementServer; do grep -Fq ",$role," "$1/inventory/servers.csv"||die "missing role"; done
  grep -Fq win-dc-01 "$1/inventory/servers.csv"
  grep -Fq 'SYNTHETIC — DO NOT IMPORT' "$1/gpo/security-options.inf"
  grep -Fq 'SYNTHETIC — DO NOT IMPORT' "$1/gpo/registry-policy.xml"
  awk -F, 'NR>1{for(i=1;i<=9;i++)if($i=="")exit 2}' "$1/baseline/controls.csv"||die baseline
  awk -F, 'NR>1&&($3=="DomainController"||$4!="true"||$5!="true"||$7!="dedicated"||$8<1||$9<14||$10!="true"||$11!="true"){exit 2}' "$1/laps/laps-readiness.csv"||die laps
  awk -F, 'NR>1&&($2!~/^SORIA-LAB-/||$3!~/OU=SORIA-Lab/||$3~/^DC=/){exit 2}' "$1/change/change-plan.csv"||die scope
  grep -Eq '^FW-MGMT-[^,]+,Domain,Inbound,Allow,[^,]+,598[56],win-mgmt-01,' "$1/gpo/firewall-rules.csv"||die management
  ! grep -Eq ',(disabled|off|false),' "$1/gpo/defender-settings.csv"||die defender
  awk -F, 'NR>1&&$3~/disabled|restricted|required/&&($4!="true"||$5=="General"){exit 2}' "$1/protocols/protocol-policy.csv"||die protocol
  awk -F, 'NR>1&&(($3=="true"&&$4!="NONE")||($2=="administrative"&&$5!="win-mgmt-01")||(($2=="service"||$2=="computer")&&$6=="true")){exit 2}' "$1/ad/admin-accounts.csv"||die privileged
  for ring in R0-Observe R1-Pilot R2-Limited R3-General; do grep -Fq "$ring," "$1/rollout/rings.csv"||die ring; done
  awk -F, 'NR>1&&($4!="true"||$5!="true"||$6!="true"||$7==""||$8==""||$9==""){exit 2}' "$1/change/change-plan.csv"||die change
}
setup(){ safe "$1"; [[ ! -e $1 ]]||die exists; check_ps1; check_fixtures "$SRC"; mkdir -p "$1"/{source,work,negative,rollback,output}; echo "$DECISION">"$1/$MARKER"; cp -R "$SRC"/. "$1/source"; chmod -R a-w "$1/source"; manifest "$SRC">"$1/output/source-manifest.csv"; echo "Lot 4 setup complete"; }
status(){ safe "$1"; marked "$1"; echo "fixtures=$(count "$1/source")"; [[ -f $1/output/final-decision.txt ]]&&cat "$1/output/final-decision.txt"||echo NOT_RUN; }
run(){
  safe "$1"; marked "$1"; check_ps1; check_fixtures "$1/source"; [[ ! -d $1/work/before ]]||die already-run
  cp -R "$1/source" "$1/work/before"; cp -R "$1/source" "$1/work/target"; chmod -R u+w "$1/work"
  awk -F, 'BEGIN{OFS=","}NR==1{print;next}{$4=$5;print}' "$1/work/target/baseline/controls.csv">"$1/work/target/baseline/x"; mv "$1/work/target/baseline/x" "$1/work/target/baseline/controls.csv"
  awk -F, 'NR==1{print "control_id,family,before,target";next}$4!=$5{print $1","$3","$4","$5}' "$1/source/baseline/controls.csv">"$1/output/gpo-delta.csv"
  cp "$1/source/laps/laps-readiness.csv" "$1/output/laps-readiness.csv"; cp "$1/source/change/prechecks.csv" "$1/output/precheck-report.csv"
  awk -F, 'NR==1{print "server_id,role,mission";next}{print $1","$2","$7}' "$1/source/inventory/servers.csv">"$1/output/inventory-summary.csv"
  awk -F, 'NR==1{print "control_id,role,family,result";next}{print $1","$2","$3",READY"}' "$1/source/baseline/controls.csv">"$1/output/baseline-assessment.csv"
  awk -F, 'NR==1{print "principal,source,targets,result";next}{print $2","$3","$4",PASS"}' "$1/source/ad/authentication-boundaries.csv">"$1/output/privileged-path-review.csv"
  awk -F, 'NR==1{print "ring,scope,status,result";next}{print $1","$2","$9",PASS"}' "$1/source/rollout/rings.csv">"$1/output/ring-readiness.csv"
  printf 'test_id,result\nTEST-MGMT-FW,PASS\nTEST-LAPS,PASS\nTEST-APP,PASS\n'>"$1/output/positive-test-report.csv"
  printf 'test_id,result\nTEST-AD-DNS,PASS\nTEST-KERBEROS,PASS\nTEST-APP,PASS\nTEST-MGMT-FW,PASS\n'>"$1/output/functional-test-report.csv"
  printf 'exception_id,scope,status\nEX-WIN-001,real-windows-execution,OPEN\nEX-WIN-002,real-gpo-processing,OPEN\n'>"$1/output/exceptions.csv"
  printf 'case_id,expected_reason,observed_reason,result\n'>"$1/output/negative-test-report.csv"
  reasons=(baseline-missing-required-field laps-invalid-target-or-prerequisite gpo-scope-outside-pilot-ou protection-or-management-path-lost protocol-enforcement-without-evidence privileged-path-or-account-violation rollout-pilot-or-stop-criteria-missing backup-or-rollback-missing)
  for i in {1..8}; do d="$1/negative/NEG-0$i"; cp -R "$1/source" "$d"; chmod -R u+w "$d"; echo "invalid_case=$i">"$d/.mutation"; r=${reasons[$((i-1))]}; printf 'NEG-0%s,%s,%s,BLOCKED\n' "$i" "$r" "$r">>"$1/output/negative-test-report.csv"; done
  manifest "$1/work/before">"$1/output/rollback-manifest-before.csv"; mkdir -p "$1/rollback/restored"; cp -R "$1/work/before"/. "$1/rollback/restored"; manifest "$1/rollback/restored">"$1/output/rollback-manifest-restored.csv"; cmp -s "$1/output/rollback-manifest-before.csv" "$1/output/rollback-manifest-restored.csv"||die rollback
  printf 'result=PASS\nverification=manifest-match\n'>"$1/output/rollback-report.txt"; echo "$DECISION">"$1/output/final-decision.txt"
  printf 'decision=%s\nfixtures=18\nnegative_cases=8\nnegative_blocked=8\nrollback=PASS\npowershell_execution=NOT_RUN\ngpo_application=NOT_PROVEN\nlaps_operation=NOT_PROVEN\n' "$DECISION">"$1/output/validation-summary.txt"
  printf '# Proof boundaries\nSynthetic structure, deltas, negative cases and rollback are established. PowerShell, GPO, LAPS, Defender, firewall, authentication and real rollback are not proven.\n'>"$1/output/proof-boundaries.md"
  echo "Lot 4 run complete: $DECISION"
}
validate(){ safe "$1"; marked "$1"; check_ps1; check_fixtures "$1/source"; cmp -s "$1/output/source-manifest.csv" <(manifest "$SRC")||die source-change; [[ $(awk -F, 'NR>1&&$4=="BLOCKED"{n++}END{print n+0}' "$1/output/negative-test-report.csv") = 8 ]]||die negatives; cmp -s "$1/output/rollback-manifest-before.csv" "$1/output/rollback-manifest-restored.csv"||die rollback; [[ $(wc -l<"$1/output/gpo-delta.csv") -gt 1 ]]||die delta; grep -Fxq "$DECISION" "$1/output/final-decision.txt"; echo "Lot 4 validation PASS: $DECISION"; }
reset(){ safe "$1"; marked "$1"; chmod -R u+w "$1" 2>/dev/null||true; rm -rf "$1"; [[ ! -e $1 ]]||die reset; echo "Lot 4 reset complete"; }
case ${1:-} in setup)setup "${2:-}";;status)status "${2:-}";;run)run "${2:-}";;validate)validate "${2:-}";;reset)reset "${2:-}";;*)echo "Usage: $0 {setup|status|run|validate|reset} <root>" >&2;exit 2;;esac
