#!/usr/bin/env bash

set -euo pipefail

REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"

set +e
output="$(
  python3 - "$REPO_ROOT" <<'PY'
from __future__ import annotations

import ast
import os
from pathlib import Path
import subprocess
import sys
import tokenize

repo_root = Path(sys.argv[1])
tracked = subprocess.run(
    [
        "git",
        "-C",
        str(repo_root),
        "ls-files",
        "-z",
        "--",
        ":(glob)**/*.py",
    ],
    check=False,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
)
if tracked.returncode != 0:
    print(os.fsdecode(tracked.stderr), file=sys.stderr)
    raise SystemExit(2)

findings: set[tuple[str, int, str]] = set()
scan_errors: list[str] = []
for raw_path in tracked.stdout.split(b"\0"):
    if not raw_path:
        continue
    relative = Path(os.fsdecode(raw_path))
    path = repo_root / relative
    if not path.is_file():
        continue
    try:
        with tokenize.open(path) as source_file:
            source = source_file.read()
        if "unittest" not in source or "mock" not in source:
            continue
        tree = ast.parse(source, filename=relative.as_posix())
    except (OSError, SyntaxError, UnicodeError) as error:
        scan_errors.append(f"{relative.as_posix()}: {error}")
        continue

    source_lines = source.splitlines()
    unittest_aliases = {
        alias.asname or "unittest"
        for node in ast.walk(tree)
        if isinstance(node, ast.Import)
        for alias in node.names
        if alias.name == "unittest"
    }
    for node in ast.walk(tree):
        forbidden = False
        if isinstance(node, ast.Import):
            forbidden = any(
                alias.name == "unittest.mock"
                or alias.name.startswith("unittest.mock.")
                for alias in node.names
            )
        elif isinstance(node, ast.ImportFrom) and node.level == 0:
            module = node.module or ""
            forbidden = (
                module == "unittest.mock"
                or module.startswith("unittest.mock.")
                or (
                    module == "unittest"
                    and any(alias.name == "mock" for alias in node.names)
                )
            )
        elif (
            isinstance(node, ast.Attribute)
            and node.attr == "mock"
            and isinstance(node.value, ast.Name)
            and node.value.id in unittest_aliases
        ):
            forbidden = True

        if forbidden:
            line = source_lines[node.lineno - 1].strip()
            findings.add((relative.as_posix(), node.lineno, line))

if scan_errors:
    print("failed to parse tracked Python source:", file=sys.stderr)
    print("\n".join(sorted(scan_errors)), file=sys.stderr)
    raise SystemExit(2)
if findings:
    for relative, line_number, line in sorted(findings):
        print(f"{relative}:{line_number}: {line}")
    raise SystemExit(1)
print("ok: unittest.mock is absent from tracked Python source")
PY
)"
status=$?
set -e

case "$status" in
0)
  printf '%s\n' "$output"
  ;;
1)
  echo "::error title=Forbidden Python dependency::unittest.mock must not be added to tracked Python source."
  printf '%s\n' "$output"
  exit 1
  ;;
*)
  printf 'failed to scan for unittest.mock usage:\n%s\n' "$output" >&2
  exit "$status"
  ;;
esac
