"""
Tests for the raw-SQL check inside the custom-checks pre-commit hook.

Covers regressions for previously-missed patterns:
- f-string SQL (e.g. f"SELECT ... FROM ...") — highest-risk form (injection)
- trailing `# SQL` comments that previously bypassed the check
- auth_db.py allowlist inconsistency between execute and statement patterns
- loose "test" substring filename matching (e.g. attestation_service.py)
"""

import subprocess
import sys
import tempfile
from pathlib import Path


HOOK_SCRIPT = (
    Path(__file__).parent.parent.parent
    / ".pre-commit-hooks"
    / "custom-checks.py"
)


def _run_hook(content: str, filename: str) -> subprocess.CompletedProcess:
    """Write content to a temp file with a specific basename (may include subdirs) and run the hook."""
    with tempfile.TemporaryDirectory() as d:
        path = Path(d) / filename
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(content)
        return subprocess.run(
            [sys.executable, str(HOOK_SCRIPT), str(path)],
            capture_output=True,
            text=True,
        )


class TestFlagsRawSql:
    """Patterns that must be flagged by the hook."""

    def test_plain_cursor_execute(self):
        result = _run_hook(
            'cursor.execute("SELECT * FROM users")\n',
            "service.py",
        )
        assert result.returncode == 1
        assert "Raw SQL" in result.stdout

    def test_fstring_cursor_execute(self):
        """f-string SQL is an injection risk — must not be silently allowed."""
        result = _run_hook(
            'cursor.execute(f"DELETE FROM users WHERE id = {user_id}")\n',
            "service.py",
        )
        assert result.returncode == 1
        assert "Raw SQL" in result.stdout

    def test_fstring_conn_execute(self):
        result = _run_hook(
            'conn.execute(f"INSERT INTO logs VALUES ({val})")\n',
            "service.py",
        )
        assert result.returncode == 1

    def test_fstring_session_execute(self):
        result = _run_hook(
            "session.execute(f\"UPDATE users SET name = '{n}'\")\n",
            "service.py",
        )
        assert result.returncode == 1

    def test_trailing_sql_comment_does_not_bypass(self):
        """Previously, `# SQL` anywhere on the line disabled the whole check."""
        result = _run_hook(
            'cursor.execute("DELETE FROM users")  # SQL cleanup\n',
            "service.py",
        )
        assert result.returncode == 1

    def test_attestation_filename_not_exempted(self):
        """'attestation' contains the substring 'test' but is not a test file."""
        result = _run_hook(
            'cursor.execute("SELECT * FROM certificates")\n',
            "attestation_service.py",
        )
        assert result.returncode == 1

    def test_uppercase_f_prefix(self):
        """Uppercase F-string prefix must also be caught (re.IGNORECASE)."""
        result = _run_hook(
            'query = F"SELECT * FROM users WHERE id = {uid}"\n',
            "service.py",
        )
        assert result.returncode == 1
        assert "Raw SQL" in result.stdout

    def test_rf_prefix(self):
        """rf"..." raw-f-string prefix variant must be caught."""
        result = _run_hook(
            'query = rf"DELETE FROM users WHERE id = {uid}"\n',
            "service.py",
        )
        assert result.returncode == 1

    def test_fr_prefix(self):
        """fr"..." f-raw-string prefix variant must be caught."""
        result = _run_hook(
            "query = fr\"UPDATE users SET name = '{n}'\"\n",
            "service.py",
        )
        assert result.returncode == 1


class TestAllowsLegitimateUsage:
    """Patterns that must continue to pass."""

    def test_orm_query(self):
        result = _run_hook(
            "session.query(User).filter(User.id == 1).all()\n",
            "service.py",
        )
        assert result.returncode == 0

    def test_text_escape_hatch(self):
        """SQLAlchemy text() is the sanctioned way to run raw SQL."""
        result = _run_hook(
            'session.execute(text("SELECT 1"))\n',
            "service.py",
        )
        assert result.returncode == 0

    def test_test_file_exempted(self):
        result = _run_hook(
            'cursor.execute("DELETE FROM users")\n',
            "test_example.py",
        )
        assert result.returncode == 0

    def test_migration_file_exempted(self):
        result = _run_hook(
            'cursor.execute("CREATE TABLE foo (id INT)")\n',
            "migration_001.py",
        )
        assert result.returncode == 0

    def test_auth_db_exempted_for_statement_pattern(self):
        """Regression: auth_db.py was missing from one of the two allowlists."""
        result = _run_hook(
            'cursor.execute("SELECT * FROM users")\n',
            "auth_db.py",
        )
        assert result.returncode == 0

    def test_auth_db_exempted_for_fstring_pattern(self):
        result = _run_hook(
            'conn.execute(f"INSERT INTO logs VALUES ({val})")\n',
            "auth_db.py",
        )
        assert result.returncode == 0


