#!/usr/bin/env bash

set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RESULTS="${ROOT}/results"
PROJECT="pers03"
readonly ROOT RESULTS PROJECT

cleanup() {
  docker compose -p "${PROJECT}" -f "${ROOT}/compose.yaml" down --volumes \
    >/dev/null 2>&1 || true
}
trap cleanup EXIT

normalize() {
  local file="$1"
  sed -E -e 's/[[:space:]]+$//' -e '${/^$/d;}' "${file}" >"${file}.tmp"
  mv "${file}.tmp" "${file}"
}

capture() {
  local name="$1" expected="$2"
  shift 2
  local directory="${RESULTS}/${name}" actual
  mkdir -p "${directory}"
  { printf '%q' "$1"; for argument in "${@:2}"; do printf ' %q' "${argument}"; done; printf '\n'; } \
    >"${directory}/command.txt"
  set +e
  "$@" >"${directory}/output.txt.tmp" 2>&1
  actual=$?
  set -e
  normalize "${directory}/output.txt.tmp"
  mv "${directory}/output.txt.tmp" "${directory}/output.txt"
  printf 'expected_exit_code=%s\nexit_code=%s\n' "${expected}" "${actual}" \
    >"${directory}/metadata.txt"

  if [[ "${expected}" == "nonzero" && ${actual} -eq 0 ]]; then
    echo "FAILED: ${name} exited 0; expected nonzero" >&2
    return 1
  fi

  if [[ "${expected}" != "nonzero" && ${actual} -ne ${expected} ]]; then
    echo "FAILED: ${name} exited ${actual}; expected ${expected}" >&2
    return 1
  fi

  printf '%-52s exit %s as expected\n' "${name}" "${actual}"
}

in_image() {
  docker run --rm \
    --network "${PROJECT}_default" \
    --env DATABASE_DSN='pgsql:host=database;port=5432;dbname=concurrency' \
    --env DATABASE_USER=concurrency \
    --env DATABASE_PASSWORD=concurrency \
    --volume "${RESULTS}:/app/results" \
    pers03-test "$@"
}

mutant_case() {
  in_image sh -lc "php mutate.php $1 && php tests/run.php $2"
}

mutant_pessimistic() {
  in_image sh -lc "php mutate.php $1 && php tests/pessimistic.php"
}

mutant_atomic() {
  in_image sh -lc "php mutate.php $1 && php tests/atomic.php"
}

mutant_analysis() {
  in_image sh -lc "php mutate.php $1 && php analyze.php"
}

mutation_diff() {
  in_image sh -lc "cp $2 /tmp/original && php mutate.php $1 >/dev/null && diff -u --label '$2 (clean)' --label '$2 ($1)' /tmp/original $2"
}

rm -rf \
  "${RESULTS}/analysis" \
  "${RESULTS}/behavior" \
  "${RESULTS}/diffs" \
  "${RESULTS}/environment" \
  "${RESULTS}/negative" \
  "${RESULTS}/setup" \
  "${RESULTS}/sources" \
  "${RESULTS}/verification"
mkdir -p \
  "${RESULTS}/setup/database" \
  "${RESULTS}/setup/test-image" \
  "${RESULTS}/environment/php" \
  "${RESULTS}/environment/postgres" \
  "${RESULTS}/environment/images" \
  "${RESULTS}/behavior"

printf 'docker compose up --build --detach --wait database\n' \
  >"${RESULTS}/setup/database/command.txt"
docker compose -p "${PROJECT}" -f "${ROOT}/compose.yaml" \
  up --build --detach --wait database \
  >"${RESULTS}/setup/database/output.txt" 2>&1
normalize "${RESULTS}/setup/database/output.txt"
printf 'exit_code=0\n' >"${RESULTS}/setup/database/metadata.txt"

printf 'docker compose build test\n' \
  >"${RESULTS}/setup/test-image/command.txt"
docker compose -p "${PROJECT}" -f "${ROOT}/compose.yaml" \
  build test >"${RESULTS}/setup/test-image/output.txt" 2>&1
normalize "${RESULTS}/setup/test-image/output.txt"
printf 'exit_code=0\n' >"${RESULTS}/setup/test-image/metadata.txt"

printf 'php --version\n' >"${RESULTS}/environment/php/command.txt"
docker compose -p "${PROJECT}" -f "${ROOT}/compose.yaml" \
  run --rm test php --version >"${RESULTS}/environment/php/output.txt"
printf 'exit_code=0\n' >"${RESULTS}/environment/php/metadata.txt"

printf 'postgres --version; SHOW transaction_isolation\n' \
  >"${RESULTS}/environment/postgres/command.txt"
