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

ACTION="${1:-setup}"
LAB_ROOT="${2:-/tmp/soria-git-undo-${USER:-student}}"
MARKER_NAME=".soria-git-undo-history"

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

resolve_safe_root() {
  case "$LAB_ROOT" in
    /tmp/soria-git-*|"${HOME}"/soria-git-*) ;;
    *) 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"
}

configure_repo() {
  local repo="$1"
  git -C "$repo" config user.name "SORIA Student"
  git -C "$repo" config user.email "student@soria.invalid"
  git -C "$repo" config init.defaultBranch main
}

init_repo() {
  local repo="$1"
  mkdir -p "$repo"
  git -C "$repo" init -q -b main
  configure_repo "$repo"
}

create_restore_repo() {
  local repo="$LAB_ROOT/restore"
  init_repo "$repo"
  cat > "$repo/app.conf" <<'TXT'
mode=stable
port=8080
TXT
  git -C "$repo" add app.conf
  git -C "$repo" commit -q -m "chore: add stable application configuration"

  cat > "$repo/app.conf" <<'TXT'
mode=experimental
port=9090
TXT
  git -C "$repo" add app.conf
  cat > "$repo/app.conf" <<'TXT'
mode=broken-working-tree
port=9999
TXT
}

create_reset_template() {
  local repo="$1"
  init_repo "$repo"
  printf 'version=1\n' > "$repo/version.txt"
  git -C "$repo" add version.txt
  git -C "$repo" commit -q -m "chore: add version one"
  printf 'version=2\n' > "$repo/version.txt"
  git -C "$repo" add version.txt
  git -C "$repo" commit -q -m "feat: add version two"
  printf 'version=3\n' > "$repo/version.txt"
  git -C "$repo" add version.txt
  git -C "$repo" commit -q -m "feat: add version three"
}

create_revert_repo() {
  local repo="$LAB_ROOT/revert"
  init_repo "$repo"
  printf 'feature=disabled\n' > "$repo/service.conf"
  git -C "$repo" add service.conf
  git -C "$repo" commit -q -m "chore: initialize service"
  printf 'feature=enabled\n' > "$repo/service.conf"
  git -C "$repo" add service.conf
  git -C "$repo" commit -q -m "feat: enable service feature"
  printf 'feature=broken\n' > "$repo/service.conf"
  git -C "$repo" add service.conf
  git -C "$repo" commit -q -m "fix: apply unsafe service value"
}

create_reflog_repo() {
  local repo="$LAB_ROOT/reflog"
  init_repo "$repo"
  printf 'checkpoint=one\n' > "$repo/checkpoint.txt"
  git -C "$repo" add checkpoint.txt
  git -C "$repo" commit -q -m "chore: create first checkpoint"
  printf 'checkpoint=two\n' > "$repo/checkpoint.txt"
  git -C "$repo" add checkpoint.txt
  git -C "$repo" commit -q -m "feat: create second checkpoint"
  printf 'checkpoint=three\n' > "$repo/checkpoint.txt"
  git -C "$repo" add checkpoint.txt
  git -C "$repo" commit -q -m "feat: create third checkpoint"
  git -C "$repo" rev-parse HEAD > "$LAB_ROOT/reflog-lost-sha.txt"
}

create_ignore_repo() {
  local repo="$LAB_ROOT/ignore-secrets"
  init_repo "$repo"
  mkdir -p "$repo/config" "$repo/logs"
  cat > "$repo/.gitignore" <<'TXT'
.env
*.log
secrets/
TXT
  cat > "$repo/config/runtime.env" <<'TXT'
APP_ENV=development
APP_PORT=8080
TXT
  cat > "$repo/.env" <<'TXT'
TRAINING_TOKEN=not-a-real-secret
TXT
  printf 'temporary log\n' > "$repo/logs/app.log"
  git -C "$repo" add .gitignore config/runtime.env
  git -C "$repo" commit -q -m "chore: add ignore rules and tracked runtime template"
  printf 'config/runtime.env\n' >> "$repo/.gitignore"
  git -C "$repo" add .gitignore
  git -C "$repo" commit -q -m "chore: ignore generated runtime environment"
}