class TestExecuteCallVariants:
    """Every execute-call variant in db_execute_patterns must be flagged.

    Locks in current behavior so a refactor of the pattern list can't silently
    drop coverage for one of the call styles.
    """

    def test_cursor_executemany_flagged(self):
        result = _run_hook(
            'cursor.executemany("INSERT INTO users VALUES (?)", rows)\n',
            "service.py",
        )
        assert result.returncode == 1

    def test_conn_execute_flagged(self):
        result = _run_hook(
            'conn.execute("DELETE FROM users WHERE id = 1")\n',
            "service.py",
        )
        assert result.returncode == 1

    def test_connection_execute_flagged(self):
        result = _run_hook(
            'connection.execute("SELECT * FROM users")\n',
            "service.py",
        )
        assert result.returncode == 1


class TestExemptionBranches:
    """Every branch of _is_raw_sql_exempt must be covered.

    If any branch silently regresses (e.g. DB_UTIL_FILES entry removed,
    _test.py suffix check dropped), one of these will fail.
    """

    def test_tests_directory_path_exempted(self):
        """Files under a /tests/ directory are exempt even without test_ prefix."""
        result = _run_hook(
            'cursor.execute("DELETE FROM users")\n',
            "tests/helpers.py",
        )
        assert result.returncode == 0

    def test_underscore_test_suffix_exempted(self):
        """The `_test.py` suffix (Go-style) is an exempted test convention."""
        result = _run_hook(
            'cursor.execute("DELETE FROM users")\n',
            "model_test.py",
        )
        assert result.returncode == 0

    def test_alembic_filename_exempted(self):
        result = _run_hook(
            'cursor.execute("CREATE TABLE foo (id INT)")\n',
            "alembic_env.py",
        )
        assert result.returncode == 0

    def test_sqlcipher_utils_exempted(self):
        result = _run_hook(
            'cursor.execute("SELECT * FROM users")\n',
            "sqlcipher_utils.py",
        )
        assert result.returncode == 0

    def test_backup_service_exempted(self):
        result = _run_hook(
            'cursor.execute("SELECT * FROM users")\n',
            "backup_service.py",
        )
        assert result.returncode == 0

    def test_encrypted_db_exempted(self):
        result = _run_hook(
            'cursor.execute("SELECT * FROM users")\n',
            "encrypted_db.py",
        )
        assert result.returncode == 0


class TestSkipLogic:
    """Lines that start with comments / docstrings / are empty must be skipped.

    The skip block at the top of the per-line loop is load-bearing: without it,
    any file explaining SQL in comments would be flagged. Lock in its behavior.
    """

    def test_full_line_comment_with_sql_not_flagged(self):
        """A comment line containing SQL keywords must not be flagged."""
        result = _run_hook(
            '# Example: cursor.execute("SELECT * FROM users")\n',
            "service.py",
        )
        assert result.returncode == 0

    def test_docstring_line_with_sql_not_flagged(self):
        """A line that starts with triple-quotes must be skipped."""
        result = _run_hook(
            '"""cursor.execute(\'SELECT * FROM users\')"""\n',
            "service.py",
        )
        assert result.returncode == 0

    def test_single_quote_docstring_line_not_flagged(self):
        result = _run_hook(
            "'''SELECT * FROM users'''\n",
            "service.py",
        )
        assert result.returncode == 0


class TestOrmPatternsNotFlagged:
    """Legitimate ORM builder chains must never trigger raw-SQL detection."""

    def test_filter_by_not_flagged(self):
        result = _run_hook(
            "session.query(User).filter_by(id=1).all()\n",
            "service.py",
        )
        assert result.returncode == 0

    def test_join_order_by_not_flagged(self):
        result = _run_hook(
            "session.query(User).join(Role).order_by(User.id).all()\n",
            "service.py",
        )
        assert result.returncode == 0

    def test_group_by_not_flagged(self):
        result = _run_hook(
            "session.query(User).group_by(User.role).all()\n",
            "service.py",
        )
        assert result.returncode == 0
