#!/usr/bin/env python3
# allow: no-sut-import — guardian; scans the codebase for raw SQL outside allowed locations
"""Test to verify no raw SQL is used in the codebase (except in allowed locations)."""

import sys
from pathlib import Path

sys.path.insert(
    0,
    str(Path(__file__).parent.parent.parent.resolve()),
)

import re


def check_file_for_raw_sql(filepath):
    """Check a single file for raw SQL usage."""
    with open(filepath, "r", encoding="utf-8") as f:
        content = f.read()

    # Skip if it's a test file, migration file, or database-specific files
    filepath_str = str(filepath).lower()
    if any(
        skip in filepath_str
        for skip in [
            "test",
            "migration",
            "encrypted_db.py",
            "sqlcipher_utils.py",
            "thread_local_session.py",
            "queue/processor.py",
            "database/initialize.py",  # Schema migrations using DDL
            "alembic_runner.py",  # Migration runner: drops orphan _alembic_tmp_* tables (#3817), toggles foreign_keys (#3990)
            "auth_db.py",  # SQLAlchemy DDL (CreateTable/CreateIndex), not raw SQL
            "backup_service.py",  # SQLCipher ATTACH/DETACH/export operations
            "journal_quality/db.py",  # Read-only SQLite DB build + PRAGMA user_version
        ]
    ):
        return []

    violations = []
    lines = content.split("\n")

    # Patterns that indicate raw SQL
    sql_patterns = [
        r"cursor\.execute\s*\(",
        r"conn\.execute\s*\(",
        r'session\.execute\s*\(\s*["\']',
        r'["\']SELECT\s+.*FROM\s+',
        r'["\']INSERT\s+INTO\s+',
        r'["\']UPDATE\s+.*SET\s+',
        r'["\']DELETE\s+FROM\s+',
        r'["\']CREATE\s+TABLE\s+',
        r'["\']DROP\s+TABLE\s+',
        r'["\']ALTER\s+TABLE\s+',
    ]

    # Allowed patterns (SQLCipher PRAGMAs and simple connection tests)
    allowed_patterns = [
        r"PRAGMA\s+(cipher_|quick_check|rekey)",  # SQLCipher pragmas
        r"SELECT\s+1(?:\s|$|;)",  # Simple connection test
        r'text\s*\(\s*["\'](?:SELECT\s+1|PRAGMA)',  # SQLAlchemy text() with allowed queries
    ]

    for line_num, line in enumerate(lines, 1):
        for pattern in sql_patterns:
            if re.search(pattern, line, re.IGNORECASE):
                # Check if it's in a comment or docstring
                stripped = line.strip()
                if (
                    stripped.startswith("#")
                    or stripped.startswith('"""')
                    or stripped.startswith("'''")
                ):
                    continue

                # Check if it's an allowed pattern
                is_allowed = False
                for allowed in allowed_patterns:
                    if re.search(allowed, line, re.IGNORECASE):
                        is_allowed = True
                        break

                if not is_allowed:
                    violations.append((line_num, line.strip()))

    return violations


def test_no_raw_sql_in_src():
    """Test that no raw SQL is used in src directory (except allowed patterns).

    Allowed exceptions:
    - encrypted_db.py file (SQLCipher-specific operations)
    - PRAGMA commands for SQLCipher (cipher_*, quick_check, rekey)
    - Simple connection tests (SELECT 1)
    """
    src_path = Path(__file__).parent.parent.parent / "src"
    violations = {}

    # Walk through all Python files
    for filepath in src_path.rglob("*.py"):
        file_violations = check_file_for_raw_sql(filepath)
        if file_violations:
            violations[str(filepath)] = file_violations

    # Report violations
    if violations:
        print("\n❌ Found raw SQL in the following files:")
        for filepath, file_violations in violations.items():
            print(f"\n{filepath}:")
            for line_num, line in file_violations:
                print(f"  Line {line_num}: {line[:80]}...")

        # This should fail the test
        assert False, f"Found raw SQL in {len(violations)} files"
    else:
        print(
            "✓ No raw SQL found in src directory (excluding allowed patterns)"
        )


def test_orm_imports_used():
    """Files that touch the DB should reach for the ORM, not raw SQL.

    Walk the src tree, collect every file that performs a DB operation
    (``get_db_session``, references to ``ResearchHistory`` or
    ``ResearchResource``), and require that the vast majority of them
    also import SQLAlchemy or use ORM-style query helpers.

    The ratio guard is intentionally loose (≥80%) because a handful of
    helper modules legitimately reference these names without
    constructing queries themselves (e.g. type-only imports, fixtures,
    docstring examples).
    """
    src_path = Path(__file__).parent.parent.parent / "src"
    files_with_db_operations = []
    files_with_orm_imports = []

    for filepath in src_path.rglob("*.py"):
        with open(filepath, "r", encoding="utf-8") as f:
            content = f.read()

        # Check if file has database operations
        if any(
            pattern in content
            for pattern in [
                "get_db_session",
                "ResearchHistory",
                "ResearchResource",
            ]
        ):
            files_with_db_operations.append(filepath)

            # Check if it has ORM imports
            if any(
                pattern in content
                for pattern in [
                    "from sqlalchemy",
                    "import.*session",
                    ".query(",
                    ".filter",
                ]
            ):
                files_with_orm_imports.append(filepath)

    # Sanity: at least some files matched the DB-operation heuristic, or
    # the test has silently stopped finding anything (e.g., a refactor
    # renamed the symbols above and the test would otherwise pass with 0
    # files inspected).
    assert files_with_db_operations, (
        "No files with DB operations detected — patterns may be stale"
    )

    orm_ratio = len(files_with_orm_imports) / len(files_with_db_operations)
    assert orm_ratio >= 0.8, (
        f"Only {len(files_with_orm_imports)}/{len(files_with_db_operations)} "
        f"files with DB operations use ORM imports (ratio {orm_ratio:.2f} < 0.80)"
    )


def test_models_imported_from_correct_location():
    """Test that models are imported from the consolidated location."""
    src_path = Path(__file__).parent.parent.parent / "src"
    incorrect_imports = {}

    # Old import patterns that should not be used
    old_patterns = [
        r"from.*web\.models\.database import.*(?:Research|ResearchHistory)",
        r"from.*web\.database\.models import",
        r"from.*benchmarks\.models\.benchmark_models import",
    ]

    for filepath in src_path.rglob("*.py"):
        with open(filepath, "r", encoding="utf-8") as f:
            content = f.read()

        violations = []
        lines = content.split("\n")

        for line_num, line in enumerate(lines, 1):
            for pattern in old_patterns:
                if re.search(pattern, line):
                    violations.append((line_num, line.strip()))

        if violations:
            incorrect_imports[str(filepath)] = violations

    if incorrect_imports:
        print("\n❌ Found imports from old model locations:")
        for filepath, violations in incorrect_imports.items():
            print(f"\n{filepath}:")
            for line_num, line in violations:
                print(f"  Line {line_num}: {line}")

        # This should fail the test
        assert False, (
            f"Found {len(incorrect_imports)} files with incorrect model imports"
        )
    else:
        print("✓ All models imported from correct location (database.models)")


if __name__ == "__main__":
    test_no_raw_sql_in_src()
    test_orm_imports_used()
    test_models_imported_from_correct_location()
    print("\n✅ All SQL/ORM compliance tests passed!")
