#!/usr/bin/env python3
"""
BreakPilot Pre-Commit Check Script

Prüft vor einem Commit:
1. Sind alle geänderten Dateien dokumentiert?
2. Haben alle geänderten Funktionen Tests?
3. Sind Security-relevante Änderungen markiert?
4. Sind ADRs für neue Module vorhanden?

Verwendung:
  python3 scripts/pre-commit-check.py           # Prüft staged files
  python3 scripts/pre-commit-check.py --all     # Prüft alle uncommitted changes
  python3 scripts/pre-commit-check.py --fix     # Versucht automatische Fixes

Exit Codes:
  0 - Alles OK
  1 - Warnungen (nicht blockierend)
  2 - Fehler (blockierend)
"""

import subprocess
import sys
import os
import re
from pathlib import Path
from typing import List, Tuple, Dict

# ============================================
# Konfiguration
# ============================================

# Dateien die dokumentiert sein sollten
DOC_REQUIRED_PATTERNS = {
    r"consent-service/internal/handlers/.*\.go$": "docs/api/consent-service-api.md",
    r"backend/.*_api\.py$": "docs/api/backend-api.md",
    r"website/app/api/.*/route\.ts$": "docs/api/frontend-api.md",
}

# Dateien die Tests haben sollten
TEST_REQUIRED_PATTERNS = {
    r"consent-service/internal/services/([^/]+)\.go$": r"consent-service/internal/services/\1_test.go",
    r"backend/([^/]+)\.py$": r"backend/tests/test_\1.py",
}

# Neue Module die ADRs benötigen
ADR_REQUIRED_PATTERNS = [
    r"consent-service/internal/services/\w+_service\.go$",
    r"backend/[^/]+_service\.py$",
    r"website/app/admin/[^/]+/page\.tsx$",
]

# Security-relevante Patterns
SECURITY_PATTERNS = [
    (r"password|secret|token|api_key|apikey", "Credentials"),
    (r"exec\(|eval\(|subprocess|os\.system", "Code Execution"),
    (r"sql|query.*\+|f['\"].*select|f['\"].*insert", "SQL Injection Risk"),
]

# ============================================
# Git Helpers
# ============================================

def get_staged_files() -> List[str]:
    """Gibt die für Commit vorgemerkten Dateien zurück."""
    result = subprocess.run(
        ["git", "diff", "--cached", "--name-only", "--diff-filter=ACMR"],
        capture_output=True, text=True
    )
    return [f.strip() for f in result.stdout.strip().split("\n") if f.strip()]

def get_all_changed_files() -> List[str]:
    """Gibt alle geänderten Dateien zurück (staged + unstaged)."""
    result = subprocess.run(
        ["git", "diff", "--name-only", "--diff-filter=ACMR", "HEAD"],
        capture_output=True, text=True
    )
    return [f.strip() for f in result.stdout.strip().split("\n") if f.strip()]

def get_file_content(filepath: str) -> str:
    """Liest den Inhalt einer Datei."""
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            return f.read()
    except Exception:
        return ""

def file_exists(filepath: str) -> bool:
    """Prüft ob eine Datei existiert."""
    return Path(filepath).exists()

# ============================================
# Checks
# ============================================

def check_documentation(files: List[str]) -> List[Tuple[str, str]]:
    """Prüft ob Dokumentation für geänderte Dateien existiert."""
    issues = []

    for filepath in files:
        for pattern, doc_file in DOC_REQUIRED_PATTERNS.items():
            if re.match(pattern, filepath):
                if not file_exists(doc_file):
                    issues.append((filepath, f"Dokumentation fehlt: {doc_file}"))
                break

    return issues

def check_tests(files: List[str]) -> List[Tuple[str, str]]:
    """Prüft ob Tests für geänderte Dateien existieren."""
    issues = []

    for filepath in files:
        # Überspringe Test-Dateien selbst
        if "_test.go" in filepath or "test_" in filepath or "__tests__" in filepath:
            continue

        for pattern, test_pattern in TEST_REQUIRED_PATTERNS.items():
            match = re.match(pattern, filepath)
            if match:
                test_file = re.sub(pattern, test_pattern, filepath)
                if not file_exists(test_file):
                    issues.append((filepath, f"Test fehlt: {test_file}"))
                break

    return issues