setup_lab() {
  if [ -e "$LAB_ROOT" ]; then
    require_marker
    rm -rf "$LAB_ROOT/restore" "$LAB_ROOT/reset-soft" "$LAB_ROOT/reset-mixed" \
      "$LAB_ROOT/reset-hard" "$LAB_ROOT/revert" "$LAB_ROOT/reflog" \
      "$LAB_ROOT/ignore-secrets" "$LAB_ROOT/evidence"
    rm -f "$LAB_ROOT/reflog-lost-sha.txt"
  else
    mkdir -p "$LAB_ROOT"
    : > "$LAB_ROOT/$MARKER_NAME"
  fi

  create_restore_repo
  create_reset_template "$LAB_ROOT/reset-soft"
  create_reset_template "$LAB_ROOT/reset-mixed"
  create_reset_template "$LAB_ROOT/reset-hard"
  create_revert_repo
  create_reflog_repo
  create_ignore_repo
  mkdir -p "$LAB_ROOT/evidence"

  cat > "$LAB_ROOT/README.txt" <<'TXT'
Commands:
  setup     create isolated repositories for restore/reset/revert/reflog/ignore
  status    inspect every scenario without changing it
  run       execute the reference recovery scenarios
  validate  verify each expected state and safety rule
  reset     delete this marked lab directory
TXT

  printf 'Git undo laboratory ready: %s\n' "$LAB_ROOT"
}

show_status() {
  require_marker
  local name repo
  for name in restore reset-soft reset-mixed reset-hard revert reflog ignore-secrets; do
    repo="$LAB_ROOT/$name"
    printf '\n[%s]\n' "$name"
    git -C "$repo" status --short --branch
    git -C "$repo" log --oneline --decorate -5
  done
}

run_reference() {
  require_marker

  local repo lost_sha

  repo="$LAB_ROOT/restore"
  git -C "$repo" diff > "$LAB_ROOT/evidence/restore-working-tree-before.patch"
  git -C "$repo" diff --cached > "$LAB_ROOT/evidence/restore-index-before.patch"
  git -C "$repo" restore app.conf
  git -C "$repo" restore --staged app.conf
  git -C "$repo" restore app.conf
  git -C "$repo" status --short > "$LAB_ROOT/evidence/restore-status-after.txt"

  repo="$LAB_ROOT/reset-soft"
  git -C "$repo" reset --soft HEAD~1
  git -C "$repo" status --short > "$LAB_ROOT/evidence/reset-soft-status.txt"

  repo="$LAB_ROOT/reset-mixed"
  git -C "$repo" reset --mixed HEAD~1 >/dev/null
  git -C "$repo" status --short > "$LAB_ROOT/evidence/reset-mixed-status.txt"

  repo="$LAB_ROOT/reset-hard"
  git -C "$repo" reset --hard HEAD~1 >/dev/null
  git -C "$repo" status --short > "$LAB_ROOT/evidence/reset-hard-status.txt"

  repo="$LAB_ROOT/revert"
  git -C "$repo" revert --no-edit HEAD >/dev/null
  git -C "$repo" log --oneline -4 > "$LAB_ROOT/evidence/revert-history.txt"

  repo="$LAB_ROOT/reflog"
  lost_sha="$(cat "$LAB_ROOT/reflog-lost-sha.txt")"
  git -C "$repo" reset --hard HEAD~1 >/dev/null
  git -C "$repo" branch recovered-checkpoint "$lost_sha"
  git -C "$repo" reflog -5 > "$LAB_ROOT/evidence/reflog.txt"
  git -C "$repo" reflog --all --format='%H' > "$LAB_ROOT/evidence/reflog-full-shas.txt"

  repo="$LAB_ROOT/ignore-secrets"
  git -C "$repo" rm --cached config/runtime.env >/dev/null
  git -C "$repo" commit -q -m "chore: stop tracking generated runtime environment"
  git -C "$repo" check-ignore -v .env config/runtime.env logs/app.log > "$LAB_ROOT/evidence/ignored-files.txt"
  git -C "$repo" log -S 'TRAINING_TOKEN' --all --oneline > "$LAB_ROOT/evidence/secret-search.txt"

  printf 'Reference undo and recovery scenarios completed.\n'
}

