#!/bin/sh

# Colors
RED=$(tput setaf 1)
BOLD=$(tput bold)
RESET=$(tput sgr0)

ANY_ERRORS=false

# Make sets this variable when we run it and can influence the test results.
# Since this script is run through `make`, we need to unset it.
unset MAKELEVEL

# Get the directory of the current script, in a POSIX compatible way
# https://stackoverflow.com/a/29835459
script_directory=$(CDPATH="$(cd -- "$(dirname -- "$0")")" && pwd -P)

# Set this variable to ensure top level Makefile doesn't affect the results.
export PROJECT_ROOT="${script_directory}/tests"

describe() {
  printf "\n%s%s%s" "$BOLD" "$1" "$RESET"
}

it() {
  printf "\n  %s" "$1"
}

assert() {
  if [ $RUN_EXIT -eq 0 ]; then
    printf " ✓"
  else
    printf "\n    Fail\n"
    ANY_ERRORS=true
  fi
}

assertFails() {
  if [ $RUN_EXIT -ne 0 ]; then
    printf " ✓"
  else
    printf "\n    Fail\n"
    ANY_ERRORS=true
  fi
}

assertEqual() {
  if [ "$1" = "$2" ]; then
    printf " ✓"
  else
    printf "\n   %s%s Error:%s Expected \"%s\" to equal \"%s\"\n" "$BOLD" "$RED" "$RESET" "$1" "$2"
    ANY_ERRORS=true
  fi
}
RUN_EXIT=0

do_build_in() {
  RUN_RESULT=$(cd tests/"$1" && "$script_directory"/projectdo -n build)
  RUN_EXIT=$?
}

do_run_in() {
  RUN_RESULT=$(cd tests/"$1" && "$script_directory"/projectdo -n run)
  RUN_EXIT=$?
}

do_test_in() {
  RUN_RESULT=$(cd tests/"$1" && "$script_directory"/projectdo -n test)
  RUN_EXIT=$?
}

do_print_tool_in() {
  RUN_RESULT=$(cd tests/"$1" && "$script_directory"/projectdo -n tool)
  RUN_EXIT=$?
}

if describe "cargo"; then
  if it "can run build"; then
    do_build_in "cargo"
    assert
    assertEqual "$RUN_RESULT" "cargo build"
  fi
  if it "can run run"; then
    do_run_in "cargo"
    assert
    assertEqual "$RUN_RESULT" "cargo run"
  fi
  if it "can run test"; then
    do_test_in "cargo"
    assert
    assertEqual "$RUN_RESULT" "cargo test"
  fi
  if it "can print tool"; then
    do_print_tool_in "cargo"
    assert
    assertEqual "$RUN_RESULT" "cargo"
  fi
  if it "passes additional arguments to the tool"; then
    RUN_RESULT=$(cd tests/cargo && ../../projectdo -n build --release)
    assertEqual "$RUN_RESULT" "cargo build --release"
  fi
fi

if describe "stack"; then
  if it "can run build"; then
    do_build_in "stack"
    assert
    assertEqual "$RUN_RESULT" "stack build"
  fi
  if it "can run run"; then
    do_run_in "stack"
    assert
    assertEqual "$RUN_RESULT" "stack run"
  fi
  if it "can run test"; then
    do_test_in "stack"
    assert
    assertEqual "$RUN_RESULT" "stack test"
  fi
  if it "can print tool"; then
    do_print_tool_in "stack"
    assert
    assertEqual "$RUN_RESULT" "stack"
  fi
fi