docker compose -p "${PROJECT}" -f "${ROOT}/compose.yaml" \
  exec -T database postgres --version \
  >"${RESULTS}/environment/postgres/output.txt"
docker compose -p "${PROJECT}" -f "${ROOT}/compose.yaml" \
  exec -T database psql -U concurrency -d concurrency -At \
  -c 'SHOW transaction_isolation' \
  >>"${RESULTS}/environment/postgres/output.txt"
printf 'exit_code=0\n' >"${RESULTS}/environment/postgres/metadata.txt"

printf 'docker compose config --images\n' \
  >"${RESULTS}/environment/images/command.txt"
docker compose -p "${PROJECT}" -f "${ROOT}/compose.yaml" \
  config --images >"${RESULTS}/environment/images/output.txt"
printf 'exit_code=0\n' >"${RESULTS}/environment/images/metadata.txt"

for case_name in \
  domain-validation \
  naive-lost-update \
  optimistic-conflict \
  retry-redecision \
  in-memory-contract
do
  directory="${RESULTS}/behavior/${case_name}"
  mkdir -p "${directory}"
  printf 'php tests/run.php %s\n' "${case_name}" >"${directory}/command.txt"
  docker compose -p "${PROJECT}" -f "${ROOT}/compose.yaml" \
    run --rm test php tests/run.php "${case_name}" >"${directory}/evidence.json"
  printf 'exit_code=0\n' >"${directory}/metadata.txt"
done

directory="${RESULTS}/behavior/atomic-counter"
mkdir -p "${directory}"
printf 'php tests/atomic.php\n' >"${directory}/command.txt"
docker compose -p "${PROJECT}" -f "${ROOT}/compose.yaml" \
  run --rm test php tests/atomic.php >"${directory}/evidence.json"
printf 'exit_code=0\n' >"${directory}/metadata.txt"

directory="${RESULTS}/behavior/pessimistic-lock"
mkdir -p "${directory}"
printf 'php tests/pessimistic.php\n' >"${directory}/command.txt"
docker compose -p "${PROJECT}" -f "${ROOT}/compose.yaml" \
  run --rm test php tests/pessimistic.php >"${directory}/evidence.json"
printf 'exit_code=0\n' >"${directory}/metadata.txt"

capture analysis/inventory 0 in_image php analyze.php
capture sources/primary 0 in_image php source-evidence.php
capture behavior/full-suite 0 in_image php tests/run.php all

capture negative/remove-version-predicate nonzero \
  mutant_case remove-version-predicate optimistic-conflict
capture negative/ignore-zero-rows nonzero \
  mutant_case ignore-zero-rows optimistic-conflict
capture negative/commit-children-before-compare nonzero \
  mutant_case commit-children-before-compare optimistic-conflict
capture negative/mark-before-commit nonzero \
  mutant_analysis mark-before-commit
capture negative/overwrite-stale-version nonzero \
  mutant_case overwrite-stale-version optimistic-conflict
capture negative/retry-without-reload nonzero \
  mutant_case retry-without-reload retry-redecision
capture negative/release-lock-before-decision nonzero \
  mutant_pessimistic release-lock-before-decision
capture negative/remove-for-update nonzero \
  mutant_pessimistic remove-for-update
capture negative/unbounded-atomic-counter nonzero \
  mutant_atomic unbounded-atomic-counter
capture negative/unversioned-in-memory nonzero \
  mutant_case unversioned-in-memory in-memory-contract

capture diffs/remove-version-predicate 1 \
  mutation_diff remove-version-predicate src/CapacityPoolRepository.php
capture diffs/ignore-zero-rows 1 \
  mutation_diff ignore-zero-rows src/CapacityPoolRepository.php
capture diffs/commit-children-before-compare 1 \
  mutation_diff commit-children-before-compare src/CapacityPoolRepository.php
capture diffs/mark-before-commit 1 \
  mutation_diff mark-before-commit src/CapacityPoolRepository.php
capture diffs/overwrite-stale-version 1 \
  mutation_diff overwrite-stale-version src/CapacityPoolRepository.php
capture diffs/retry-without-reload 1 \
  mutation_diff retry-without-reload src/RetryingAllocation.php
capture diffs/release-lock-before-decision 1 \
  mutation_diff release-lock-before-decision src/CapacityPoolRepository.php
capture diffs/remove-for-update 1 \
  mutation_diff remove-for-update src/CapacityPoolRepository.php
capture diffs/unbounded-atomic-counter 1 \
  mutation_diff unbounded-atomic-counter tests/atomic.php
capture diffs/unversioned-in-memory 1 \
  mutation_diff unversioned-in-memory src/InMemoryCapacityPools.php

capture verification/results 0 in_image php verify-results.php

echo "Evidence retained in ${RESULTS}"
