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

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

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: $LAB_ROOT/$MARKER_NAME"
}

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

assert_unique_column() {
  local file="$1"
  local column="$2"
  local 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"
}

assert_reference_column() {
  local child_file="$1"
  local child_column="$2"
  local parent_file="$3"
  local parent_column="$4"
  local label="$5"
  awk -F',' -v child_col="$child_column" -v parent_col="$parent_column" -v label="$label" '
    NR == FNR {
      if (FNR > 1) parent[$parent_col] = 1
      next
    }
    FNR == 1 { next }
    $child_col != "" && !($child_col in parent) {
      printf "unknown %s: %s at line %d\n", label, $child_col, FNR > "/dev/stderr"
      exit 1
    }
  ' "$parent_file" "$child_file" || fail "$label reference validation failed"
}

setup_lab() {
  if [ -e "$LAB_ROOT" ]; then
    require_marker
    rm -rf "$LAB_ROOT/refs" "$LAB_ROOT/policy" "$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/policy" "$LAB_ROOT/input" \
    "$LAB_ROOT/output" "$LAB_ROOT/evidence"

  cat > "$LAB_ROOT/refs/entities.csv" <<'CSV'
entity_code,parent_code,name
SORIA,,SORIA Distribution
ORLEANS,SORIA,Siège Orléans
TOURS,SORIA,Agence Tours
LAB,SORIA,Laboratoire
CSV

  cat > "$LAB_ROOT/refs/locations.csv" <<'CSV'
location_code,entity_code,name
ORL-F1,ORLEANS,Orléans étage 1
ORL-DC,ORLEANS,Orléans salle technique
TOU-F1,TOURS,Tours étage 1
LAB-ROOM,LAB,Laboratoire isolé
CSV

  cat > "$LAB_ROOT/refs/asset-types.csv" <<'CSV'
asset_type,name,preferred_prefix
COMPUTER,Ordinateur,PC
SERVER,Serveur,SRV
MONITOR,Écran,MON
PRINTER,Imprimante,PRN
NETWORK,Équipement réseau,NET
PHONE,Téléphone,PHN
VIRTUAL_MACHINE,Machine virtuelle,VM
CSV

  cat > "$LAB_ROOT/refs/statuses.csv" <<'CSV'
status_code,name,terminal
IN_STOCK,En stock,no
IN_SERVICE,En service,no
LOANED,Prêté,no
MAINTENANCE,Maintenance,no
RETIRED,Retiré,yes
CSV

  cat > "$LAB_ROOT/refs/manufacturers.csv" <<'CSV'
manufacturer_code,canonical_name
DELL,Dell
HP,HP
LENOVO,Lenovo
CISCO,Cisco
EPSON,Epson
QEMU,QEMU
CSV

  cat > "$LAB_ROOT/refs/manufacturer-aliases.csv" <<'CSV'
raw_name,manufacturer_code
Dell Inc.,DELL
DELL,DELL
Hewlett-Packard,HP
HP,HP
Lenovo,LENOVO
Cisco Systems,CISCO
Epson,EPSON
QEMU,QEMU
CSV

  cat > "$LAB_ROOT/refs/models.csv" <<'CSV'
model_code,manufacturer_code,asset_type,canonical_name
DL-LAT-5420,DELL,COMPUTER,Latitude 5420
HP-ELI-800G6,HP,COMPUTER,EliteDesk 800 G6
LEN-T14-G2,LENOVO,COMPUTER,ThinkPad T14 Gen 2
CIS-C9200,CISCO,NETWORK,Catalyst 9200
EPS-WF4830,EPSON,PRINTER,WorkForce Pro WF-4830
DEL-P2422H,DELL,MONITOR,P2422H
QEMU-KVM,QEMU,VIRTUAL_MACHINE,KVM Virtual Machine
CSV

  cat > "$LAB_ROOT/refs/model-aliases.csv" <<'CSV'
raw_model,model_code
Latitude 5420,DL-LAT-5420
EliteDesk 800 G6,HP-ELI-800G6
ThinkPad T14 Gen 2,LEN-T14-G2
Catalyst 9200,CIS-C9200
WF-4830,EPS-WF4830
P2422H,DEL-P2422H
KVM Virtual Machine,QEMU-KVM
CSV

  cat > "$LAB_ROOT/policy/tag-mapping.csv" <<'CSV'
tag,entity_code,location_code,environment
ORL-USERS,ORLEANS,ORL-F1,production
TOU-USERS,TOURS,TOU-F1,production
ORL-INFRA,ORLEANS,ORL-DC,production
LAB-GLPI,LAB,LAB-ROOM,lab
CSV

  cat > "$LAB_ROOT/policy/agent-policy.csv" <<'CSV'
platform,server_url,tag,interval_minutes,mode,tls_verify,rollout
windows,https://glpi.lab.soria.invalid/,ORL-USERS,720,service,yes,pilot
linux,https://glpi.lab.soria.invalid/,ORL-INFRA,360,daemon,yes,pilot
macos,https://glpi.lab.soria.invalid/,TOU-USERS,720,daemon,yes,pilot
CSV

  cat > "$LAB_ROOT/policy/identity-policy.md" <<'MD'
# Politique d'identification et de rapprochement

Ordre de confiance proposé :

1. type + numéro de série non vide ;
2. type + UUID non vide ;
3. type + adresse MAC stable ;
4. nom + entité uniquement comme signal de revue.

Une correspondance forte ne déclenche jamais une suppression automatique. Elle produit un plan :
`MERGE_HISTORY_KEEP_NEWEST`, `MANUAL_REVIEW_PRESERVE_BOTH` ou `CREATE_NEW`.

Les règles d'affectation par tag sont testées avant import. L'ordre des règles est documenté,
car le moteur s'arrête sur la première règle correspondante.
MD

  cat > "$LAB_ROOT/policy/agent-deployment.md" <<'MD'
# Déploiement contrôlé de GLPI Agent

- commencer par un pilote représentatif ;
- utiliser une URL HTTPS et vérifier le certificat ;
- affecter un tag stable lié à une règle d'entité et de lieu ;
- éviter une fréquence trop courte qui provoquerait une tempête d'inventaires ;
- conserver la configuration et les logs localement ;
- tester un inventaire autonome avant l'envoi au serveur ;
- documenter désinstallation, rollback et traitement des agents silencieux.

Exemples à adapter dans une instance de lab :

```text
glpi-inventory --json --tag ORL-USERS > inventory.json
glpi-agent --server https://glpi.lab.soria.invalid/ --tag ORL-USERS
```

Le laboratoire hors ligne ne contacte aucun serveur GLPI.
MD

  cat > "$LAB_ROOT/input/inventories.csv" <<'CSV'
source_id,reported_name,asset_type,manufacturer_raw,model_raw,serial,uuid,mac,tag,status_code,agent_version,last_inventory
INV-001,ORL-PC-001,COMPUTER,Dell Inc.,Latitude 5420,SN-DL-001,UUID-DL-001,00:11:22:33:44:01,ORL-USERS,IN_SERVICE,1.15,2026-08-04T08:00:00Z
INV-002,orleans-pc-001,COMPUTER,DELL,Latitude 5420,SN-DL-001,UUID-DL-001,00:11:22:33:44:01,ORL-USERS,IN_SERVICE,1.15,2026-08-04T09:00:00Z
INV-003,TOU-PC-014,COMPUTER,Hewlett-Packard,EliteDesk 800 G6,SN-HP-014,UUID-HP-014,00:11:22:33:44:14,TOU-USERS,IN_SERVICE,1.15,2026-08-04T08:10:00Z
INV-004,ORL-NET-001,NETWORK,Cisco Systems,Catalyst 9200,FCW1234A1,,00:AA:BB:CC:DD:01,ORL-INFRA,IN_SERVICE,network-scan,2026-08-04T08:20:00Z
INV-005,LAB-VM-001,VIRTUAL_MACHINE,QEMU,KVM Virtual Machine,,UUID-LAB-001,52:54:00:12:34:56,LAB-GLPI,IN_SERVICE,1.15,2026-08-04T08:30:00Z
INV-006,ORL-MON-011,MONITOR,Dell Inc.,P2422H,SN-MON-011,,,ORL-USERS,IN_SERVICE,1.15,2026-08-04T08:40:00Z
INV-007,TOU-PC-014B,COMPUTER,HP,EliteDesk 800 G6,SN-HP-014-R,UUID-HP-014,00:11:22:33:44:15,TOU-USERS,MAINTENANCE,1.15,2026-08-04T10:00:00Z
INV-008,TOU-PC-020,COMPUTER,Lenovo,ThinkPad T14 Gen 2,SN-LEN-020,UUID-LEN-020,00:11:22:33:44:20,TOU-USERS,IN_STOCK,1.15,2026-08-04T08:50:00Z
INV-009,ORL-PRN-003,PRINTER,Epson,WF-4830,SN-EP-003,,00:11:22:33:44:30,ORL-USERS,IN_SERVICE,1.15,2026-08-04T09:10:00Z
CSV

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

  cat > "$LAB_ROOT/README.txt" <<'TXT'
Commands:
  setup     create the offline GLPI asset and inventory dossier
  status    show source counts and current outputs
  run       normalize references, map tags and produce a reconciliation plan
  validate  verify references, quality gates, duplicates and source immutability
  reset     delete this marker-protected lab root
TXT

  printf 'GLPI assets and inventory laboratory ready: %s\n' "$LAB_ROOT"
}