def check_adrs(files: List[str]) -> List[Tuple[str, str]]:
    """Prüft ob ADRs für neue Module vorhanden sind."""
    issues = []
    adr_dir = Path("docs/adr")

    for filepath in files:
        for pattern in ADR_REQUIRED_PATTERNS:
            if re.match(pattern, filepath):
                # Prüfe ob es ein neues File ist (nicht nur Änderung)
                result = subprocess.run(
                    ["git", "diff", "--cached", "--name-status", filepath],
                    capture_output=True, text=True
                )
                if result.stdout.startswith("A"):  # Added
                    # Prüfe ob es einen ADR dafür gibt
                    module_name = Path(filepath).stem
                    adr_exists = any(
                        adr_dir.glob(f"ADR-*{module_name}*.md")
                    ) if adr_dir.exists() else False

                    if not adr_exists:
                        issues.append((
                            filepath,
                            f"Neues Modul ohne ADR. Erstelle: docs/adr/ADR-NNNN-{module_name}.md"
                        ))
                break

    return issues

def check_security(files: List[str]) -> List[Tuple[str, str]]:
    """Prüft auf security-relevante Änderungen."""
    warnings = []

    for filepath in files:
        content = get_file_content(filepath)
        content_lower = content.lower()

        for pattern, category in SECURITY_PATTERNS:
            if re.search(pattern, content_lower):
                warnings.append((filepath, f"Security-Review empfohlen: {category}"))
                break  # Nur eine Warnung pro Datei

    return warnings

# ============================================
# Main
# ============================================

def print_section(title: str, items: List[Tuple[str, str]], icon: str = "⚠️"):
    """Gibt eine Sektion aus."""
    if items:
        print(f"\n{icon} {title}:")
        for filepath, message in items:
            print(f"   {filepath}")
            print(f"      → {message}")

def main():
    import argparse

    parser = argparse.ArgumentParser(description="BreakPilot Pre-Commit Check")
    parser.add_argument("--all", action="store_true", help="Check all changed files, not just staged")
    parser.add_argument("--fix", action="store_true", help="Attempt automatic fixes")
    parser.add_argument("--strict", action="store_true", help="Treat warnings as errors")
    args = parser.parse_args()

    print("=" * 60)
    print("BREAKPILOT PRE-COMMIT CHECK")
    print("=" * 60)

    # Dateien ermitteln
    files = get_all_changed_files() if args.all else get_staged_files()

    if not files:
        print("\n✅ Keine Dateien zu prüfen.")
        return 0

    print(f"\nPrüfe {len(files)} Datei(en)...")

    # Checks ausführen
    doc_issues = check_documentation(files)
    test_issues = check_tests(files)
    adr_issues = check_adrs(files)
    security_warnings = check_security(files)

    # Ergebnisse ausgeben
    has_errors = False
    has_warnings = False

    if doc_issues:
        print_section("Fehlende Dokumentation", doc_issues, "📝")
        has_warnings = True

    if test_issues:
        print_section("Fehlende Tests", test_issues, "🧪")
        has_warnings = True

    if adr_issues:
        print_section("Fehlende ADRs", adr_issues, "📋")
        has_warnings = True

    if security_warnings:
        print_section("Security-Hinweise", security_warnings, "🔒")
        has_warnings = True

    # Zusammenfassung
    print("\n" + "=" * 60)

    if not has_errors and not has_warnings:
        print("✅ Alle Checks bestanden!")
        return 0

    total_issues = len(doc_issues) + len(test_issues) + len(adr_issues)
    total_warnings = len(security_warnings)

    print(f"📊 Zusammenfassung:")
    print(f"   Issues:   {total_issues}")
    print(f"   Warnings: {total_warnings}")

    if args.strict and (has_errors or has_warnings):
        print("\n❌ Commit blockiert (--strict mode)")
        return 2

    if has_errors:
        print("\n❌ Commit blockiert wegen Fehlern")
        return 2

    print("\n⚠️  Commit möglich, aber Warnings beachten!")
    return 1

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