#!/bin/bash

show_help() {
  printf "Usage:\n"
  printf "  openwisp-qa-check --help\n"
  printf "  openwisp-qa-check {--migration-path <path> [--migrations-to-ignore <int>] | --skip-checkmigrations}\n"
  printf "            {[--message <commit-message>] | --skip-checkcommit }\n"
  printf "            {[--migration-module <module>] | --skip-checkmakemigrations}\n"
  printf "            [--skip-checkendline]\n"
  printf "            [--skip-flake8]\n"
  printf "            [--skip-isort]\n"
  printf "            [--skip-black]\n"
  printf "            [--skip-checkrst]\n"
  printf "            [--csslinter]\n"
  printf "            [--jslinter]\n"
  printf "\n"
  printf "Runs all checks for quality assurance. Unneeded checks may be skipped by passing --skip-<check-name> argument.\n"
  printf "csslinter and jslinter checks are skipped by default for backward compatibility. To run them pass --csslinter or --jslinter as an argument.\n"
  printf "\n"
  printf "General options:\n"
  printf "  --help\t\t\t: Show this help text\n"
  printf "Migration check options:\n"
  printf "  --migration-path <path>\t: Path to migrations/ folder\n"
  printf "  --migrations-to-ignore <int>\t: Number of migrations after which checking of migration file names should begin,\n"
  printf "\t\t\t\t  say, if checking needs to start after 0003_auto_20150410_3242.py value should be 3\n"
  printf "  --skip-checkmigrations\t: Skip migration name check\n"
  printf "  You can do multiple migration check by passing the arguments with space-delimited string array.\n"
  printf "Commit message check options:\n"
  printf "  --message <commit-message>\t: Commit message (by default is equal to latest commit name)\n"
  printf "  --migration-module <module>\t: Name of Migration Module\n"
  printf "  --skip-checkmakemigrations\t: Skip make migration check\n"
  printf "  --skip-checkcommit\t\t: Skip commit check\n"
  printf "Blank endlines check options:\n"
  printf "  --skip-checkendline\t\t: Skip blank endlines check\n"
  printf "Flake8 check options:\n"
  printf "  --skip-flake8\t\t\t: Skip flake8 check\n"
  printf "Isort check options:\n"
  printf "  --skip-isort\t\t\t: Skip isort check\n"
  printf "Black check options:\n"
  printf "  --skip-black\t\t\t: Skip black check\n"
  printf "ReStructuredText check options:\n"
  printf "  --skip-checkrst\t\t: Skip ReStructuredText check\n"
  printf "CSSlinter check options:\n"
  printf "  --csslinter\t\t\t: Runs prettier check on css files\n"
  printf "Jslinter check options:\n"
  printf "  --jslinter\t\t\t: Runs prettier check on javascript files\n"
}

echoerr() { echo "$@" 1>&2; }

runcheckendline() {
  flag=0
  # Read null-delimited paths so filenames containing whitespace stay intact.
  while IFS= read -r -d '' i; do
    if [ "$(tail -c 1 -- "$i")" != "" ]; then
      echo "$i needs newline at the end"
      flag=1
    fi
  done < <(find . -type f ! -path "*/*.egg-info/*" \
    ! -path "./.*" \
    ! -path "*/.venv/*" \
    ! -path "*/venv/*" \
    ! -path "*/env/*" \
    ! -path "*/.tox/*" \
    ! -path "*/htmlcov/*" \
    ! -path "*/.coverage*" \
    ! -path "*/coverage.xml" \
    ! -path "*.min.*" \
    ! -path "*.svg" -exec grep -Iq . {} \; -and -print0)
  if [ $flag -ne 0 ]; then
    echoerr "ERROR: Blank endline check failed!"
    FAILURE=1
  else
    echo "SUCCESS: Blank endline check successful!"
  fi
}