show_status() {
  require_marker
  printf 'Inventories: %s\n' "$(csv_rows "$LAB_ROOT/input/inventories.csv")"
  printf 'Asset types: %s\n' "$(csv_rows "$LAB_ROOT/refs/asset-types.csv")"
  printf 'Models: %s\n' "$(csv_rows "$LAB_ROOT/refs/models.csv")"
  printf 'Tags: %s\n' "$(csv_rows "$LAB_ROOT/policy/tag-mapping.csv")"
  if [ -f "$LAB_ROOT/output/normalized-assets.csv" ]; then
    printf 'Normalized assets: %s\n' "$(csv_rows "$LAB_ROOT/output/normalized-assets.csv")"
  else
    printf 'Normalized assets: not generated\n'
  fi
  if [ -f "$LAB_ROOT/output/duplicate-candidates.csv" ]; then
    printf 'Duplicate candidates: %s\n' "$(csv_rows "$LAB_ROOT/output/duplicate-candidates.csv")"
  else
    printf 'Duplicate candidates: not generated\n'
  fi
}

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

  assert_unique_column "$LAB_ROOT/refs/entities.csv" 1 "entity code"
  assert_unique_column "$LAB_ROOT/refs/locations.csv" 1 "location code"
  assert_unique_column "$LAB_ROOT/refs/asset-types.csv" 1 "asset type"
  assert_unique_column "$LAB_ROOT/refs/statuses.csv" 1 "status code"
  assert_unique_column "$LAB_ROOT/refs/manufacturers.csv" 1 "manufacturer code"
  assert_unique_column "$LAB_ROOT/refs/models.csv" 1 "model code"
  assert_unique_column "$LAB_ROOT/policy/tag-mapping.csv" 1 "tag"
  assert_unique_column "$LAB_ROOT/input/inventories.csv" 1 "source id"

  assert_reference_column "$LAB_ROOT/refs/locations.csv" 2 \
    "$LAB_ROOT/refs/entities.csv" 1 "location entity"
  assert_reference_column "$LAB_ROOT/refs/models.csv" 2 \
    "$LAB_ROOT/refs/manufacturers.csv" 1 "model manufacturer"
  assert_reference_column "$LAB_ROOT/refs/models.csv" 3 \
    "$LAB_ROOT/refs/asset-types.csv" 1 "model asset type"
  assert_reference_column "$LAB_ROOT/policy/tag-mapping.csv" 2 \
    "$LAB_ROOT/refs/entities.csv" 1 "tag entity"
  assert_reference_column "$LAB_ROOT/policy/tag-mapping.csv" 3 \
    "$LAB_ROOT/refs/locations.csv" 1 "tag location"

  awk -F',' '
    NR == FNR {
      if (FNR > 1) manufacturers[$1] = $2
      next
    }
    FNR == 1 { next }
    !($2 in manufacturers) {
      printf "unknown manufacturer alias target: %s\n", $2 > "/dev/stderr"
      exit 1
    }
  ' "$LAB_ROOT/refs/manufacturers.csv" "$LAB_ROOT/refs/manufacturer-aliases.csv" \
    || fail "manufacturer alias validation failed"

  awk -F',' '
    NR == FNR {
      if (FNR > 1) models[$1] = 1
      next
    }
    FNR == 1 { next }
    !($2 in models) {
      printf "unknown model alias target: %s\n", $2 > "/dev/stderr"
      exit 1
    }
  ' "$LAB_ROOT/refs/models.csv" "$LAB_ROOT/refs/model-aliases.csv" \
    || fail "model alias validation failed"

  awk -F',' '
    NR == 1 { next }
    $2 !~ /^https:\/\// {
      printf "non-HTTPS server URL for %s\n", $1 > "/dev/stderr"
      exit 1
    }
    $2 ~ /@/ {
      printf "credential-like server URL for %s\n", $1 > "/dev/stderr"
      exit 1
    }
    $4 + 0 < 60 {
      printf "inventory interval too short for %s\n", $1 > "/dev/stderr"
      exit 1
    }
    $6 != "yes" {
      printf "TLS verification disabled for %s\n", $1 > "/dev/stderr"
      exit 1
    }
  ' "$LAB_ROOT/policy/agent-policy.csv" || fail "agent policy validation failed"

  awk -F',' -v OFS=',' \
    -v types="$LAB_ROOT/refs/asset-types.csv" \
    -v statuses="$LAB_ROOT/refs/statuses.csv" \
    -v manufacturers="$LAB_ROOT/refs/manufacturers.csv" \
    -v manufacturer_aliases="$LAB_ROOT/refs/manufacturer-aliases.csv" \
    -v models="$LAB_ROOT/refs/models.csv" \
    -v model_aliases="$LAB_ROOT/refs/model-aliases.csv" \
    -v tags="$LAB_ROOT/policy/tag-mapping.csv" \
    -v normalized="$LAB_ROOT/output/normalized-assets.csv" \
    -v duplicates="$LAB_ROOT/output/duplicate-candidates.csv" \
    -v plan="$LAB_ROOT/output/reconciliation-plan.csv" '
    function load_csv(file, kind, line, count) {
      count = 0
      while ((getline line < file) > 0) {
        count++
        if (count == 1) continue
        split(line, f, ",")
        if (kind == "types") type[f[1]] = f[3]
        else if (kind == "statuses") status[f[1]] = 1
        else if (kind == "manufacturers") manufacturer_name[f[1]] = f[2]
        else if (kind == "manufacturer_aliases") manufacturer_alias[f[1]] = f[2]
        else if (kind == "models") {
          model_manufacturer[f[1]] = f[2]
          model_type[f[1]] = f[3]
          model_name[f[1]] = f[4]
        }
        else if (kind == "model_aliases") model_alias[f[1]] = f[2]
        else if (kind == "tags") {
          tag_entity[f[1]] = f[2]
          tag_location[f[1]] = f[3]
        }
      }
      close(file)
    }
    BEGIN {
      load_csv(types, "types")
      load_csv(statuses, "statuses")
      load_csv(manufacturers, "manufacturers")
      load_csv(manufacturer_aliases, "manufacturer_aliases")
      load_csv(models, "models")
      load_csv(model_aliases, "model_aliases")
      load_csv(tags, "tags")

      print "source_id,reported_name,asset_type,manufacturer_code,manufacturer_name,model_code,model_name,serial,uuid,mac,entity_code,location_code,status_code,identity_key,name_quality,last_inventory" > normalized
      print "candidate_a,candidate_b,match_key,reason,recommended_action" > duplicates
      print "candidate_a,candidate_b,decision,evidence_required" > plan
    }
    NR == 1 { next }
    {
      source = $1
      name = toupper($2)
      asset_type = $3
      manufacturer_code = manufacturer_alias[$4]
      model_code = model_alias[$5]
      serial = $6
      uuid = $7
      mac = toupper($8)
      tag = $9
      status_code = $10
      last_inventory = $12

      if (!(asset_type in type)) {
        printf "unknown asset type for %s: %s\n", source, asset_type > "/dev/stderr"
        exit 1
      }
      if (!(status_code in status)) {
        printf "unknown status for %s: %s\n", source, status_code > "/dev/stderr"
        exit 1
      }
      if (manufacturer_code == "" || !(manufacturer_code in manufacturer_name)) {
        printf "unmapped manufacturer for %s: %s\n", source, $4 > "/dev/stderr"
        exit 1
      }
      if (model_code == "" || !(model_code in model_name)) {
        printf "unmapped model for %s: %s\n", source, $5 > "/dev/stderr"
        exit 1
      }
      if (model_manufacturer[model_code] != manufacturer_code) {
        printf "manufacturer/model mismatch for %s\n", source > "/dev/stderr"
        exit 1
      }
      if (model_type[model_code] != asset_type) {
        printf "model/asset type mismatch for %s\n", source > "/dev/stderr"
        exit 1
      }
      if (!(tag in tag_entity)) {
        printf "unknown tag for %s: %s\n", source, tag > "/dev/stderr"
        exit 1
      }

      if (serial != "") identity = asset_type ":SERIAL:" serial
      else if (uuid != "") identity = asset_type ":UUID:" uuid
      else if (mac != "") identity = asset_type ":MAC:" mac
      else identity = asset_type ":NAME:" name ":" tag_entity[tag]

      quality = (name ~ /^(ORL|TOU|LAB)-(PC|SRV|MON|PRN|NET|PHN|VM)-[0-9][0-9][0-9]$/ ? "PASS" : "REVIEW")

      print source, name, asset_type, manufacturer_code, manufacturer_name[manufacturer_code], \
        model_code, model_name[model_code], serial, uuid, mac, tag_entity[tag], \
        tag_location[tag], status_code, identity, quality, last_inventory > normalized

      if (identity in identity_source) {
        action = (last_inventory >= identity_last[identity] ? "MERGE_HISTORY_KEEP_NEWEST" : "MERGE_HISTORY_KEEP_OLDEST")
        print identity_source[identity], source, identity, "EXACT_IDENTITY", action > duplicates
        print identity_source[identity], source, action, "serial+uuid+mac+timestamps+history" > plan
      } else {
        identity_source[identity] = source
        identity_last[identity] = last_inventory
      }

      if (uuid != "") {
        if ((uuid in uuid_source) && uuid_serial[uuid] != serial) {
          key = asset_type ":UUID:" uuid
          print uuid_source[uuid], source, key, "UUID_WITH_CONFLICTING_SERIAL", "MANUAL_REVIEW_PRESERVE_BOTH" > duplicates
          print uuid_source[uuid], source, "MANUAL_REVIEW_PRESERVE_BOTH", "hardware-change+serial+mac+user+history" > plan
        } else if (!(uuid in uuid_source)) {
          uuid_source[uuid] = source
          uuid_serial[uuid] = serial
        }
      }
    }
  ' "$LAB_ROOT/input/inventories.csv" || fail "inventory normalization failed"

  {
    printf '# Rapport de qualité de l’inventaire\n\n'
    printf -- '- actifs sources : %s\n' "$(csv_rows "$LAB_ROOT/input/inventories.csv")"
    printf -- '- actifs normalisés : %s\n' "$(csv_rows "$LAB_ROOT/output/normalized-assets.csv")"
    printf -- '- candidats au rapprochement : %s\n' "$(csv_rows "$LAB_ROOT/output/duplicate-candidates.csv")"
    printf -- '- noms à revoir : %s\n' "$(awk -F',' 'NR > 1 && $15 == "REVIEW" { count++ } END { print count + 0 }' "$LAB_ROOT/output/normalized-assets.csv")"
    printf -- '- tags connus : %s\n' "$(csv_rows "$LAB_ROOT/policy/tag-mapping.csv")"
    printf '\nAucun objet source n’est supprimé. Le rapprochement reste un plan révisable.\n'
  } > "$LAB_ROOT/evidence/inventory-quality-report.md"

  {
    printf '# Rapport de déploiement agent\n\n'
    printf 'Chaque cible utilise HTTPS, vérifie TLS et respecte un intervalle supérieur ou égal à 60 minutes.\n\n'
    awk -F',' 'NR > 1 {
      printf "- %s : tag `%s`, intervalle %s min, mode %s, vague %s\n", $1, $3, $4, $5, $7
    }' "$LAB_ROOT/policy/agent-policy.csv"
    printf '\nLe laboratoire ne contacte aucun serveur GLPI et ne déploie aucun agent.\n'
  } > "$LAB_ROOT/evidence/agent-policy-report.md"

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

  printf 'Reference inventory normalization and reconciliation completed.\n'
}