if describe "npm / yarn / pnpm / bun"; then
  if it "can run npm build if package.json with build script"; then
    do_build_in "npm"
    assert
    assertEqual "$RUN_RESULT" "npm run build"
  fi
  if it "can run npm start if package.json with start script"; then
    do_run_in "npm"
    assert
    assertEqual "$RUN_RESULT" "npm start"
  fi
  if it "can run npm test if package.json with test script"; then
    do_test_in "npm"
    assert
    assertEqual "$RUN_RESULT" "npm test"
  fi
  if it "uses yarn if yarn.lock is present"; then
    do_test_in "yarn"
    assert
    assertEqual "$RUN_RESULT" "yarn test"
  fi
  if it "uses pnpm if pnpm-lock.yaml is present"; then
    do_test_in "pnpm"
    assert
    assertEqual "$RUN_RESULT" "pnpm test"
  fi
  if it "uses bun if bun.lock is present"; then
    do_test_in "bun"
    assert
    assertEqual "$RUN_RESULT" "bun test"
  fi
  if it "uses bun if bun.lockb is present"; then
    do_test_in "bun-lockb"
    assert
    assertEqual "$RUN_RESULT" "bun test"
  fi
  if it "can run bun build if package.json with build script"; then
    do_build_in "bun"
    assert
    assertEqual "$RUN_RESULT" "bun run build"
  fi
  if it "can run bun start if package.json with start script"; then
    do_run_in "bun"
    assert
    assertEqual "$RUN_RESULT" "bun start"
  fi
  if it "can run bun test if package.json with test script"; then
    do_test_in "bun"
    assert
    assertEqual "$RUN_RESULT" "bun test"
  fi
  if it "can run pnpm build if package.json with build script"; then
    do_build_in "pnpm"
    assert
    assertEqual "$RUN_RESULT" "pnpm run build"
  fi
  if it "can run pnpm start if package.json with start script"; then
    do_run_in "pnpm"
    assert
    assertEqual "$RUN_RESULT" "pnpm start"
  fi
  if it "can run pnpm test if package.json with test script"; then
    do_test_in "pnpm"
    assert
    assertEqual "$RUN_RESULT" "pnpm test"
  fi
  if it "does not use npm if package.json contains no test script"; then
    do_test_in "npm-without-test"
    assert
    assertEqual "$RUN_RESULT" "make test"
  fi
  if it "can print tool"; then
    do_print_tool_in "npm"
    assert
    assertEqual "$RUN_RESULT" "npm"
    do_print_tool_in "yarn"
    assert
    assertEqual "$RUN_RESULT" "yarn"
    do_print_tool_in "pnpm"
    assert
    assertEqual "$RUN_RESULT" "pnpm"
    do_print_tool_in "bun"
    assert
    assertEqual "$RUN_RESULT" "bun"
  fi
  if it "detects yarn in workspace setup"; then
    do_print_tool_in "yarn-workspace/workspace-a"
    assert
    assertEqual "$RUN_RESULT" "yarn"
    do_print_tool_in "yarn-workspace/workspace-b"
    assert
    assertEqual "$RUN_RESULT" "yarn"
  fi
fi

if describe "just"; then
  if it "finds test target"; then
    do_test_in "just"
    assert
    assertEqual "$RUN_RESULT" "just test"
  fi
  if it "finds build target"; then
    do_build_in "just"
    assert
    assertEqual "$RUN_RESULT" "just build"
  fi
  if it "finds run target"; then
    do_run_in "just"
    assert
    assertEqual "$RUN_RESULT" "just run"
  fi
  if it "can print tool"; then
    do_print_tool_in "just"
    assert
    assertEqual "$RUN_RESULT" "just"
  fi
fi

if describe "make"; then
  if it "finds check target"; then
    do_test_in "make-check"
    assert
    assertEqual "$RUN_RESULT" "make check"
  fi
  if it "ignores file named check"; then
    do_test_in "make-check-with-check-file"
    assertFails
    assertEqual "$RUN_RESULT" "No way to test found :'("
  fi
  if it "finds check target if both target and file named check"; then
    do_test_in "make-check-with-check-file-and-target"
    assert
    assertEqual "$RUN_RESULT" "make check"
  fi
  if it "finds check target despite package.json"; then
    do_test_in "make-with-npm"
    assert
    assertEqual "$RUN_RESULT" "make check"
  fi
fi

if describe "nix-flake"; then
  if it "can build with nix"; then
    do_build_in "nix-flake"
    assert
    assertEqual "$RUN_RESULT" "nix build"
  fi
  if it "can run with nix"; then
    do_run_in "nix-flake"
    assert
    assertEqual "$RUN_RESULT" "nix run"
  fi
  if it "can test with nix"; then
    do_test_in "nix-flake"
    assert
    assertEqual "$RUN_RESULT" "nix flake check"
  fi
  if it "can print tool"; then
    do_print_tool_in "nix-flake"
    assert
    assertEqual "$RUN_RESULT" "nix"
  fi
