#!/usr/bin/env python3
"""
Claude Code PostToolUse hook — blocks `import psycopg2` in backend Python.

GenDesign uses psycopg v3 (`psycopg[binary]>=3.2.0`); psycopg2 is NOT installed,
so `import psycopg2` is a guaranteed `ModuleNotFoundError` at runtime — a recurring
bug class (see `.claude/rules/backend.md`). This catches it at edit-time, not on CI.

NOTE: the related `:param::type` cast trap is deliberately NOT checked here — the
codebase documents that anti-pattern in dozens of comments/test-docstrings
("never :param::type"), so a regex would false-positive constantly. That trap is
already covered by dedicated unit tests.

Wired in `.claude/settings.json` hooks.PostToolUse with matcher "Edit|Write|MultiEdit".
Watches `backend/**/*.py` (also `tradein-mvp/backend/**/*.py`, normalized to `backend/`).
"""
from __future__ import annotations

import json
import re
import sys
from pathlib import Path

WATCH_PATH_PREFIX = "backend/"
# Real import statement at line start (after optional indent); not a comment or a
# docstring sentence like "never import psycopg2" (which starts with a word, not `import`).
PSYCOPG2_RE = re.compile(
    r"(?m)^\s*(?:import\s+psycopg2|from\s+psycopg2(?:\.[\w.]+)?\s+import)\b"
)


def normalize_path(raw: str) -> str:
    """Repo-relative POSIX path anchored on the first `backend` segment."""
    parts = Path(raw).parts
    try:
        idx = parts.index("backend")
    except ValueError:
        return Path(raw).as_posix()
    return Path(*parts[idx:]).as_posix()


def main() -> int:
    try:
        payload = json.load(sys.stdin)
    except Exception:
        return 0

    tool_name = payload.get("tool_name") or payload.get("tool") or ""
    if tool_name not in {"Edit", "Write", "MultiEdit"}:
        return 0

    tool_input = payload.get("tool_input") or {}
    file_path = tool_input.get("file_path") or tool_input.get("path") or ""
    if not file_path:
        return 0

    norm = normalize_path(file_path)
    if not norm.startswith(WATCH_PATH_PREFIX) or not norm.endswith(".py"):
        return 0

    edits = tool_input.get("edits")
    if isinstance(edits, list):
        new_text = "\n".join(
            str(e.get("new_string") or "") for e in edits if isinstance(e, dict)
        )
    else:
        new_text = tool_input.get("new_string") or tool_input.get("content") or ""
    if not new_text:
        return 0

    for lineno, line in enumerate(new_text.splitlines(), start=1):
        if line.lstrip().startswith("#"):
            continue
        if PSYCOPG2_RE.match(line):
            msg = [
                f"Blocked: `import psycopg2` в {norm} (line {lineno}).",
                "psycopg2 НЕ установлен → ModuleNotFoundError. Используй `import psycopg` (v3).",
                "Bulk INSERT: `cur.executemany()` / COPY (НЕ execute_values). См. .claude/rules/backend.md.",
            ]
            print("\n".join(msg), file=sys.stderr)
            return 2

    return 0


if __name__ == "__main__":
    sys.exit(main())