validate_reference() {
  require_marker

  for file in \
    "$LAB_ROOT/output/normalized-assets.csv" \
    "$LAB_ROOT/output/duplicate-candidates.csv" \
    "$LAB_ROOT/output/reconciliation-plan.csv" \
    "$LAB_ROOT/evidence/inventory-quality-report.md" \
    "$LAB_ROOT/evidence/agent-policy-report.md" \
    "$LAB_ROOT/evidence/source-checksums-before.sha256" \
    "$LAB_ROOT/evidence/source-checksums-after.sha256" \
    "$LAB_ROOT/evidence/output-checksums.sha256" \
    "$LAB_ROOT/evidence/source-immutability.diff"
  do
    [ -f "$file" ] || fail "missing output: $file"
  done

  [ "$(csv_rows "$LAB_ROOT/input/inventories.csv")" -eq 9 ] \
    || fail "expected 9 source inventories"
  [ "$(csv_rows "$LAB_ROOT/output/normalized-assets.csv")" -eq 9 ] \
    || fail "expected 9 normalized assets"
  [ "$(csv_rows "$LAB_ROOT/output/duplicate-candidates.csv")" -eq 2 ] \
    || fail "expected 2 duplicate candidates"
  [ "$(csv_rows "$LAB_ROOT/output/reconciliation-plan.csv")" -eq 2 ] \
    || fail "expected 2 reconciliation decisions"

  grep -Fq 'INV-001,INV-002' "$LAB_ROOT/output/duplicate-candidates.csv" \
    || fail "exact duplicate pair missing"
  grep -Fq 'MERGE_HISTORY_KEEP_NEWEST' "$LAB_ROOT/output/reconciliation-plan.csv" \
    || fail "merge recommendation missing"
  grep -Fq 'INV-003,INV-007' "$LAB_ROOT/output/duplicate-candidates.csv" \
    || fail "conflicting UUID pair missing"
  grep -Fq 'MANUAL_REVIEW_PRESERVE_BOTH' "$LAB_ROOT/output/reconciliation-plan.csv" \
    || fail "manual review recommendation missing"

  grep -Fq 'ORLEANS-PC-001' "$LAB_ROOT/output/normalized-assets.csv" \
    || fail "expected normalized uppercase name missing"
  grep -Fq ',REVIEW,2026-08-04T09:00:00Z' "$LAB_ROOT/output/normalized-assets.csv" \
    || fail "name quality review missing"
  grep -Fq ',DELL,Dell,DL-LAT-5420,Latitude 5420,' "$LAB_ROOT/output/normalized-assets.csv" \
    || fail "manufacturer/model normalization missing"
  grep -Fq ',ORLEANS,ORL-F1,IN_SERVICE,' "$LAB_ROOT/output/normalized-assets.csv" \
    || fail "tag mapping 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"

  grep -Fq 'Le laboratoire ne contacte aucun serveur GLPI' \
    "$LAB_ROOT/evidence/agent-policy-report.md" \
    || fail "proof boundary missing"

  printf 'Validation successful: taxonomy, normalization, tags, agent policy, duplicate detection and reconciliation are correct.\n'
}

reset_lab() {
  require_marker
  rm -rf "$LAB_ROOT"
  printf 'GLPI assets and inventory 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
