#!/usr/bin/env python3
"""
Anti-smuggle CI gate: detect vacuous theorems that pass `lake build`.

Catches patterns like:
  - theorem body is `:= rfl` with no prior computation
  - equality where both sides are syntactically identical (bound var rename)
  - `:=` followed by a reference to a trivially-true definition
  - `by` block with only `rfl` or `simp` that does no work

Usage:
  python3 scripts/anti_smuggle_check.py              # scan formal/
  python3 scripts/anti_smuggle_check.py --ci          # strict mode (exit 1 on any finding)
  python3 scripts/anti_smuggle_check.py --file foo.lean  # single file
"""

import argparse
import os
import re
import sys
from pathlib import Path

SUSPICIOUS_RFL = re.compile(
    r'(theorem|lemma)\s+(\w+)\s*[^:]*:\s*([^=]+)=\s*([^=]+)\s*:=\s*rfl\b'
)
SUSPICIOUS_BY_RFL = re.compile(
    r'(theorem|lemma)\s+(\w+)\s*[^:]*:\s*([^=]+)=\s*([^=]+)\s*:=\s*by\s+rfl\b'
)
SUSPICIOUS_RENAME = re.compile(
    r'(theorem|lemma)\s+(\w+)\s*[^:]*:\s*'
    r'∀[^,]+,([^,]+),([^,]+),([^,]+),([^,]+),([^,]+),([^,]+)\.\s*'
    r'\(∑[^:]+\s*=\s*∑[^:]+\)'
)

BARE_RFL_LINE = re.compile(r'^\s*(:=|=>)\s*rfl\s*$')
LAMBDA_RFL = re.compile(
    r'^\s*:=\s*(fun\s+(\w+\s+)+\w+\s*=>\s*rfl|λ\s+(\w+\s+)+\w+\s*=>\s*rfl)\s*$'
)
EMPTY_SORRY = re.compile(r'^\s*(:=|=>)\s*(by\s+)?sorry\s*$')


def check_file(path: str, ci_mode: bool) -> int:
    """Check a single .lean file for smuggle patterns. Returns finding count."""
    findings = 0
    try:
        with open(path, 'r') as f:
            content = f.read()
    except Exception as e:
        print(f"  ERROR reading {path}: {e}")
        return 0

    rel = os.path.relpath(path)

    # 1. Check for `:= rfl` after an equality
    for m in SUSPICIOUS_RFL.finditer(content):
        lhs = m.group(3).strip()
        rhs = m.group(4).strip()
        if lhs == rhs:
            findings += 1
            print(f"  [SMUGGLE] {rel}:{line_of(content, m.start())} "
                  f"theorem '{m.group(2)}' is `:= rfl` with identical LHS/RHS '{lhs}'")

    # 2. Check for `:= by rfl`
    for m in SUSPICIOUS_BY_RFL.finditer(content):
        lhs = m.group(3).strip()
        rhs = m.group(4).strip()
        if lhs == rhs:
            findings += 1
            print(f"  [SMUGGLE] {rel}:{line_of(content, m.start())} "
                  f"theorem '{m.group(2)}' is `:= by rfl` with identical LHS/RHS")

    # 3. Check for the YB pattern: sums with bound variable rename
    for m in SUSPICIOUS_RENAME.finditer(content):
        # This is a specific pattern: ∀ ... (sum ... r ... = sum ... s ...)
        # It might still be non-trivial if the body does work; flag for review
        findings += 1
        print(f"  [SMUGGLE] {rel}:{line_of(content, m.start())} "
              f"theorem '{m.group(2)}' has ∀-quantified equation with sums "
              f"(possible bound-variable rename)")

    # 4. Check for trivial theorem bodies: `:= rfl`, `:= fun ... => rfl`, `:= by rfl`
    lines = content.split('\n')
    for i, line in enumerate(lines):
        stripped = line.strip()

        # Check single-line `:= rfl`
        if re.match(r'^\s*:=\s*rfl\s*$', stripped):
            surrounding = '\n'.join(lines[max(0, i - 3):i + 1])
            if re.search(r'(theorem|lemma)\s+\w+', surrounding):
                findings += 1
                print(f"  [SMUGGLE] {rel}:{i + 1} bare `:= rfl` as theorem body")

        # Check `:= fun ... => rfl` (possibly multi-line: `:=` on prev line, `fun ... => rfl` on this line)
        if re.match(r'^\s*fun\s+(\w+\s+)+\w+\s*=>\s*rfl\s*$', stripped) or \
           re.match(r'^\s*λ\s+(\w+\s+)+\w+\s*=>\s*rfl\s*$', stripped):
            surrounding = '\n'.join(lines[max(0, i - 2):i + 1])
            if re.search(r'(theorem|lemma)\s+\w+|:=\s*$', surrounding, re.MULTILINE):
                findings += 1
                print(f"  [SMUGGLE] {rel}:{i + 1} `fun ... => rfl` lambda as theorem body")

        if EMPTY_SORRY.match(stripped) and ci_mode:
            findings += 1
            print(f"  [WARN] {rel}:{i + 1} bare sorry (not an axiom with TODO)")

    return findings


def line_of(content: str, pos: int) -> int:
    """Return 1-indexed line number for a character position."""
    return content[:pos].count('\n') + 1


def check_dir(root: str, ci_mode: bool) -> int:
    """Recursively check all .lean files under root."""
    total = 0
    for dirpath, _, filenames in os.walk(root):
        for fn in filenames:
            if fn.endswith('.lean'):
                total += check_file(os.path.join(dirpath, fn), ci_mode)
    return total


def main():
    parser = argparse.ArgumentParser(
        description="Anti-smuggle CI gate for Lean theorem vacuity"
    )
    parser.add_argument('paths', nargs='*', default=['formal'],
                        help='Files or directories to check')
    parser.add_argument('--ci', action='store_true',
                        help='Strict mode: exit 1 on any finding')
    parser.add_argument('--file', type=str,
                        help='Check a single file')
    args = parser.parse_args()

    total = 0
    if args.file:
        total += check_file(args.file, args.ci)
    for path in args.paths:
        if os.path.isfile(path):
            total += check_file(path, args.ci)
        elif os.path.isdir(path):
            total += check_dir(path, args.ci)
        elif path != 'formal':
            print(f"WARNING: {path} not found", file=sys.stderr)

    if total == 0:
        print("Anti-smuggle check: PASSED (no vacuities detected)")
        return 0
    else:
        msg = f"Anti-smuggle check: {total} potential vacuit{'y' if total == 1 else 'ies'} found"
        if args.ci:
            print(f"FAIL: {msg}", file=sys.stderr)
            return 1
        else:
            print(f"WARNING: {msg}")
            return 0


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