runprettiercheck() {
  if which prettier > /dev/null; then
    local files=()
    local patterns=(-name "*.yml" -o -name "*.yaml" -o -name "*.md")
    [[ "$1" != "true" ]] && patterns+=(-o -name "*.js" -o -name "*.json")
    [[ "$2" != "true" ]] && patterns+=(-o -name "*.css")
    # Collect null-delimited paths so formatter arguments preserve whitespace.
    while IFS= read -r -d '' file; do
      files+=("$file")
    done < <(find . -path "*/.venv/*" -prune -o -path "*/venv/*" -prune \
      -o -path "*/env/*" -prune -o -path "*/.tox/*" -prune -o -type f \
      \( "${patterns[@]}" \) -print0)

    # Prettier excludes node_modules by default unless --with-node-modules is used.
    if [ ${#files[@]} -gt 0 ]; then
      prettier --check "${files[@]}" --print-width 88 &&
        echo "SUCCESS: Prettier check successful!" ||
        {
          echoerr "ERROR: Prettier check failed! Hint: did you forget to run openwisp-qa-format?"
          echoerr "For more information refer to:"
          echoerr "https://openwisp.io/docs/dev/developer/contributing.html#css-and-javascript-code-conventions"
          FAILURE=1
        }
    fi
  else
    echoerr "MODULE NOT FOUND: Please install prettier, e.g.:"
    echoerr "sudo npm install -g prettier"
    FAILURE=1
  fi
}

runcheckmigrations() {
  if [ "$MIGRATION_PATH" == "" ]; then
    echoerr "SKIPPED: No migration path specified!"
    FAILURE=1
    return
  fi
  for i in "${!MIGRATION_PATH[@]}"; do
    path="${MIGRATION_PATH[$i]}"
    ignore="${MIGRATIONS_TO_IGNORE[$i]}"
    [ -n "$ignore" ] || ignore=0
    checkmigrations --migration-path "$path" --migrations-to-ignore "$ignore" &&
      echo "SUCCESS: Migration name check on \"$path\" with migrations-to-ignore: $ignore successful!" ||
      {
        echoerr "ERROR: Migration name check on \"$path\" with migrations-to-ignore: $ignore failed!"
        FAILURE=1
      }
  done
}

runflake8() {
  flake8 --extend-exclude=.venv,venv,env,.tox && echo "SUCCESS: Flake8 check successful!" ||
    {
      echoerr "ERROR: Flake8 check failed!"
      FAILURE=1
    }
}

runrstcheck() {
  local files=()
  # Build the file list manually so docstrfmt never sees node_modules or
  # hidden directories; its built-in exclude matching misses deeply nested paths.
  while IFS= read -r -d '' file; do
    files+=("$file")
  done < <(find . -path "*/.*" -prune -o -path "*/node_modules/*" -prune \
    -o -path "*/venv/*" -prune -o -path "*/env/*" -prune \
    -o -type f \( -name "*.py" -o -name "*.rst" \) -print0)
  if [ ${#files[@]} -gt 0 ]; then
    local cmd=(docstrfmt --no-docstring-trailing-line -i -c -l 74 -s "=-~+^\"'.:")
    "${cmd[@]}" --quiet "${files[@]}" || {
      echoerr "ERROR: ReStructuredText check failed!"
      "${cmd[@]}" "${files[@]}"
      FAILURE=1
      return
    }
  fi
  echo "SUCCESS: ReStructuredText check successful!"
}

runisort() {
  isort --check-only --diff --quiet --extend-skip .venv --extend-skip venv \
    --extend-skip env --extend-skip .tox . &&
    echo "SUCCESS: Isort check successful!" ||
    {
      echoerr "ERROR: Isort check failed! Hint: did you forget to run openwisp-qa-format?"
      FAILURE=1
    }
}

runblack() {
  # Black excludes .venv, venv, and .tox by default. Extend its defaults to
  # cover env and ENV without replacing the built-in exclusions.
  black --check --diff --quiet --extend-exclude '/(env|ENV)/' . &&
    echo "SUCCESS: Black check successful!" ||
    {
      echoerr "ERROR: Black check failed! Hint: did you forget to run openwisp-qa-format?"
      FAILURE=1
    }
}

runcheckcommit() {
  if ! command -v cz >/dev/null 2>&1; then
    echoerr "ERROR: Commitizen (cz) is not installed or not available in PATH."
    echoerr "Please install it to enable commit message enforcement."
    FAILURE=1
    return
  fi
  openwisp-commit --check
}

runcheckpendingmigrations() {
  if [ ! -f "./tests/manage.py" ]; then
    echo "File manage.py not found, skipping Make Migration Check."
  else
    OUTPUT=$(python tests/manage.py makemigrations --dry-run $MODULE)
    echo $OUTPUT | grep "No changes detected" &>/dev/null &&
      echo "SUCCESS: Migrations check successful!" ||
      {
        echo $OUTPUT
        echoerr "ERROR: Migrations check failed! Models' changes not migrated, please run './manage.py makemigrations' to solve the issue!"
        FAILURE=1
      }
  fi
}

## Main

SKIP_CHECKENDLINE=false
SKIP_CHECKMIGRATIONS=false
SKIP_MAKEMIGRATIONS=false
SKIP_FLAKE8=false
SKIP_ISORT=false
SKIP_RSTCHECK=false
SKIP_BLACK=false
SKIP_CHECKCOMMIT=false
SKIP_CSS_LINTER=true
SKIP_JS_LINTER=true
MIGRATION_PATH=""
MIGRATIONS_TO_IGNORE="0"
COMMIT_MESSAGE=$(git log $TRAVIS_PULL_REQUEST_SHA --format=%B -n 1)
FAILURE=0
MODULE=""

while [ "$1" != "" ]; do
  case "$1" in
  --skip-checkendline)
    SKIP_CHECKENDLINE=true
    ;;
  --skip-checkmigrations)
    SKIP_CHECKMIGRATIONS=true
    ;;
  --migration-path)
    shift
    MIGRATION_PATH=($1)
    ;;
  --migrations-to-ignore)
    shift
    MIGRATIONS_TO_IGNORE=($1)
    ;;
  --skip-flake8)
    SKIP_FLAKE8=true
    ;;
  --skip-isort)
    SKIP_ISORT=true
    ;;
  --skip-black)
    SKIP_BLACK=true
    ;;
  --skip-checkrst)
    SKIP_RSTCHECK=true
    ;;
  --skip-checkcommit)
    SKIP_CHECKCOMMIT=true
    ;;
  --csslinter)
    SKIP_CSS_LINTER=false
    ;;
  --jslinter)
    SKIP_JS_LINTER=false
    ;;
  --message)
    shift
    COMMIT_MESSAGE="$1"
    ;;
  --skip-checkmakemigrations)
    SKIP_MAKEMIGRATIONS=true
    ;;
  --migration-module)
    shift
    MODULE="$1"
    ;;
  --help)
    show_help
    exit
    ;;
  *)
    printf "Unknown argument: %s\n" "$1"
    printf "\n"
    show_help
    exit 1
    ;;
  esac
  shift
done

set -e

if ! $SKIP_CHECKENDLINE; then
  runcheckendline
fi

if ! $SKIP_CHECKMIGRATIONS; then
  runcheckmigrations
fi

if ! $SKIP_ISORT; then
  runisort
fi

if ! $SKIP_BLACK; then
  runblack
fi

if ! $SKIP_FLAKE8; then
  runflake8
fi

if [[ "$SKIP_JS_LINTER" != "true" || "$SKIP_CSS_LINTER" != "true" ]]; then
  runprettiercheck "$SKIP_JS_LINTER" "$SKIP_CSS_LINTER"
fi

if ! $SKIP_RSTCHECK; then
  runrstcheck
fi

if ! $SKIP_CHECKCOMMIT; then
  runcheckcommit
fi

if ! $SKIP_MAKEMIGRATIONS; then
  runcheckpendingmigrations
fi

exit $FAILURE