fi

if describe "nix"; then
  if it "can build with nix-build"; then
    do_build_in "nix"
    assert
    assertEqual "$RUN_RESULT" "nix-build"
  fi
  if it "can print tool"; then
    do_print_tool_in "nix"
    assert
    assertEqual "$RUN_RESULT" "nix-build"
  fi
fi

if describe "go"; then
  if it "finds check target in magefile"; then
    do_test_in "mage"
    assert
    assertEqual "$RUN_RESULT" "mage check"
  fi
  if it "runs go test without magefile"; then
    do_test_in "go"
    assert
    assertEqual "$RUN_RESULT" "go test"
  fi
  if it "can run go run"; then
    do_run_in "go"
    assert
    assertEqual "$RUN_RESULT" "go run"
  fi
  if it "can run go build"; then
    do_build_in "go"
    assert
    assertEqual "$RUN_RESULT" "go build"
  fi
  if it "can print tool"; then
    do_print_tool_in "go"
    assert
    assertEqual "$RUN_RESULT" "go"
  fi
fi

if describe "python"; then
  if it "can build with poetry"; then
    do_build_in "poetry"
    assert
    assertEqual "$RUN_RESULT" "poetry build"
  fi
  if it "runs pytest with poetry"; then
    do_test_in "poetry"
    assert
    assertEqual "$RUN_RESULT" "poetry run pytest"
  fi
  if it "can print tool with poetry"; then
    do_print_tool_in "poetry"
    assert
    assertEqual "$RUN_RESULT" "poetry"
  fi
  if it "can run with uv"; then
    do_run_in "uv"
    assert
    assertEqual "$RUN_RESULT" "uv run"
  fi
  if it "can print tool with uv"; then
    do_print_tool_in "uv"
    assert
    assertEqual "$RUN_RESULT" "uv"
  fi
fi

if describe "latex"; then
  if it "can build with tectonic"; then
    do_build_in "tectonic"
    assert
    assertEqual "$RUN_RESULT" "tectonic -X build"
  fi
  if it "can print tool"; then
    do_print_tool_in "tectonic"
    assert
    assertEqual "$RUN_RESULT" "tectonic"
  fi
fi

if describe "meson"; then
  if it "can build with meson"; then
    do_build_in "meson"
    assert
    assertEqual "$RUN_RESULT" "meson compile"
  fi
  if it "can test with meson"; then
    do_test_in "meson"
    assert
    assertEqual "$RUN_RESULT" "meson test"
  fi
  if it "can print tool"; then
    do_print_tool_in "meson"
    assert
    assertEqual "$RUN_RESULT" "meson"
  fi
fi

if describe "gradle"; then
  if it "can build with gradle"; then
    do_build_in "gradle"
    assert
    assertEqual "$RUN_RESULT" "gradle build"
  fi
  if it "can run with gradle"; then
    do_run_in "gradle"
    assert
    assertEqual "$RUN_RESULT" "gradle run"
  fi
  if it "can test with gradle"; then
    do_test_in "gradle"
    assert
    assertEqual "$RUN_RESULT" "gradle test"
  fi
  if it "can print tool"; then
    do_print_tool_in "gradle"
    assert
    assertEqual "$RUN_RESULT" "gradle"
  fi
fi

if describe "cmake"; then
  if it "can test tool"; then
    do_test_in "cmake"
    assert
    assertEqual "$RUN_RESULT" "cmake --build . --target test"
  fi
  if it "can print tool"; then
    do_print_tool_in "cmake"
    assert
    assertEqual "$RUN_RESULT" "cmake"
  fi
fi

if describe "maven"; then
  if it "can print tool"; then
    do_print_tool_in "maven"
    assert
    assertEqual "$RUN_RESULT" "mvn"
  fi
fi