validate_reference() {
  require_marker

  local repo lost_sha

  repo="$LAB_ROOT/restore"
  [ -z "$(git -C "$repo" status --porcelain)" ] || fail "restore repository is not clean"
  grep -Fxq 'mode=stable' "$repo/app.conf" || fail "restore repository did not return to HEAD"
  grep -Fxq 'port=8080' "$repo/app.conf" || fail "restore repository has wrong port"

  repo="$LAB_ROOT/reset-soft"
  [ "$(git -C "$repo" log -1 --format='%s')" = 'feat: add version two' ] || fail "soft reset HEAD is wrong"
  [ "$(git -C "$repo" diff --cached --name-only)" = 'version.txt' ] || fail "soft reset should keep changes staged"
  [ -z "$(git -C "$repo" diff --name-only)" ] || fail "soft reset should keep working tree equal to index"
  grep -Fxq 'version=3' "$repo/version.txt" || fail "soft reset lost version three content"

  repo="$LAB_ROOT/reset-mixed"
  [ "$(git -C "$repo" log -1 --format='%s')" = 'feat: add version two' ] || fail "mixed reset HEAD is wrong"
  [ -z "$(git -C "$repo" diff --cached --name-only)" ] || fail "mixed reset should clear index changes"
  [ "$(git -C "$repo" diff --name-only)" = 'version.txt' ] || fail "mixed reset should keep working tree changes"
  grep -Fxq 'version=3' "$repo/version.txt" || fail "mixed reset lost version three content"

  repo="$LAB_ROOT/reset-hard"
  [ "$(git -C "$repo" log -1 --format='%s')" = 'feat: add version two' ] || fail "hard reset HEAD is wrong"
  [ -z "$(git -C "$repo" status --porcelain)" ] || fail "hard reset repository is not clean"
  grep -Fxq 'version=2' "$repo/version.txt" || fail "hard reset did not restore version two"

  repo="$LAB_ROOT/revert"
  [ "$(git -C "$repo" rev-list --count HEAD)" -eq 4 ] || fail "revert should add a fourth commit"
  case "$(git -C "$repo" log -1 --format='%s')" in
    Revert\ \"fix:\ apply\ unsafe\ service\ value\") ;;
    *) fail "unexpected revert commit message" ;;
  esac
  grep -Fxq 'feature=enabled' "$repo/service.conf" || fail "revert did not restore enabled feature"

  repo="$LAB_ROOT/reflog"
  lost_sha="$(cat "$LAB_ROOT/reflog-lost-sha.txt")"
  [ "$(git -C "$repo" rev-parse recovered-checkpoint)" = "$lost_sha" ] || fail "recovered branch does not point to lost commit"
  [ "$(git -C "$repo" show recovered-checkpoint:checkpoint.txt)" = 'checkpoint=three' ] || fail "lost checkpoint was not recovered"
  grep -Fxq "$lost_sha" "$LAB_ROOT/evidence/reflog-full-shas.txt" || fail "lost commit absent from reflog"

  repo="$LAB_ROOT/ignore-secrets"
  ! git -C "$repo" ls-files --error-unmatch .env >/dev/null 2>&1 || fail ".env is tracked"
  ! git -C "$repo" ls-files --error-unmatch config/runtime.env >/dev/null 2>&1 || fail "runtime environment is still tracked"
  git -C "$repo" check-ignore -q .env || fail ".env is not ignored"
  git -C "$repo" check-ignore -q config/runtime.env || fail "runtime environment is not ignored"
  git -C "$repo" check-ignore -q logs/app.log || fail "log file is not ignored"
  [ -z "$(git -C "$repo" log -S 'TRAINING_TOKEN' --all --oneline)" ] || fail "training token appears in history"
  [ -z "$(git -C "$repo" remote)" ] || fail "a remote was configured unexpectedly"

  printf 'Validation successful: restore, reset, revert, reflog and ignore scenarios are correct.\n'
}

resolve_safe_root

case "$ACTION" in
  setup)
    setup_lab
    ;;
  status)
    show_status
    ;;
  run)
    run_reference
    ;;
  validate)
    validate_reference
    ;;
  reset)
    if [ ! -e "$LAB_ROOT" ]; then
      printf 'Nothing to remove: %s\n' "$LAB_ROOT"
      exit 0
    fi
    require_marker
    rm -rf "$LAB_ROOT"
    printf 'Laboratory removed: %s\n' "$LAB_ROOT"
    ;;
  *)
    fail "unknown action '$ACTION' (setup|status|run|validate|reset)"
    ;;
esac
