"""GO-SQL-001: SQL Injection detection for Go.

Detects string formatting and concatenation in SQL queries
that enable SQL injection attacks.
"""

from __future__ import annotations

import re

from . import GoRule
from ..models import Finding, Severity

# fmt.Sprintf used for SQL query construction
SQL_SPRINTF_PATTERN = re.compile(
    r'(?:db|tx|conn|rows|stmt)\s*\.\s*(?:Query|QueryRow|Exec|Prepare|QueryContext|ExecContext|QueryRowContext)\s*\('
    r'[^)]*fmt\.Sprintf',
    re.IGNORECASE,
)
# String concatenation in SQL queries
SQL_CONCAT_PATTERN = re.compile(
    r'(?:db|tx|conn|rows|stmt)\s*\.\s*(?:Query|QueryRow|Exec|Prepare|QueryContext|ExecContext|QueryRowContext)\s*\('
    r'[^)]*\+',
    re.IGNORECASE,
)
# Raw string with SQL keywords + fmt.Sprintf
RAW_SQL_SPRINTF = re.compile(
    r'fmt\.Sprintf\s*\(\s*["`]'
    r'(?:SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|GRANT|EXEC)\b',
    re.IGNORECASE,
)
# Direct string interpolation in SQL
SQL_DIRECT_INTERP = re.compile(
    r'(?:SELECT|INSERT|UPDATE|DELETE|WHERE)\s+[^"]*"\s*\+\s*\w+',
    re.IGNORECASE,
)


class GoSqlInjectionRule(GoRule):
    """Detects SQL injection patterns in Go."""

    @property
    def rule_id(self) -> str:
        return "GO-SQL-001"

    @property
    def name(self) -> str:
        return "SQL Injection (string formatting)"

    @property
    def severity(self) -> Severity:
        return Severity.CRITICAL

    @property
    def cwe(self) -> str:
        return "CWE-89"

    @property
    def description(self) -> str:
        return (
            "SQL query built with fmt.Sprintf or string concatenation "
            "enables SQL injection."
        )

    @property
    def detailed_description(self) -> str:
        return (
            "SQL query built with fmt.Sprintf or string concatenation "
            "enables SQL injection.\n\n"
            "Go's database/sql supports parameterized queries with $1, $2\n"
            "placeholders. Using fmt.Sprintf or string concatenation to build\n"
            "SQL queries bypasses this protection.\n\n"
            "Examples of vulnerable code:\n"
            '  db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %s", id))\n'
            '  db.Exec("DELETE FROM users WHERE name = \'" + name + "\'")\n\n'
            "How to fix:\n"
            '  - Use parameterized queries: db.Query("SELECT * FROM users WHERE id = $1", id)\n'
            "  - Use an ORM or query builder (sqlx, gorm)\n"
            "  - Never concatenate user input into SQL strings"
        )

    def check(self, source: str, filename: str = "") -> list[Finding]:
        """Check for SQL injection patterns."""
        findings: list[Finding] = []

        # fmt.Sprintf in db.Query/Exec
        for lineno, line, _match in self._search_lines(source, SQL_SPRINTF_PATTERN):
            findings.append(self._make_finding(
                lineno, line,
                "SQL query built with fmt.Sprintf — use parameterized queries.",
            ))

        # String concatenation in db.Query/Exec
        for lineno, line, _match in self._search_lines(source, SQL_CONCAT_PATTERN):
            if any(f.line == lineno for f in findings):
                continue
            findings.append(self._make_finding(
                lineno, line,
                "SQL query built with string concatenation.",
            ))

        # Raw SQL with fmt.Sprintf
        for lineno, line, _match in self._search_lines(source, RAW_SQL_SPRINTF):
            if any(f.line == lineno for f in findings):
                continue
            findings.append(self._make_finding(
                lineno, line,
                "SQL string constructed with fmt.Sprintf.",
            ))

        # Direct string interpolation
        for lineno, line, _match in self._search_lines(source, SQL_DIRECT_INTERP):
            if any(f.line == lineno for f in findings):
                continue
            findings.append(self._make_finding(
                lineno, line,
                "SQL string with direct concatenation.",
            ))

        return findings

    def _make_finding(self, lineno: int, line: str, desc: str) -> Finding:
        """Create a SQL injection finding."""
        return Finding(
            rule_id=self.rule_id,
            severity=self.severity,
            title="SQL Injection",
            description=desc,
            file="",
            line=lineno,
            code_snippet=line.strip()[:80],
            recommendation=(
                "Use parameterized queries with $1/$2 placeholders "
                "instead of string formatting."
            ),
            cwe=self.cwe,
        )