if describe "lein"; then
  if it "can test"; then
    do_test_in "lein"
    assert
    assertEqual "$RUN_RESULT" "lein test"
  fi
  if it "can run"; then
    do_run_in "lein"
    assert
    assertEqual "$RUN_RESULT" "lein run"
  fi
  if it "can print tool"; then
    do_print_tool_in "lein"
    assert
    assertEqual "$RUN_RESULT" "lein"
  fi
fi

if describe "dotnet csproj"; then
  if it "can build with dotnet"; then
    do_build_in "dotnet-csproj"
    assert
    assertEqual "$RUN_RESULT" "dotnet build"
  fi
  if it "can run with dotnet"; then
    do_run_in "dotnet-csproj"
    assert
    assertEqual "$RUN_RESULT" "dotnet run"
  fi
  if it "can test with dotnet"; then
    do_test_in "dotnet-csproj"
    assert
    assertEqual "$RUN_RESULT" "dotnet test"
  fi
  if it "can print tool"; then
    do_print_tool_in "dotnet-csproj"
    assert
    assertEqual "$RUN_RESULT" "dotnet"
  fi
fi

if describe "dotnet fsproj"; then
  if it "can build with dotnet"; then
    do_build_in "dotnet-fsproj"
    assert
    assertEqual "$RUN_RESULT" "dotnet build"
  fi
  if it "can run with dotnet"; then
    do_run_in "dotnet-fsproj"
    assert
    assertEqual "$RUN_RESULT" "dotnet run"
  fi
  if it "can test with dotnet"; then
    do_test_in "dotnet-fsproj"
    assert
    assertEqual "$RUN_RESULT" "dotnet test"
  fi
  if it "can print tool"; then
    do_print_tool_in "dotnet-fsproj"
    assert
    assertEqual "$RUN_RESULT" "dotnet"
  fi
fi

if describe "dotnet sln"; then
  if it "can build with dotnet"; then
    do_build_in "dotnet-sln"
    assert
    assertEqual "$RUN_RESULT" "dotnet build"
  fi
  if it "can run with dotnet"; then
    do_run_in "dotnet-sln"
    assert
    assertEqual "$RUN_RESULT" "dotnet run"
  fi
  if it "can test with dotnet"; then
    do_test_in "dotnet-sln"
    assert
    assertEqual "$RUN_RESULT" "dotnet test"
  fi
  if it "can print tool"; then
    do_print_tool_in "dotnet-sln"
    assert
    assertEqual "$RUN_RESULT" "dotnet"
  fi
fi

if describe "dune"; then
  if it "can build with dune"; then
    do_build_in "dune"
    assert
    assertEqual "$RUN_RESULT" "dune build"
  fi
  if it "can run with dune"; then
    do_run_in "dune"
    assert
    assertEqual "$RUN_RESULT" "dune exec"
  fi
  if it "can test with dune"; then
    do_test_in "dune"
    assert
    assertEqual "$RUN_RESULT" "dune runtest"
  fi
  if it "can print tool"; then
    do_print_tool_in "dune"
    assert
    assertEqual "$RUN_RESULT" "dune"
  fi
fi

if describe "lake"; then
  if it "can run build"; then
    do_build_in "lake"
    assert
    assertEqual "$RUN_RESULT" "lake build"
  fi
  if it "can run run"; then
    do_run_in "lake"
    assert
    assertEqual "$RUN_RESULT" "lake run"
  fi
  if it "can run test"; then
    do_test_in "lake"
    assert
    assertEqual "$RUN_RESULT" "lake test"
  fi
  if it "can print tool"; then
    do_print_tool_in "lake"
    assert
    assertEqual "$RUN_RESULT" "lake"
  fi
  if it "passes additional arguments to the tool"; then
    RUN_RESULT=$(cd tests/lake && ../../projectdo -n build --release)
    assertEqual "$RUN_RESULT" "lake build --release"
  fi
fi

if describe "build script"; then
  if it "can build with build script"; then
    do_build_in "build_script"
    assert
    assertEqual "$RUN_RESULT" "sh -c build.sh"
  fi
fi

echo ""

if [ $ANY_ERRORS = true ]; then
  exit 1
fi
