#!/usr/bin/env bash
# FIFA 17 squad-adapter mutation battery.
#
# Each mutation injects a specific WRONG behaviour into the committed source,
# runs the one test that defends the invariant, and requires that test to FAIL
# (non-zero exit) — i.e. the mutant is killed. The source is reverted via
# `git checkout` after every mutation, so the tree is left untouched.
#
# A mutant that SURVIVES (its guard test still passes) means the invariant is
# not actually defended; the battery then exits non-zero.
#
# Run from the adapter crate root:  bash mutation-battery.sh
set -u
cd "$(dirname "$0")"

FUT=src/fut
PASS=0
FAIL=0
declare -a SURVIVORS=()

# mutate <file> <literal-old> <literal-new>  (literal, multiline-safe)
mutate() {
  OLD="$2" NEW="$3" perl -0777 -pi -e \
    's/\Q$ENV{OLD}\E/$ENV{NEW}/g or die "MUTATION PATTERN NOT FOUND in '"$1"'\n"' "$1"
}

# kill <n> <label> <test-filter> <file> <old> <new>
kill_test() {
  local n="$1" label="$2" filter="$3" file="$4" old="$5" new="$6"
  git checkout -- "$file"
  mutate "$file" "$old" "$new" || { echo "  [#$n] SETUP ERROR"; FAIL=$((FAIL+1)); SURVIVORS+=("#$n $label (setup)"); return; }
  if cargo test --quiet "$filter" >/dev/null 2>&1; then
    echo "  [#$n] SURVIVED  — $label  (test '$filter' still passed)"
    FAIL=$((FAIL+1)); SURVIVORS+=("#$n $label")
  else
    echo "  [#$n] killed    — $label"
    PASS=$((PASS+1))
  fi
  git checkout -- "$file"
}

echo "== FIFA17 squad adapter mutation battery =="

kill_test 1 "kitNumber keyed by slot index, not owned item" \
  kit_number_is_keyed_by_owned_item_not_slot "$FUT/squad_ext.rs" \
  '(s.owned_card_id.clone(), s.kit_number)' '(s.index.to_string(), s.kit_number)'

kill_test 2 "captain emitted using resourceId (asset), not wire id" \
  fresh_projects_full_23_slot_array_with_captain_wire_id "$FUT/squad_projection.rs" \
  'captain_wire = id.item_id as i64;' 'captain_wire = id.asset_id as i64;'

kill_test 3 "client chemistry silently reconciled (shadow lost)" \
  swap_moves_two_players_with_their_kits_and_round_trips "$FUT/squad_projection.rs" \
  '"chemistry": ext.client_reported.chemistry,' '"chemistry": 0,'

kill_test 4 "custom[] regenerated instead of round-tripped verbatim" \
  custom_is_preserved_byte_for_byte "$FUT/squad_ext.rs" \
  'custom: put.custom.clone(),' 'custom: Some("[]".to_string()),'

kill_test 5 "index derived (zeroed) instead of round-tripped" \
  baseline_projects_the_known_squad_round_trip "$FUT/squad.rs" \
  'index: p.index,' 'index: 0,'

kill_test 6 "stale extension accepted and projected" \
  stale_is_never_applied "$FUT/squad_projection.rs" \
  'SquadExtInput::Stale(_) => return Ok(SquadProjection::Stale),' 'SquadExtInput::Stale(ext) => ext,'

kill_test 7 "missing extension fabricates default fields" \
  missing_is_explicit_never_fabricated "$FUT/squad_projection.rs" \
  'SquadExtInput::Missing => return Ok(SquadProjection::Missing),' \
  'SquadExtInput::Missing => return Ok(SquadProjection::Projected(json!({"custom":"[]","manager":[]}))),'

kill_test 8 "shaper fabricates asset id, bypassing real FIFA identity" \
  shapes_real_identity_and_reverse_entity_ids "$FUT/item.rs" \
  'let asset = id.asset_id;' 'let asset = 0;'

kill_test 9 "duplicate definition collapses owned instances (id=asset)" \
  two_owned_copies_of_one_definition_stay_distinct_on_the_wire "$FUT/item.rs" \
  '"id": id.item_id,' '"id": id.asset_id,'

kill_test 10 "PUT treated as a partial slot diff (drops slots)" \
  full_replacement_carries_every_occupied_slot_no_diff "$FUT/squad.rs" \
  'if p.item_data.id == 0 {' 'if p.item_data.id == 0 || p.index > 1 {'

kill_test 11 "FIFA wire item id stored in canonical replacement" \
  full_replacement_carries_every_occupied_slot_no_diff "$FUT/squad.rs" \
  'ProposedSlot {
                owned_card_id,' 'ProposedSlot {
                owned_card_id: p.item_data.id.to_string(),'

kill_test 12 "projector rebuilds items independently of shared shaper" \
  persisted_read_round_trips_via_reconstructed_canonical_and_extension "$FUT/squad_projection.rs" \
  '"itemData": shape_item(item, id, ent),' '"itemData": json!({"id": id.item_id}),'

kill_test 13 "extension schema version ignored on read" \
  unknown_schema_version_is_rejected_not_coerced "$FUT/squad_ext.rs" \
  'if schema_version != EXT_SCHEMA_VERSION {' 'if false {'

kill_test 14 "player state keyed by CardDefinitionId not OwnedItemId" \
  two_owned_copies_of_one_definition_project_as_distinct_players "$FUT/squad_projection.rs" \
  '.get(&slot.owned_card_id)' '.get(&item.card_id)'

echo "== mutants killed: $PASS / $((PASS+FAIL)) =="
if [ "$FAIL" -ne 0 ]; then
  printf 'SURVIVORS:\n'; printf '  - %s\n' "${SURVIVORS[@]}"
  exit 1
fi
echo "all mutants killed"
